Update: Самым быстрым оказался алгоритм V7
Продолжение предыдущего вопроса.
Есть несколько вариантов реализации функции для проверки переменной на значение по умолчанию. Из комментариев к предыдущему вопросу я понял лишь то, что мне еще очень много предстоит выучить в C#. Помогите пожалуйста найти самый производительный и надежный вариант реализации функции.
В качестве параметра функции может быть все что угодно, включая класс, структуру, типы на подобие int? и так далее.
Некоторые варианты реализации функции:
V1:
bool IsDefault<T>(T o)
{
if (o == null) // => ссылочный тип или nullable
return true;
if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
return false;
var type = o.GetType();
//для .net core type.GetTypeInfo().IsClass
if (type.IsClass)
return false;
else // => тип-значение, есть конструктор по умолчанию
return Activator.CreateInstance(type).Equals(o);
}
V3 (улучшенный)
bool isDefault<T>(T o)
{
return (o==null)?true: // считаем что null default
o.GetType().IsValueType && !typeof(T).IsGenericType ?
o.Equals(Activator.CreateInstance(o.GetType())): // default типа который внутри nullable
o.Equals(default(T)); // настоящий default
}
V5 смешать V1 и большое кол-во перегрузок под все типы:
public class DefaultChecker
{
bool IsDefault(byte value) => value == 0;
bool IsDefault(byte? value) => value == null;
bool IsDefault(sbyte value) => value == 0;
bool IsDefault(sbyte? value) => value == null;
bool IsDefault(int value) => value == 0;
bool IsDefault(int? value) => value == null;
bool IsDefault(uint value) => value == 0;
bool IsDefault(uint? value) => value == null;
bool IsDefault(short value) => value == 0;
bool IsDefault(short? value) => value == null;
bool IsDefault(ushort value) => value == 0;
bool IsDefault(ushort? value) => value == null;
bool IsDefault(long value) => value == 0;
bool IsDefault(long? value) => value == null;
bool IsDefault(ulong value) => value == 0;
bool IsDefault(ulong? value) => value == null;
bool IsDefault(float value) => value == 0.0F;
bool IsDefault(float? value) => value == null;
bool IsDefault(double value) => value == 0.0D;
bool IsDefault(double? value) => value == null;
bool IsDefault(char value) => value == '\0';
bool IsDefault(char? value) => value == null;
bool IsDefault(bool value) => !value;
bool IsDefault(bool? value) => value == null;
bool IsDefault(string value) => value == null;
bool IsDefault(decimal value) => value == 0.0M;
bool IsDefault(decimal? value) => value == null;
public bool IsDefault<T>(T value)
{
if (value == null) // => ссылочный тип или nullable
return true;
if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
return false;
var type = value.GetType();
//для .net core type.GetTypeInfo().IsClass
if (type.IsClass)
return false;
else // => тип-значение, есть конструктор по умолчанию
return Activator.CreateInstance(type).Equals(value);
}
}
V6:
class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }
static bool IsDefault<T>(T o, RequireClass<T> ignore = null) where T : class
{
if (o == null)
return true;
if (!(o is ValueType)) // не упакованная ли это структура?
return false; // нет - выходим
return Activator.CreateInstance(o.GetType()).Equals(o); // медленный путь
}
static bool IsDefault<T>(T? o) where T : struct =>
o == null;
static bool IsDefault<T>(T o, RequireStruct<T> ignore = null) where T : struct =>
default(T).Equals(o); // default(T) не требует рефлексии
V7 смешать V6 и большое кол-во перегрузок под все типы:
bool IsDefault(byte value) => value == 0;
bool IsDefault(byte? value) => value == null;
bool IsDefault(sbyte value) => value == 0;
bool IsDefault(sbyte? value) => value == null;
bool IsDefault(int value) => value == 0;
bool IsDefault(int? value) => value == null;
bool IsDefault(uint value) => value == 0;
bool IsDefault(uint? value) => value == null;
bool IsDefault(short value) => value == 0;
bool IsDefault(short? value) => value == null;
bool IsDefault(ushort value) => value == 0;
bool IsDefault(ushort? value) => value == null;
bool IsDefault(long value) => value == 0;
bool IsDefault(long? value) => value == null;
bool IsDefault(ulong value) => value == 0;
bool IsDefault(ulong? value) => value == null;
bool IsDefault(float value) => value == 0.0F;
bool IsDefault(float? value) => value == null;
bool IsDefault(double value) => value == 0.0D;
bool IsDefault(double? value) => value == null;
bool IsDefault(char value) => value == '\0';
bool IsDefault(char? value) => value == null;
bool IsDefault(bool value) => !value;
bool IsDefault(bool? value) => value == null;
bool IsDefault(string value) => value == null;
bool IsDefault(decimal value) => value == 0.0M;
bool IsDefault(decimal? value) => value == null;
bool IsDefault<T>(T o, RequireClass<T> ignore = null) where T : class
{
if (o == null)
return true;
if (!(o is ValueType))
return false;
return Activator.CreateInstance(o.GetType()).Equals(o);
}
bool IsDefault<T>(T? o) where T : struct =>
o == null;
bool IsDefault<T>(T o, RequireStruct<T> ignore = null) where T : struct =>
default(T).Equals(o);
---Реализации, которые не проходят тест---
V2:
public object GetDefaultValue(Type target)
{
Expression<Func<object>> e = Expression.Lambda<Func<object>>(
Expression.Convert(
Expression.Default(target), typeof(object)));
return e.Compile()();
}
public bool IsDefault(object o)
{
if(o == null)
throw new ArgumentNullException(nameof(o));
return o.Equals(GetDefaultValue(o.GetType()));
}
V3:
bool isDefault<T>(T o)
{
return (o==null)?true:
o.GetType().IsValueType ?
Activator.CreateInstance(o.GetType()).Equals(o) :
o.Equals(default(T));
}
V4:
//isDefault((object)0) этот вариант даст false
bool isDefault<T>(T o)
{
return (o==null)?(default(T)==null):o.Equals(default(T));
}
Для начала, нужно составить юнит-тесты на желаемую функцию. Например, такие:
Debug.Assert(IsDefault(default(string)));
Debug.Assert(IsDefault((object)default(string)));
Debug.Assert(!IsDefault(string.Empty));
Debug.Assert(!IsDefault((object)string.Empty));
Debug.Assert(IsDefault(default(int)));
Debug.Assert(!IsDefault(1));
Debug.Assert(IsDefault(default(int?)));
Debug.Assert(!IsDefault((int?)0));
Debug.Assert(IsDefault((object)0));
Debug.Assert(!IsDefault((object)1));
Пробуем различные имплементации:
ArgumentNullException вместо того, чтобы вернуть false, на Debug.Assert(IsDefault(default(string)));.Debug.Assert(!IsDefault((int?)0)); проходит. Debug.Assert(IsDefault((object)0)).Окей, на текущий момент тесты проходят V1, V3, V5, V6 и V7. Тестировалось при помощи BenchmarkDotNet, результаты ниже. Вот результаты:
BenchmarkDotNet=v0.10.9, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i7-6700 CPU 3.40GHz (Skylake), ProcessorCount=8
Frequency=3328123 Hz, Resolution=300.4697 ns, Timer=TSC
[Host] : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2102.0
DefaultJob : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2102.0
Method | Mean | Error | StdDev |
------- |-----------:|---------:|---------:|
TestV1 | 1,793.9 ns | 4.960 ns | 4.639 ns |
TestV3 | 1,260.1 ns | 6.523 ns | 5.782 ns |
TestV5 | 1,195.5 ns | 9.269 ns | 7.740 ns |
TestV6 | 481.8 ns | 2.526 ns | 2.363 ns |
TestV7 | 445.3 ns | 1.847 ns | 1.728 ns |
Обратите внимание, что код проверки пробегает в худшем случае за менее двух, а в лучшем — за половину микросекунды (то есть, одной миллионной части секунды). Поэтому искать выигрыш в любой из реализаций нет особенного смысла: все реализации пробегают очень быстро. Единственный случай, при котором имеет смысл задуматься об оптимизации — это если такая вот операция вызывается десятки тысяч раз. (Но в этом случае, возможно, имеет смысл пересмотреть дизайн программы.)
Если кому интересно, вот код сравнения (длинный и скучный):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace Т
{
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<Benchmarks>();
}
}
public class Benchmarks
{
bool IsDefaultV1<T>(T o)
{
if (o == null) // => ссылочный тип или nullable
return true;
if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
return false;
var type = o.GetType();
if (type.IsClass)
return false;
else // => тип-значение, есть конструктор по умолчанию
return Activator.CreateInstance(type).Equals(o);
}
bool IsDefaultV3<T>(T o)
{
return (o == null) ? true : // считаем что null default
o.GetType().IsValueType && !typeof(T).IsGenericType ?
o.Equals(Activator.CreateInstance(o.GetType())) : // default типа который внутри nullable
o.Equals(default(T)); // настоящий default
}
public bool IsDefaultV5(byte value) => value == 0;
public bool IsDefaultV5(byte? value) => value == null;
public bool IsDefaultV5(sbyte value) => value == 0;
public bool IsDefaultV5(sbyte? value) => value == null;
public bool IsDefaultV5(int value) => value == 0;
public bool IsDefaultV5(int? value) => value == null;
public bool IsDefaultV5(uint value) => value == 0;
public bool IsDefaultV5(uint? value) => value == null;
public bool IsDefaultV5(short value) => value == 0;
public bool IsDefaultV5(short? value) => value == null;
public bool IsDefaultV5(ushort value) => value == 0;
public bool IsDefaultV5(ushort? value) => value == null;
public bool IsDefaultV5(long value) => value == 0;
public bool IsDefaultV5(long? value) => value == null;
public bool IsDefaultV5(ulong value) => value == 0;
public bool IsDefaultV5(ulong? value) => value == null;
public bool IsDefaultV5(float value) => value == 0.0F;
public bool IsDefaultV5(float? value) => value == null;
public bool IsDefaultV5(double value) => value == 0.0D;
public bool IsDefaultV5(double? value) => value == null;
public bool IsDefaultV5(char value) => value == '\0';
public bool IsDefaultV5(char? value) => value == null;
public bool IsDefaultV5(bool value) => !value;
public bool IsDefaultV5(bool? value) => value == null;
public bool IsDefaultV5(string value) => value == null;
public bool IsDefaultV5(decimal value) => value == 0.0M;
public bool IsDefaultV5(decimal? value) => value == null;
public bool IsDefaultV5<T>(T value)
{
if (value == null) // => ссылочный тип или nullable
return true;
if (Nullable.GetUnderlyingType(typeof(T)) != null) // nullable, не null
return false;
var type = value.GetType();
//для .net core type.GetTypeInfo().IsClass
if (type.IsClass)
return false;
else // => тип-значение, есть конструктор по умолчанию
return Activator.CreateInstance(type).Equals(value);
}
class RequireStruct<T> where T : struct { }
class RequireClass<T> where T : class { }
static bool IsDefaultV6<T>(T o, RequireClass<T> ignore = null) where T : class
{
if (o == null)
return true;
if (!(o is ValueType))
return false;
return Activator.CreateInstance(o.GetType()).Equals(o);
}
static bool IsDefaultV6<T>(T? o) where T : struct =>
o == null;
static bool IsDefaultV6<T>(T o, RequireStruct<T> ignore = null) where T : struct =>
default(T).Equals(o);
static bool IsDefaultV7(byte value) => value == 0;
static bool IsDefaultV7(byte? value) => value == null;
static bool IsDefaultV7(sbyte value) => value == 0;
static bool IsDefaultV7(sbyte? value) => value == null;
static bool IsDefaultV7(int value) => value == 0;
static bool IsDefaultV7(int? value) => value == null;
static bool IsDefaultV7(uint value) => value == 0;
static bool IsDefaultV7(uint? value) => value == null;
static bool IsDefaultV7(short value) => value == 0;
static bool IsDefaultV7(short? value) => value == null;
static bool IsDefaultV7(ushort value) => value == 0;
static bool IsDefaultV7(ushort? value) => value == null;
static bool IsDefaultV7(long value) => value == 0;
static bool IsDefaultV7(long? value) => value == null;
static bool IsDefaultV7(ulong value) => value == 0;
static bool IsDefaultV7(ulong? value) => value == null;
static bool IsDefaultV7(float value) => value == 0.0F;
static bool IsDefaultV7(float? value) => value == null;
static bool IsDefaultV7(double value) => value == 0.0D;
static bool IsDefaultV7(double? value) => value == null;
static bool IsDefaultV7(char value) => value == '\0';
static bool IsDefaultV7(char? value) => value == null;
static bool IsDefaultV7(bool value) => !value;
static bool IsDefaultV7(bool? value) => value == null;
static bool IsDefaultV7(string value) => value == null;
static bool IsDefaultV7(decimal value) => value == 0.0M;
static bool IsDefaultV7(decimal? value) => value == null;
static bool IsDefaultV7<T>(T o, RequireClass<T> ignore = null) where T : class
{
if (o == null)
return true;
if (!(o is ValueType))
return false;
return Activator.CreateInstance(o.GetType()).Equals(o);
}
static bool IsDefaultV7<T>(T? o) where T : struct =>
o == null;
static bool IsDefaultV7<T>(T o, RequireStruct<T> ignore = null) where T : struct =>
default(T).Equals(o);
struct Test<T>
{
int x;
public Test(int x) { this.x = x; }
}
List<object> l1 = default;
List<object> l2 = new List<object>();
string s1 = default;
string s2 = string.Empty;
int i1 = default;
int i2 = 1;
int? ni1 = default;
int? ni2 = 0;
object bi1 = 0;
object bi2 = 1;
Test<int> t1 = default;
Test<int> t2 = new Test<int>(1);
object tb1 = default(Test<int>);
object tb2 = new Test<int>(1);
ValueType tc1 = default(Test<int>);
ValueType tc2 = new Test<int>(1);
[Benchmark]
public int TestV1()
{
return
(IsDefaultV1(l1) ? 1 : 0) +
(!IsDefaultV1(l2) ? 1 : 0) +
(IsDefaultV1(s1) ? 1 : 0) +
(!IsDefaultV1(s2) ? 1 : 0) +
(IsDefaultV1(i1) ? 1 : 0) +
(!IsDefaultV1(i2) ? 1 : 0) +
(IsDefaultV1(ni1) ? 1 : 0) +
(!IsDefaultV1(ni2) ? 1 : 0) +
(IsDefaultV1(bi1) ? 1 : 0) +
(!IsDefaultV1(bi2) ? 1 : 0) +
(IsDefaultV1(t1) ? 1 : 0) +
(!IsDefaultV1(t2) ? 1 : 0) +
(IsDefaultV1(tb1) ? 1 : 0) +
(!IsDefaultV1(tb2) ? 1 : 0) +
(IsDefaultV1(tc1) ? 1 : 0) +
(!IsDefaultV1(tc2) ? 1 : 0);
}
[Benchmark]
public int TestV3()
{
return
(IsDefaultV3(l1) ? 1 : 0) +
(!IsDefaultV3(l2) ? 1 : 0) +
(IsDefaultV3(s1) ? 1 : 0) +
(!IsDefaultV3(s2) ? 1 : 0) +
(IsDefaultV3(i1) ? 1 : 0) +
(!IsDefaultV3(i2) ? 1 : 0) +
(IsDefaultV3(ni1) ? 1 : 0) +
(!IsDefaultV3(ni2) ? 1 : 0) +
(IsDefaultV3(bi1) ? 1 : 0) +
(!IsDefaultV3(bi2) ? 1 : 0) +
(IsDefaultV3(t1) ? 1 : 0) +
(!IsDefaultV3(t2) ? 1 : 0) +
(IsDefaultV3(tb1) ? 1 : 0) +
(!IsDefaultV3(tb2) ? 1 : 0) +
(IsDefaultV3(tc1) ? 1 : 0) +
(!IsDefaultV3(tc2) ? 1 : 0);
}
[Benchmark]
public int TestV5()
{
return
(IsDefaultV5(l1) ? 1 : 0) +
(!IsDefaultV5(l2) ? 1 : 0) +
(IsDefaultV5(s1) ? 1 : 0) +
(!IsDefaultV5(s2) ? 1 : 0) +
(IsDefaultV5(i1) ? 1 : 0) +
(!IsDefaultV5(i2) ? 1 : 0) +
(IsDefaultV5(ni1) ? 1 : 0) +
(!IsDefaultV5(ni2) ? 1 : 0) +
(IsDefaultV5(bi1) ? 1 : 0) +
(!IsDefaultV5(bi2) ? 1 : 0) +
(IsDefaultV5(t1) ? 1 : 0) +
(!IsDefaultV5(t2) ? 1 : 0) +
(IsDefaultV5(tb1) ? 1 : 0) +
(!IsDefaultV5(tb2) ? 1 : 0) +
(IsDefaultV5(tc1) ? 1 : 0) +
(!IsDefaultV5(tc2) ? 1 : 0);
}
[Benchmark]
public int TestV6()
{
return
(IsDefaultV6(l1) ? 1 : 0) +
(!IsDefaultV6(l2) ? 1 : 0) +
(IsDefaultV6(s1) ? 1 : 0) +
(!IsDefaultV6(s2) ? 1 : 0) +
(IsDefaultV6(i1) ? 1 : 0) +
(!IsDefaultV6(i2) ? 1 : 0) +
(IsDefaultV6(ni1) ? 1 : 0) +
(!IsDefaultV6(ni2) ? 1 : 0) +
(IsDefaultV6(bi1) ? 1 : 0) +
(!IsDefaultV6(bi2) ? 1 : 0) +
(IsDefaultV6(t1) ? 1 : 0) +
(!IsDefaultV6(t2) ? 1 : 0) +
(IsDefaultV6(tb1) ? 1 : 0) +
(!IsDefaultV6(tb2) ? 1 : 0) +
(IsDefaultV6(tc1) ? 1 : 0) +
(!IsDefaultV6(tc2) ? 1 : 0);
}
[Benchmark]
public int TestV7()
{
return
(IsDefaultV7(l1) ? 1 : 0) +
(!IsDefaultV7(l2) ? 1 : 0) +
(IsDefaultV7(s1) ? 1 : 0) +
(!IsDefaultV7(s2) ? 1 : 0) +
(IsDefaultV7(i1) ? 1 : 0) +
(!IsDefaultV7(i2) ? 1 : 0) +
(IsDefaultV7(ni1) ? 1 : 0) +
(!IsDefaultV7(ni2) ? 1 : 0) +
(IsDefaultV7(bi1) ? 1 : 0) +
(!IsDefaultV7(bi2) ? 1 : 0) +
(IsDefaultV7(t1) ? 1 : 0) +
(!IsDefaultV7(t2) ? 1 : 0) +
(IsDefaultV7(tb1) ? 1 : 0) +
(!IsDefaultV7(tb2) ? 1 : 0) +
(IsDefaultV7(tc1) ? 1 : 0) +
(!IsDefaultV7(tc2) ? 1 : 0);
}
public void CheckCorrectness()
{
var testMethods =
this.GetType()
.GetMethods()
.Where(m => m.Name.StartsWith("TestV"))
.Select(m => (name: m.Name, func: (Func<int>)m.CreateDelegate(typeof(Func<int>), this)))
.ToList();
foreach ((var name, var func) in testMethods)
{
Console.Write($"Testing {name}... ");
var ok = func() == 16;
Console.WriteLine(ok ? "success" : "FAILED");
}
}
}
}
Продвижение своими сайтами как стратегия роста и независимости