2016-12-08 3 views
10

Как обычно, int? означает System.Nullable<int> (или System.Nullable`1[System.Int32]).Почему сумма Linq-to-Objects из последовательности нулевых значений сама по себе является нулевой?

Предположим, у вас есть оперативная память IEnumerable<int?> (например, List<int?>), назовем ее seq; то вы можете найти его сумму с:

var seqSum = seq.Sum(); 

Конечно это идет к перегрузке int? IEnumerable<int?>.Sum() (documentation) метод расширения, который на самом деле статический метод на System.Linq.Enumerable.

Однако метод никогда не возвращает null, , так почему же тип возврата объявлен как Nullable<>? Даже в тех случаях, когда seq представляет собой пустую коллекцию или, в более общем смысле, коллекцию, все элементы которой являются величинами null типа int?, метод Sum по-прежнему возвращает ноль, а не null.

Это видно из документации, но и из исходного кода System.Core.dll:

public static int? Sum(this IEnumerable<int?> source) { 
    if (source == null) throw Error.ArgumentNull("source"); 
    int sum = 0; 
    checked { 
     foreach (int? v in source) { 
      if (v != null) sum += v.GetValueOrDefault(); 
     } 
    } 
    return sum; 
} 

Заметим, что существует только один return заявление и его выражение sum имеет тип int (который будет затем неявно преобразуется в int? путем обертывания).

Кажется расточительным всегда обернуть возвращаемое значение. (При желании вызывающий может всегда делать неявно на своей стороне.)

Кроме того, этот тип возврата может привести вызывающего абонента к написанию кода, такого как if (!seqSum.HasValue) { /* logic to handle this */ }, который в действительности будет недоступен (факт, который компилятор C# не может знать из).

Так почему же этот параметр возврата не просто объявлен как int без значения NULL?

Интересно, есть ли какие-либо выгоды от того же типа возврата, что и у int? IQueryable<int?>.Sum() (в классе System.Linq.Queryable). Этот последний метод может возвращать null на практике, если есть поставщики LINQ (возможно, LINQ to SQL?), Которые его реализуют.

+0

Я предполагаю, что это продолжение формата 'Sum', возвращающего тот же тип, который вы суммируете. В конечном итоге только один из разработчиков C# может ответить на это. – juharr

+2

Совместимость с 'Queryable' - это единственное, о чем я могу думать. Вы имеете право сказать, что он никогда не возвращает «null»: https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Sum.cs –

+0

@HimBromBeere нет перегрузка формы 'Sum (этот IEnumerable источник)'; API-интерфейсы * generic * также принимают * селектор * формы 'Func ' и т. д. –

ответ

2

Несколько замечаний отметили, что на самом деле это не отвечает (или только мнение основано без официального ответа). Я не буду спорить об этом. Тем не менее, все еще можно провести анализ доступного кода и сформировать достаточно прочную теорию. Моя просто так, что это существующий шаблон MS.

Если смотреть через остальную часть System.Linq.Enumerable, в частности, связанные математические функции, вы начинаете видеть образец, имеющий тенденцию возвращаться один и тот же тип, как входной параметр, если возвращение не имеет конкретную причину для быть другого типа.

Смотрите следующие функции:

Max():

public static int Max(this IEnumerable<int> source); 
public static int? Max(this IEnumerable<int?> source); 
public static long Max(this IEnumerable<long> source); 
public static long? Max(this IEnumerable<long?> source); 

Min():

public static int Min(this IEnumerable<int> source); 
public static int? Min(this IEnumerable<int?> source); 
public static long Min(this IEnumerable<long> source); 
public static long? Min(this IEnumerable<long?> source); 

Sum():

public static int Sum(this IEnumerable<int> source); 
public static int? Sum(this IEnumerable<int?> source); 
public static long Sum(this IEnumerable<long> source); 
public static long? Sum(this IEnumerable<long?> source); 

Для исключения из правила, посмотрите на Average ...

public static double Average(this IEnumerable<int> source); 
public static double? Average(this IEnumerable<int?> source); 

Вы можете видеть, что она по-прежнему сохраняет тип Nullable<T>, однако тип возвращаемого значения должен быть изменен до подходящего типа для поддержки результата, усреднение целых чисел дает.

Когда вы смотрите дальше в Average, хотя, вы увидите следующее:

public static float Average(this IEnumerable<float> source); 
public static float? Average(this IEnumerable<float?> source); 

Снова вернемся к шаблону по умолчанию возвращения того же типа, что и исходный тип входящего.

Теперь, когда мы видим этот шаблон здесь, давайте посмотрим, увидим ли мы это где-нибудь еще ... давайте посмотрим на System.Math, так как мы на эту тему.

Опять же, здесь мы видим ту же картину, используя один и тот же тип возвращаемого значения:

public static int Abs(int value); 
public static long Abs(long value); 

public static int Max(int val1, int val2); 
public static long Max(long val1, long val2); 

я упомяну его снова, это то, что составляет к «мнению ответ». Я искал любые лучшие рекомендации MS или информацию о спецификации языка, которые могли бы намекнуть на то, что это языковой шаблон для MS для резервного копирования моего анализа, но я ничего не мог найти. При этом, если вы посмотрите на различные места в основных библиотеках .Net, особенно в пространстве имен System.Collections.Generic, вы увидите, что, если нет конкретной причины, тип возврата соответствует типу коллекции.

Я не вижу причин, по которым это правило отклоняется от Nullable<T>.

+1

Я подтверждаю наблюдение. Возможно, существует тенденция отдавать предпочтение возвращению одного и того же типа. Но также обратите внимание, что такие вещи, как 'Max',' Min' и 'Average', которые не определены для пустых (count-0) источников, фактически возвратят' null' в этом случае. Но только с последовательностью нулевых значений; эти методы взорвутся с исключением в случае с недействительным значением на пустой входной последовательности. –

+0

Таково поведение, которого я ожидал бы. Учитывая, что исходный (не нулевой) 'Max' (и т. Д.) Генерирует исключение, если список пуст. Мое ожидание было бы либо бросить исключение, как оригинал, либо, как в данном случае, вернуть нулевое значение «Nullable ». Я ожидаю, что, поскольку в нормальных ситуациях операции, выполняемые в нулевом наборе, не имеют значения, следовательно, сами являются нулями. 'Sum' немного отличается тем, что вы начинаете со значения 0 и выполняете математические операции, используя содержимое набора. Если набор пуст, у вас все еще есть 0. Если набор содержит список нулей, 0 все еще. – gmiley

+0

Иными словами, каково максимальное значение пустого набора? Теперь, какова ценность пустой группы, добавленной вместе? – gmiley

Смежные вопросы