#c_sharp #net #рефлексия
Update: Самым быстрым оказался алгоритм V7 Продолжение предыдущего вопроса. Есть несколько вариантов реализации функции для проверки переменной на значение по умолчанию. Из комментариев к предыдущему вопросу я понял лишь то, что мне еще очень много предстоит выучить в C#. Помогите пожалуйста найти самый производительный и надежный вариант реализации функции. В качестве параметра функции может быть все что угодно, включая класс, структуру, типы на подобие int? и так далее. Некоторые варианты реализации функции: V1: bool IsDefault(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 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 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 where T : struct { } class RequireClass where T : class { } static bool IsDefault (T o, RequireClass 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? o) where T : struct => o == null; static bool IsDefault (T o, RequireStruct 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 o, RequireClass 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? o) where T : struct => o == null; bool IsDefault (T o, RequireStruct ignore = null) where T : struct => default(T).Equals(o); ---Реализации, которые не проходят тест--- V2: public object GetDefaultValue(Type target) { Expression > e = Expression.Lambda >( 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 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 o) { return (o==null)?(default(T)==null):o.Equals(default(T)); }
Ответы
Ответ 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(); } } public class Benchmarks { bool IsDefaultV1 (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 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 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 where T : struct { } class RequireClass where T : class { } static bool IsDefaultV6 (T o, RequireClass 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? o) where T : struct => o == null; static bool IsDefaultV6 (T o, RequireStruct 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 o, RequireClass 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? o) where T : struct => o == null; static bool IsDefaultV7 (T o, RequireStruct ignore = null) where T : struct => default(T).Equals(o); struct Test { int x; public Test(int x) { this.x = x; } } List
Комментариев нет:
Отправить комментарий