Сравнение алгоритмов проверки переменной на значение по умолчанию

332
03 сентября 2017, 00:34

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)); 
}
Answer 1

Для начала, нужно составить юнит-тесты на желаемую функцию. Например, такие:

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));

Пробуем различные имплементации:

  • V1 проходит.
  • V2 бросает ArgumentNullException вместо того, чтобы вернуть false, на Debug.Assert(IsDefault(default(string)));.
  • V3 не проходит Debug.Assert(!IsDefault((int?)0)); проходит.
  • V4 не проходит Debug.Assert(IsDefault((object)0)).
  • V5 проходит.
  • V6 проходит.
  • V7 проходит.

Окей, на текущий момент тесты проходят 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");
            }
        }
    }
}
READ ALSO
Не работает стиль кнопки в ResourceDictionary

Не работает стиль кнопки в ResourceDictionary

Ребят, помогите решить задачкуЕсть ResourceDictionary (содержание см

340
Telegram.bot InlineKeyboardMarkup exception

Telegram.bot InlineKeyboardMarkup exception

Если два раза быстро нажать на кнопку созданую через InlineKeyboardMarkup, то вылетает ExceptionЕсли ожидать ответа, то все работает отлично

595
Работа с текстом,C#

Работа с текстом,C#

Есть примерно следующая строка:

438
WPF: Не меняется фон кнопки, когда меняю IsEnabled = false

WPF: Не меняется фон кнопки, когда меняю IsEnabled = false

Поскольку в WPF так просто менять фон нельзя при установки IsEnabled = false у кнопки, то я сделал свой Стиль и изменил шаблон и указал начальные сеттеры:...

433