Программный эквивалент по умолчанию (Тип)

514

Я использую отражение, чтобы перебрать Typeсвойства и установить определенные типы по умолчанию. Теперь я мог бы переключить тип и установить default(Type)явно, но я бы предпочел сделать это в одну строку. Есть ли программный эквивалент дефолта?

tags2k
источник
Это должно работать: Nullable <T> a = new Nullable <T> (). GetValueOrDefault ();
dancer42

Ответы:

694
  • В случае типа значения используйте Activator.CreateInstance, и он должен работать нормально.
  • При использовании ссылочного типа просто возвращайте ноль
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

В более новой версии .net, такой как стандарт .net, type.IsValueTypeнеобходимо записать какtype.GetTypeInfo().IsValueType

Dror Helper
источник
22
Это вернет упакованный тип значения и, следовательно, не будет точным эквивалентом значения по умолчанию (Тип). Тем не менее, это так близко, как вы собираетесь получить без генериков.
Рассел Гиддингс
8
И что? Если вы найдете тип, у которого default(T) != (T)(object)default(T) && !(default(T) != default(T))есть аргумент, в противном случае не имеет значения, упакован он или нет, поскольку они эквивалентны.
Мигель Анджело
7
Последний фрагмент предиката - избежать мошенничества с перегрузкой операторов ... можно сделать default(T) != default(T)возвращение ложным, и это обман! =)
Мигель Анджело
4
Это мне очень помогло, но я подумал, что должен добавить одну вещь, которая может быть полезна для некоторых людей, ищущих этот вопрос - есть также эквивалентный метод, если вы хотите получить массив заданного типа, и вы можете получить его с помощью Array.CreateInstance(type, length).
Даррел Хоффман
4
Не беспокойтесь о создании экземпляра неизвестного типа значения? Это может иметь побочные эффекты.
igormutti
103

Почему бы не вызвать метод, который возвращает default (T) с отражением? Вы можете использовать GetDefault любого типа с:

    public object GetDefault(Type t)
    {
        return this.GetType().GetMethod("GetDefaultGeneric").MakeGenericMethod(t).Invoke(this, null);
    }

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
источник
7
Это великолепно, потому что это так просто. Хотя это не лучшее решение, но важно помнить о нем, поскольку этот метод может быть полезен во многих подобных ситуациях.
конфигуратор
Если вместо этого вы вызываете универсальный метод «GetDefault» (перегрузка), сделайте это: this.GetType (). GetMethod («GetDefault», new Type [0]). <AS_IS>
Stefan Steiger
2
Имейте в виду, что эта реализация намного медленнее (из-за размышлений), чем принятый ответ. Это все еще жизнеспособно, но вам нужно настроить некоторое кэширование для вызовов GetMethod () / MakeGenericMethod () для повышения производительности.
Дуг
1
Возможно, аргумент типа является пустым. Например, MethodBase.ResultType () метода void вернет объект Type с именем «Void» или с полным именем «System.Void». Поэтому я ставлю охрану: if (t.FullName == "System.Void") возвращает null; Спасибо за решение.
Вало
8
Лучше использовать, nameof(GetDefaultGeneric)если вы можете, вместо"GetDefaultGeneric"
Mugen
87

Вы можете использовать PropertyInfo.SetValue(obj, null). Если вызывается для типа значения, он даст вам значение по умолчанию. Это поведение описано в .NET 4.0 и .NET 4.5 .

JoelFan
источник
7
Для этого конкретного вопроса - зацикливание свойств типа И установка их на «по умолчанию» - это работает блестяще. Я использую его при преобразовании из SqlDataReader в объект с помощью отражения.
Арно Питерс
58

Если вы используете .NET 4.0 или выше и вам нужна программная версия, которая не является кодификацией правил, определенных вне кода , вы можете создавать Expression, компилировать и запускать ее на лету.

Следующий метод расширения будет принимать Typeи получить значение , возвращаемое default(T)через Defaultметод на Expressionклассе:

public static T GetDefaultValue<T>()
{
    // We want an Func<T> which returns the default.
    // Create that expression here.
    Expression<Func<T>> e = Expression.Lambda<Func<T>>(
        // The default value, always get what the *code* tells us.
        Expression.Default(typeof(T))
    );

    // Compile and return the value.
    return e.Compile()();
}

public static object GetDefaultValue(this Type type)
{
    // Validate parameters.
    if (type == null) throw new ArgumentNullException("type");

    // We want an Func<object> which returns the default.
    // Create that expression here.
    Expression<Func<object>> e = Expression.Lambda<Func<object>>(
        // Have to convert to object.
        Expression.Convert(
            // The default value, always get what the *code* tells us.
            Expression.Default(type), typeof(object)
        )
    );

    // Compile and return the value.
    return e.Compile()();
}

Вы также должны кешировать вышеуказанное значение на основе Type, но учтите, что если вы вызываете это для большого числа Typeэкземпляров и не используете его постоянно, объем памяти, используемой кешем, может перевесить преимущества.

casperOne
источник
4
Производительность для типа возврата. IsValueType? Activator.CreateInstance (type): null; ' в 1000 раз быстрее, чем e.Compile () ();
Сайрус
1
@ Cyrus Я вполне уверен, что было бы наоборот, если вы кешируете e.Compile(). Вот и весь смысл выражений.
Nawfal
2
Запустил тест. Очевидно, что результат e.Compile()должен быть кэширован, но при условии, что этот метод примерно в 14 раз быстрее, например long. См. Gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b для оценки и результатов.
Питер ван Гинкель
3
Из интереса, почему кеш, e.Compile()а не e.Compile()()? т.е. может ли тип по умолчанию изменить тип во время выполнения? Если нет (как я полагаю, так), вы можете просто сохранить кеш-результат, а не скомпилированное выражение, что должно еще больше повысить производительность.
JohnLBevan
3
@JohnLBevan - да, и тогда не имеет значения, какую технику вы используете для получения результата - у всех будет чрезвычайно быстрая амортизированная производительность (поиск по словарю).
Даниэль Уорвикер,
38

Почему вы говорите, что дженерики за кадром?

    public static object GetDefault(Type t)
    {
        Func<object> f = GetDefault<object>;
        return f.Method.GetGenericMethodDefinition().MakeGenericMethod(t).Invoke(null, null);
    }

    private static T GetDefault<T>()
    {
        return default(T);
    }
Роб Фонсека-Энсор
источник
Не удается разрешить символ Метод. Использование PCL для Windows.
Cœur
1
Насколько дорого создавать универсальный метод во время выполнения, а затем использовать его несколько тысяч раз подряд?
C. Tewalt
1
Я думал о чем-то вроде этого. Лучшее и самое элегантное решение для меня. Работает даже на Compact Framework 2.0. Если вы беспокоитесь о производительности, вы всегда можете кэшировать общий метод, не так ли?
Барт
Это решение подходит точно! Спасибо!
Лачезар Лалов
25

Это оптимизированное решение Флема:

using System.Collections.Concurrent;

namespace System
{
    public static class TypeExtension
    {
        //a thread-safe way to hold default instances created at run-time
        private static ConcurrentDictionary<Type, object> typeDefaults =
           new ConcurrentDictionary<Type, object>();

        public static object GetDefaultValue(this Type type)
        {
            return type.IsValueType
               ? typeDefaults.GetOrAdd(type, Activator.CreateInstance)
               : null;
        }
    }
}
cuft
источник
2
Краткая версия возвращения:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Марк Уитфельд
3
Как насчет изменяемых структур? Знаете ли вы, что возможно (и законно) изменить поля в штучной структуре, чтобы изменить данные?
IllidanS4 хочет вернуть Монику
@ IllidanS4, как следует из названия метода, это только для значений по умолчанию ValueType.
aderesh
8

Выбранный ответ является хорошим ответом, но будьте осторожны с возвращенным объектом.

string test = null;
string test2 = "";
if (test is string)
     Console.WriteLine("This will never be hit.");
if (test2 is string)
     Console.WriteLine("Always hit.");

Экстраполяция ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
BSick7
источник
14
истина, но это верно и для default (string), как и для любого другого ссылочного типа ...
TDaver
Строка - нечетная птица - тип значения, который также может возвращать ноль. Если вы хотите, чтобы код возвращал string.empty, просто добавьте для него специальный случай
Dror Helper
15
@Dror - строка является неизменным ссылочным типом, а не типом значения.
СМЛ
@kronoz Вы правы - я имел в виду, что строку можно обработать, возвращая string.empty или null в соответствии с необходимостью.
Помощник Дрора
5

Выражения могут помочь здесь:

    private static Dictionary<Type, Delegate> lambdasMap = new Dictionary<Type, Delegate>();

    private object GetTypedNull(Type type)
    {
        Delegate func;
        if (!lambdasMap.TryGetValue(type, out func))
        {
            var body = Expression.Default(type);
            var lambda = Expression.Lambda(body);
            func = lambda.Compile();
            lambdasMap[type] = func;
        }
        return func.DynamicInvoke();
    }

Я не тестировал этот фрагмент, но я думаю, что он должен выдавать «типизированные» нули для ссылочных типов.

Константин Исаев
источник
1
"typed" nullsобъясните. Какой объект вы возвращаете? Если вы возвращаете объект типа type, но его значение равно null, тогда у него нет - не может - никакой другой информации, кроме той, которая есть null. Вы не можете запросить nullзначение и выяснить, какой тип он предположительно. Если вы НЕ вернете ноль, но вернетесь ... Я не знаю, что .., тогда это не будет вести себя как null.
ToolmakerSteve
3

Пока не могу найти ничего простого и элегантного, но у меня есть одна идея: если вы знаете тип свойства, которое хотите установить, вы можете написать свой собственный default(T). Есть два случая - Tэто тип значения и Tссылочный тип. Вы можете увидеть это, проверив T.IsValueType. Если Tэто ссылочный тип, вы можете просто установить его null. Если Tэто тип значения, то у него будет конструктор по умолчанию без параметров, который можно вызвать, чтобы получить «пустое» значение.

Vilx-
источник
3

Я делаю ту же задачу, как это.

//in MessageHeader 
   private void SetValuesDefault()
   {
        MessageHeader header = this;             
        Framework.ObjectPropertyHelper.SetPropertiesToDefault<MessageHeader>(this);
   }

//in ObjectPropertyHelper
   public static void SetPropertiesToDefault<T>(T obj) 
   {
            Type objectType = typeof(T);

            System.Reflection.PropertyInfo [] props = objectType.GetProperties();

            foreach (System.Reflection.PropertyInfo property in props)
            {
                if (property.CanWrite)
                {
                    string propertyName = property.Name;
                    Type propertyType = property.PropertyType;

                    object value = TypeHelper.DefaultForType(propertyType);
                    property.SetValue(obj, value, null);
                }
            }
    }

//in TypeHelper
    public static object DefaultForType(Type targetType)
    {
        return targetType.IsValueType ? Activator.CreateInstance(targetType) : null;
    }
kpollock
источник
2

Эквивалентно ответу Дрора, но как метод расширения:

namespace System
{
    public static class TypeExtensions
    {
        public static object Default(this Type type)
        {
            object output = null;

            if (type.IsValueType)
            {
                output = Activator.CreateInstance(type);
            }

            return output;
        }
    }
}
Пол Флеминг
источник
2

Небольшие изменения в решении @Rob Fonseca-Ensor : следующий метод расширения также работает на .Net Standard, так как я использую GetRuntimeMethod вместо GetMethod.

public static class TypeExtensions
{
    public static object GetDefault(this Type t)
    {
        var defaultValue = typeof(TypeExtensions)
            .GetRuntimeMethod(nameof(GetDefaultGeneric), new Type[] { })
            .MakeGenericMethod(t).Invoke(null, null);
        return defaultValue;
    }

    public static T GetDefaultGeneric<T>()
    {
        return default(T);
    }
}

... и соответствующий юнит-тест для тех, кто заботится о качестве:

[Fact]
public void GetDefaultTest()
{
    // Arrange
    var type = typeof(DateTime);

    // Act
    var defaultValue = type.GetDefault();

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
источник
0
 /// <summary>
    /// returns the default value of a specified type
    /// </summary>
    /// <param name="type"></param>
    public static object GetDefault(this Type type)
    {
        return type.IsValueType ? (!type.IsGenericType ? Activator.CreateInstance(type) : type.GenericTypeArguments[0].GetDefault() ) : null;
    }
Каз-LA
источник
2
Не работает для Nullable<T>типов: он не возвращает эквивалент, default(Nullable<T>)который должен быть null. Принятый ответ от Дрор работает лучше.
Cœur
можно проверить, можно ли обнулять, используя отражение ...
dancer42
0

Это должно работать: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

dancer42
источник