Моя статья 15 самых недоиспользуемых особенностей .NET вызвала интересную дискуссию. Мне было интересно узнать, какой метод быстрее — ?? (нулевой оператор объединения) , метод GetValueOrDefault или ?: (условный оператор) . Недавно я прочитал в переполнении стека, что большинство людей считают, что метод GetValueOrDefault является самым быстрым среди этих трех. Тем не менее, я решил сделать свое исследование. Я не пытаюсь микрооптимизировать, Я думаю, что в 99% случаев не будет иметь значения, какой из трех подходов вы собираетесь использовать. Обычно вы должны выбрать тот, который легче поддерживать. Я не собираюсь спорить, какой из них более читабелен, потому что это другая тема. Скорее я собираюсь представить вам результаты моих исследований.
Нулевой оператор объединения
?? Оператор возвращает левый операнд, если он не равен нулю, или возвращает правый операнд. Обнуляемый тип может содержать значение или может быть неопределенным. ?? Оператор определяет значение по умолчанию, которое будет возвращено, когда тип, который можно обнулять, назначается типу, не обнуляемому.
int? x = null;
int y = x ?? -1;
Console.WriteLine("y now equals -1 because x was null => {0}", y);
int i = DefaultValueOperatorTest.GetNullableInt() ?? default(int);
Console.WriteLine("i equals now 0 because GetNullableInt() returned null => {0}", i);
string s = DefaultValueOperatorTest.GetStringValue();
Console.WriteLine("Returns 'Unspecified' because s is null => {0}", s ?? "Unspecified");
Официальная документация : https://msdn.microsoft.com/en-us/library/ms173224.aspx
Метод GetValueOrDefault
Извлекает значение текущего объекта Nullable <T> или значение объекта по умолчанию. Это быстрее чем ?? оператор.
float? yourSingle = -1.0f;
Console.WriteLine(yourSingle.GetValueOrDefault());
yourSingle = null;
Console.WriteLine(yourSingle.GetValueOrDefault());
// assign different default value
Console.WriteLine(yourSingle.GetValueOrDefault(-2.4f));
// returns the same result as the above statement
Console.WriteLine(yourSingle ?? -2.4f);
Если вы не укажете значение по умолчанию в качестве параметра метода, будет использоваться значение по умолчанию для используемого типа.
Официальная документация: https://msdn.microsoft.com/en-us/library/72cec0e0(v=vs.110).aspx
Условный оператор?
Условный оператор ( ? ? Возвращает одно из двух значений в зависимости от значения логического выражения. Ниже приведен синтаксис условного оператора.
условие ? first_expression: second_expression;
Условие должно оцениваться как истинное или ложное. Если условие истинно, first_expression оценивается и становится результатом. Если условие ложно, second_expression оценивается и становится результатом. Только одно из двух выражений оценивается.
int input = Convert.ToInt32(Console.ReadLine());
// ?: conditional operator.
string classify = (input > 0) ? "positive" : "negative";
Официальная документация: https://msdn.microsoft.com/en-us/library/ty67wk28.aspx
GetValueOrDefault и Null Coalescing Оператор Внутренние органы
Вы можете найти исходный код метода GetValueOrDefault по следующему URL . У метода есть две перегрузки: одна без параметров, а другая требует возврата значения по умолчанию, если переменная равна нулю.
[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault()
{
return value;
}
[System.Runtime.Versioning.NonVersionable]
public T GetValueOrDefault(T defaultValue)
{
return hasValue ? value : defaultValue;
}
Как говорится в коде, внутри метода GetValueOrDefault используется условный оператор.
Мне всего этого было недостаточно, поэтому я декомпилировал следующий код, чтобы узнать, как он переводится на общий промежуточный язык (CIL) . Для этой работы я использовал бесплатный декомпилятор Telerik .NET — Telerik JustDecompile .
public class GetValueOrDefaultAndNullCoalescingOperatorInternals
{
public void GetValueOrDefaultInternals()
{
int? a = null;
var x = a.GetValueOrDefault(7);
}
public void NullCoalescingOperatorInternals()
{
int? a = null;
var x = a ?? 7;
}
}
GetValueOrDefault CIL
.method public hidebysig instance void GetValueOrDefaultInternals () cil managed
{
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<int32> a
)
IL_0000: ldloca.s a
IL_0002: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0008: ldloca.s a
IL_000a: ldc.i4.7
IL_000b: call instance int32 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault(!0)
IL_0010: pop
IL_0011: ret
}
Нулевой оператор коалесцирования CIL
.method public hidebysig instance void NullCoalescingOperatorInternals () cil managed
{
.locals init (
[0] valuetype [mscorlib]System.Nullable`1<int32> a,
[1] valuetype [mscorlib]System.Nullable`1<int32> CS$0$0000
)
IL_0000: ldloca.s a
IL_0002: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0008: ldloc.0
IL_0009: stloc.1
IL_000a: ldloca.s CS$0$0000
IL_000c: call instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
IL_0011: brtrue.s IL_0014
IL_0013: ret
IL_0014: ldloca.s CS$0$0000
IL_0016: call instance int32 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
IL_001b: pop
IL_001c: ret
}
Насколько я могу справиться с кодом CIL, я думаю, что х ?? у превращается в x.HasValue? x.GetValueOrDefault (): y. Что автоматически должно означать, что, скорее всего, первое будет намного быстрее, чем позднее.
Который работает быстрее — нулевой оператор объединения, GetValueOrDefault или условный оператор
Для сравнения различных тестовых случаев я создал специализированный класс профилировщика.
public static class Profiler
{
public static TimeSpan Profile(long iterations, Action actionToProfile)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
actionToProfile();
}
watch.Stop();
return watch.Elapsed;
}
public static string FormatProfileResults(long iterations, TimeSpan profileResults)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(string.Format("Total: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
profileResults.TotalMilliseconds, profileResults.Ticks, iterations));
var avgElapsedMillisecondsPerRun = profileResults.TotalMilliseconds / (double)iterations;
var avgElapsedTicksPerRun = profileResults.Ticks / (double)iterations;
sb.AppendLine(string.Format("AVG: {0:0.00} ms ({1:N0} ticks) (over {2:N0} iterations)",
avgElapsedMillisecondsPerRun, avgElapsedTicksPerRun, iterations));
return sb.ToString();
}
}
Все датчики выполнены в конфигурации выпуска. Правильный инструмент для написания тестов — секундомер в пространстве имен System.Diagnostics . (Я отмечаю, что это пространство имен хорошо названо; все здесь полезно для диагностики проблем). DateTime.Now — неподходящий инструмент для работы, он был разработан, чтобы решить другую проблему. Он сложнее в использовании, чем секундомер, и его точность в тысячи или миллионы раз меньше. Избегайте этого полностью при написании тестов в C #.
Несоблюдение затрат на сборку мусора может привести к тому, что вы не сможете измерить истинную стоимость операции, или заставить вас списать эти затраты с неверного кода. То, что я обычно делаю при сравнительном тестировании, особенно если это сравнительный сравнительный анализ, — это заставить сборщик мусора выполнять полный сбор всех трех поколений до и после каждого теста.
Чтобы заставить сборщик мусора выполнить полную сборку, используйте следующий код:
GC.Collect();
GC.WaitForPendingFinalizers();
Это шесть контрольных примеров, которые я тестировал.
public static class GetValueOrDefaultVsNullCoalescingOperatorTest
{
public static void ExecuteWithGetValueOrDefault()
{
int? a = null;
int? b = 3;
int? d = null;
int? f = null;
int? g = null;
int? h = null;
int? j = null;
int? k = 7;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a.GetValueOrDefault(7);
var y = b.GetValueOrDefault(7);
var z = d.GetValueOrDefault(6) + f.GetValueOrDefault(3) + g.GetValueOrDefault(1) + h.GetValueOrDefault(1) + j.GetValueOrDefault(5) + k.GetValueOrDefault(8);
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithGetValueOrDefaultT", formattedProfileResult);
}
public static void ExecuteWithNullCoalescingOperator()
{
int? a = null;
int? b = 3;
int? d = null;
int? f = null;
int? g = null;
int? h = null;
int? j = null;
int? k = 7;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a ?? 7;
var y = b ?? 7;
var z = (d ?? 6) + (f ?? 3) + (g ?? 1) + (h ?? 1) + (j ?? 5) + (k ?? 8);
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithNullCoalescingOperatorT", formattedProfileResult);
}
public static void ExecuteWithConditionalOperator()
{
int? a = null;
int? b = 3;
int? d = null;
int? f = null;
int? g = null;
int? h = null;
int? j = null;
int? k = 7;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a.HasValue ? a : 7;
var y = b.HasValue ? b : 7;
var z = (d.HasValue ? d : 6) + (f.HasValue ? f : 3) + (g.HasValue ? g : 1) + (h.HasValue ? h : 1) + (j.HasValue ? j : 5) + (k.HasValue ? k : 8);
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithConditionalOperatorT", formattedProfileResult);
}
public static void ExecuteWithGetValueOrDefaultZero()
{
int? a = null;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a.GetValueOrDefault();
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithGetValueOrDefaultZeroT", formattedProfileResult);
}
public static void ExecuteWithNullCoalescingOperatorZero()
{
int? a = null;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a ?? 0;
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithNullCoalescingOperatorZeroT", formattedProfileResult);
}
public static void ExecuteWithConditionalOperatorZero()
{
int? a = null;
var profileResult = Profiler.Profile(100000,
() =>
{
var x = a.HasValue ? a : 0;
});
string formattedProfileResult = Profiler.FormatProfileResults(100000, profileResult);
FileWriter.WriteToDesktop("ExecuteWithConditionalOperatorZeroT", formattedProfileResult);
}
}
Выполненные тестовые случаи
(1.) GetValueOrDefault с установленным значением по умолчанию
(2.) Нулевой оператор слияния с установленным значением по умолчанию
(3.) Условный оператор с установленным значением по умолчанию
(4.) GetValueOrDefault без значения по умолчанию
(5.) Нулевой оператор объединения, возвращающий значение по умолчанию 0
(6.) Условный оператор, возвращающий значение по умолчанию 0
Результаты домашнего теста
После нескольких тестовых прогонов вы можете просмотреть результаты моего исследования.
Кроме того, я создал две сравнительные таблицы для лучшей визуализации результатов. Имейте в виду, что я исключил результаты для третьего контрольного примера, потому что я не знаю почему, но этот случай был намного медленнее, чем остальные.
Ниже приведена таблица со средним временем выполнения для всех тестовых случаев.
Найдите под диаграммой, содержащей средние значения для всех тестов.
Как видно из моих результатов, когда вы хотите вернуть значение по умолчанию, отличное от значения по умолчанию для текущего типа Nullable, лучшим исполнителем является оператор объединения нулей (??) . Однако, когда вы хотите вернуть значение по умолчанию для метода типа GetValueOrDefault, это немного быстрее.
Pro Benchmark через Telerik JustTrace
Результаты моего домашнего теста были недостаточны для меня, поэтому я установил Telerik JustTrace (профилировщик памяти и производительности 2-в-1 для .NET и нативных приложений). Результаты для одних и тех же тестовых случаев немного отличались.
Для возврата другого значения по умолчанию метод GetOrDefaultValue был более чем на 8% быстрее, чем оператор слияния NULL. Кроме того, он снова был немного быстрее среди тестовых случаев, когда возвращается значение по умолчанию для обнуляемого типа.
Пока в серии C #
1. Реализовать код C Paste Paste
2. MSBuild TCP IP Logger C # Код
3. Реестр Windows: чтение, запись, код C #
4. Измените файл .config во время выполнения кода C #
5. Общие свойства Validator C # Code
6. Уменьшенный AutoMapper — Auto-Map объектов на 180% быстрее
7. 7 новых интересных функций в C # 6.0
8. Типы покрытия кода — примеры в C #
9. Неудачные тесты MSTest Rerun с помощью программы-оболочки MSTest.exe
10. Советы по эффективной организации использования в Visual Studio
11. 19 обязательных сочетаний клавиш Visual Studio — часть 1
12. 19 Обязательных сочетаний клавиш Visual Studio — Часть 2
13. Укажите ссылки на сборки на основе конфигурации сборки в Visual Studio
14. 15 самых малоиспользуемых функций .NET
15. 15 самых малоиспользуемых функций .NET, часть 2
16. Аккуратные хитрости для легкого форматирования валюты в C #
17. Утвердите DateTime правильный путь MSTest NUnit C # Code
18. Что работает быстрее — оператор объединения нулей, GetValueOrDefault или условный оператор
Исходный код
Вы можете скачать полный исходный код из моего репозитория Github .
Если вам понравились мои публикации, не стесняйтесь ПОДПИСАТЬСЯ
Кроме того, нажмите эти кнопки обмена. Спасибо!