Расширение LINQ для принятия нулевых переменных
Во время работы с расширениями Linq нормальный вид выглядит следующим образом:
IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
if (enumerable != null)
{
sum = enumerable.Sum();
}
Чтобы повысить качество кода, я написал следующий метод расширения, который проверяет нулевые числа и прерывает выполнение linq.
public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null) yield break;
foreach (var item in enumerable)
{
yield return item;
}
}
Итак, я могу реорганизовать код таким образом:
var sum = GetEnumerable().IgnoreIfEmpty().Sum();
Мои вопросы сейчас:
- Какие штрафы связаны с моим методом расширения во время выполнения?
- Является ли хорошей практикой расширение linq таким образом?
Update:
Моя целевая структура: 3.5
Ответы
Ответ 1
- У вас будет накладные расходы на вызов метода, это будет незначительно, если вы не запускаете его в узком цикле или сценарии критики производительности. Это только тень по сравнению с чем-то вроде вызова базы данных или записи в файловую систему. Обратите внимание, что метод, вероятно, не будет встроен, поскольку он является перечислителем.
- Все о читаемости/ремонтопригодности. Что я ожидаю, когда увижу
GetEnumerable().IgnoreIfEmpty().Sum();
? В этом случае это имеет смысл.
Обратите внимание, что с С# 6 мы можем использовать следующий синтаксис: GetEnumerable()?.Sum()
, который возвращает int?
. Вы можете написать GetEnumerable()?.Sum() ?? 0
или GetEnumerable()?.Sum().GetValueOrDefault()
, чтобы получить ненулевое целое число, которое будет по умолчанию равно нулю.
Если вы действительно заинтересованы в производительности, вы также можете немного реорганизовать свой метод, чтобы он не был перечислителем. Это может увеличить вероятность вложения, хотя я не имею понятия о "тайной" логике JIT-компилятора:
public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null) return Enumerable.Empty<T>();
return enumerable;
}
В общем, о расширении Linq, я думаю, что это прекрасно, если код имеет смысл. MSDN даже имеет статью об этом. Если вы посмотрите на стандартные методы Where
, Select
в Linq и забудьте об оптимизации производительности, которые у них есть, все методы в основном однострочные методы.
Ответ 2
Какие штрафы связаны с моим методом расширения во время выполнения?
Ваш метод расширения преобразуется в состояние-машина, поэтому есть минимальные накладные расходы, но это не должно быть заметно.
Хорошо ли продлить linq таким образом?
В вашем вопросе вы указываете:
При работе с расширениями Linq это нормально, чтобы увидеть код, подобный этому (вставьте здесь перечисляемую нулевую проверку)
И я прошу отличить. В распространенной практике не возвращает значение null, где ожидается IEnumerable<T>
. Большинство случаев должны возвращать пустую коллекцию (или IEnumerable
), оставляя null
для исключительной, потому что null не пуст. Это сделает ваш метод полностью избыточным. Используйте Enumerable.Empty<T>
, если необходимо.
Ответ 3
Вы можете пропустить дополнительный метод расширения и использовать оператор null coalescing - это то, для чего он нужен, и одноразовая проверка на тождественность должна быть намного более эффективной чем другой конечный автомат:
IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
sum = (enumerable ?? Enumerable.Empty<int>()).Sum();
Ответ 4
В большинстве случаев мы пишем много кода только потому, что мы очарованы красотой нашего творения - не потому, что нам это действительно нужно - и затем мы называем это абстракцией, возможностью повторного использования, расширяемости, и т.д.
Является ли этот необработанный фрагмент менее читаемым или менее растяжимым или менее повторным или медленным:
var sum = GetEnumerable().Where(a => a != null).Sum();
Чем меньше кода вы пишете, тем меньше кода, который вы тестируете, прост.
BTW - хорошо писать методы расширения, если вы можете это обосновать.