none
C# Объясните работу LINQ в данном примере. RRS feed

  • Вопрос

  • Почему при одинаковом коде получаю разный результат.

    <img alt="Вызов из Main()" src="https://social.msdn.microsoft.com/Forums/getfile/1455670" />

    <img alt="Вызов из UnitTest" src="https://social.msdn.microsoft.com/Forums/getfile/1455671" />

    28 июня 2019 г. 13:25

Ответы

  • Тут все упирается в то, как работают Select и LastOrDefault. Возьмите следующий код:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            
            static void Main(string[] args)
            {
                var array = new int[] { 1, 2, 3 };
                var sel = array.Select((x) => { Console.WriteLine(x.ToString() + " selected"); return x; });
                var t = sel.GetType();
    
                Console.WriteLine("Select iterator type: "+t.ToString()+"\n");
    
                Console.WriteLine("Select iterator interfaces: ");
                Console.WriteLine(String.Join("\n", t.GetInterfaces().Select(x => x.ToString())));
                Console.WriteLine("\nLastOrDefault: "+sel.LastOrDefault());
                
    
            }
        }    
    }

    При запуске в .NET Framework он даст такой результат:

    Select iterator type: System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32]

    Select iterator interfaces:
    System.Collections.Generic.IEnumerable`1[System.Int32]
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerator`1[System.Int32]
    System.IDisposable
    System.Collections.IEnumerator
    1 selected
    2 selected
    3 selected

    LastOrDefault: 3

    При запуске под .NET Core:

    Select iterator type: System.Linq.Enumerable+SelectArrayIterator`2[System.Int32,System.Int32]

    Select iterator interfaces: 
    System.Collections.Generic.IEnumerable`1[System.Int32]
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerator`1[System.Int32]
    System.IDisposable
    System.Collections.IEnumerator
    System.Linq.IPartition`1[System.Int32]
    System.Linq.IIListProvider`1[System.Int32]
    3 selected

    LastOrDefault: 3

    Как видно, в .NET Core результатом Select является совершенно другой тип, который реализует интерфейс IPartition. Метод LastOrDefault содержит оптимизацию, которая заставляет его, если тип итератора реализует IPartition, не вычислять всю последовательность, я сразу брать ее последний элемент (это видно, если в выражение Select поместить вывод). Таким образом, ваш код, который рассчитывал на вычисление всей последовательности, ломается.  


    • Изменено VadimTagil 29 июня 2019 г. 16:21
    • Помечено в качестве ответа Anonymous7811 30 июня 2019 г. 5:59
    29 июня 2019 г. 16:20

Все ответы

  • Покажите код в виде текста, используйте блок для кода (вторая иконка справа).

    Так же детально опишите что вы ожидайте и что вы на деле получили.


    This posting is provided "AS IS" with no warranties, and confers no rights.

    28 июня 2019 г. 17:21
    Модератор
  • При вызове из Main(), я получаю сумму этих чисел 579.

    public static class Kata
        {
            public static void Main()
            {
                Console.WriteLine(sumStrings("123", "456"));
            }
    
            public static BigInteger Sum(this IEnumerable<BigInteger> source)
            {
                var current = new BigInteger();
    
                return source
                    .Select(n => current += n)
                    .LastOrDefault();
            }
    
            public static string sumStrings(params string[] array)
            {
                return array
                    .Select(i => BigInteger.TryParse(i, out BigInteger answer) ? answer : 0)
                    .Sum()
                    .ToString();
            }
        }

    При вызове из UnitTest, я получаю ответ 456(то есть последнее число моего массива), а ожидаю такой же ответ как и при вызове Main().

    using ConsoleApp1;
    using NUnit.Framework;
    
    namespace Tests
    {
        [TestFixture]
        public class KataTest
        {
            [Test]
            public void Given123And456Returns579()
            {
                Assert.AreEqual("579", Kata.sumStrings("123", "456"));
            }
        }
    }
    
    using System;
    using System.Linq;
    using System.Numerics;
    using System.Collections.Generic;
    
    namespace ConsoleApp1
    {
        public static class Kata
        {
            public static void Main()
            {
                Console.WriteLine(sumStrings("123", "456"));
            }
    
            public static BigInteger Sum(this IEnumerable<BigInteger> source)
            {
                var current = new BigInteger();
    
                return source
                    .Select(n => current += n)
                    .LastOrDefault();
            }
    
            public static string sumStrings(params string[] array)
            {
                return array
                    .Select(i => BigInteger.TryParse(i, out BigInteger answer) ? answer : 0)
                    .Sum()
                    .ToString();
            }
        }
    }

    29 июня 2019 г. 6:20
  • Возможно TryParse() возвращает false, например из за разных региональных настроек. 

    Почему бы вам не использовать отладчик для выяснения данного казуса? 


    This posting is provided "AS IS" with no warranties, and confers no rights.

    29 июня 2019 г. 6:40
    Модератор
  • Я пользовался отладчиком и на картинках это видно. Я не понимаю, почему я при вызове из UnitTest  в методе sumStrings, не пробегаюсь по Select и не передаю эти значение поочередно в Select метода Sum, как это делаю при вызове из Main(), а беру его только последнее значение.
    29 июня 2019 г. 8:14
  • Любопытно.

    Я попробовал ваш код. Скопировал без изменений. Тест проходит успешно. В консоли тоже верный результат.

    29 июня 2019 г. 10:58
  • Petalvik, а можно скрин вашего результата, так как без доказательств не особо вериться. Я вам скину даже 2. Первый из моей Visual Studio, второй с сайта этой задачи(которую я решил другим способом, так что не надо скидывать мне решение). Мне нужно объяснение почему такое различие при вызове из Main() и из UnitTest.

    <img alt="Visual Studio" src="https://social.msdn.microsoft.com/Forums/getfile/1455849" />

    <img alt="Сайт" src="https://social.msdn.microsoft.com/Forums/getfile/1455852" />

    29 июня 2019 г. 12:35
  • Подобный результат можно воспроизвести, например, на https://dotnetfiddle.net при выборе .NET Core 2.2. Скорее всего, это просто баг. Какое окружение использует ваш "UnitTest"?
    29 июня 2019 г. 14:27
  • .NET Core 2.1, если вы об этом.
    29 июня 2019 г. 14:45
  • Тут все упирается в то, как работают Select и LastOrDefault. Возьмите следующий код:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            
            static void Main(string[] args)
            {
                var array = new int[] { 1, 2, 3 };
                var sel = array.Select((x) => { Console.WriteLine(x.ToString() + " selected"); return x; });
                var t = sel.GetType();
    
                Console.WriteLine("Select iterator type: "+t.ToString()+"\n");
    
                Console.WriteLine("Select iterator interfaces: ");
                Console.WriteLine(String.Join("\n", t.GetInterfaces().Select(x => x.ToString())));
                Console.WriteLine("\nLastOrDefault: "+sel.LastOrDefault());
                
    
            }
        }    
    }

    При запуске в .NET Framework он даст такой результат:

    Select iterator type: System.Linq.Enumerable+WhereSelectArrayIterator`2[System.Int32,System.Int32]

    Select iterator interfaces:
    System.Collections.Generic.IEnumerable`1[System.Int32]
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerator`1[System.Int32]
    System.IDisposable
    System.Collections.IEnumerator
    1 selected
    2 selected
    3 selected

    LastOrDefault: 3

    При запуске под .NET Core:

    Select iterator type: System.Linq.Enumerable+SelectArrayIterator`2[System.Int32,System.Int32]

    Select iterator interfaces: 
    System.Collections.Generic.IEnumerable`1[System.Int32]
    System.Collections.IEnumerable
    System.Collections.Generic.IEnumerator`1[System.Int32]
    System.IDisposable
    System.Collections.IEnumerator
    System.Linq.IPartition`1[System.Int32]
    System.Linq.IIListProvider`1[System.Int32]
    3 selected

    LastOrDefault: 3

    Как видно, в .NET Core результатом Select является совершенно другой тип, который реализует интерфейс IPartition. Метод LastOrDefault содержит оптимизацию, которая заставляет его, если тип итератора реализует IPartition, не вычислять всю последовательность, я сразу брать ее последний элемент (это видно, если в выражение Select поместить вывод). Таким образом, ваш код, который рассчитывал на вычисление всей последовательности, ломается.  


    • Изменено VadimTagil 29 июня 2019 г. 16:21
    • Помечено в качестве ответа Anonymous7811 30 июня 2019 г. 5:59
    29 июня 2019 г. 16:20
  • Хороший пример побочных эффектов и почему их не следует использовать.

    Правильным решением видимо будет использование Aggregate() для реализации самодельного Sum().


    This posting is provided "AS IS" with no warranties, and confers no rights.

    29 июня 2019 г. 18:53
    Модератор
  • Вот я тоже думал, что дело может быть в .NET Core.

    Я запускал на .NET Framework.

    29 июня 2019 г. 19:01
  • VadimTagil, Спасибо за такое детальное объяснение!
    30 июня 2019 г. 5:59