Програмний еквівалент за замовчуванням (Тип)


514

Я використовую відображення для перегляду Typeвластивостей 's та встановлення певних типів за замовчуванням. Тепер я міг би зробити перемикач на тип і встановити default(Type)явно, але краще зробити це в один рядок. Чи є програмний еквівалент за замовчуванням?


Це має працювати: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
танцюрист42

Відповіді:


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

У новій версії .net, такої як .net standard, type.IsValueTypeпотрібно писати якtype.GetTypeInfo().IsValueType


22
Це поверне тип коробкового значення і, отже, не є точним еквівалентом за замовчуванням (Type). Однак це так само близько, як ви збираєтеся дістатись без дженериків.
Рассел Giddings

8
І що? Якщо ви знайдете тип, який у default(T) != (T)(object)default(T) && !(default(T) != default(T))вас є аргументом, інакше не має значення, є він чи ні, оскільки вони є рівнозначними.
Мігель Анжело

7
Останній фрагмент присудка - це уникати обману з перевантаженням оператора ... можна зробити default(T) != default(T)повернення помилковим, і це обман! =)
Мігель Анжело

4
Це мені дуже допомогло, але я подумав, що я повинен додати одне, що може бути корисним людям, які шукають це питання - є також еквівалентний метод, якщо ви хочете масив даного типу, і ви можете отримати його, використовуючи Array.CreateInstance(type, length).
Даррел Гофман

4
Ви не турбуєтесь про створення примірника невідомого типу значення? Це може мати побічні ефекти.
ygormutti

103

Чому б не зателефонувати методу, який повертає типовий (T) з відображенням? Ви можете використовувати GetDefault будь-якого типу з:

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

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

7
Це геніально, тому що це так просто. Незважаючи на те, що це не найкраще рішення, слід пам’ятати про важливе рішення, оскільки ця методика може бути корисною за багатьох подібних обставин.
конфігуратор

Якщо ви називаєте загальний метод "GetDefault" замість (перевантаження), зробіть це: this.GetType (). GetMethod ("GetDefault", новий тип [0]). <AS_IS>
Стефан Штайгер

2
Майте на увазі, це реалізація набагато повільніше (через роздуми), ніж прийнята відповідь. Він все ще є життєздатним, але вам потрібно буде встановити кешування для викликів GetMethod () / MakeGenericMethod () для підвищення продуктивності.
Дуг

1
Можливо, що аргумент типу недійсний. Наприклад, метод void methodBase.ResultType () поверне об'єкт Type з ім'ям "Void" або з FullName "System.Void". Тому я ставлю охорону: якщо (t.FullName == "System.Void") повернути null; Дякую за рішення.
Вало

8
Краще скористайтеся, nameof(GetDefaultGeneric)якщо зможете, замість"GetDefaultGeneric"
Mugen

87

Можна використовувати PropertyInfo.SetValue(obj, null). Якщо буде викликано тип значення, воно надасть вам за замовчуванням. Така поведінка задокументована у .NET 4.0 та .NET 4.5 .


7
Для цього конкретного питання - перегляд властивостей типу та встановлення їх на "за замовчуванням" - це працює чудово. Я використовую це під час перетворення з SqlDataReader в об'єкт, використовуючи відображення.
Арно Петерс

57

Якщо ви використовуєте .NET 4.0 або новішої версії, і вам потрібна програмна версія, яка не є кодифікацією правил, визначених поза кодом , ви можете створитиExpression , компілювати та запустити його на ходу.

Наступний метод розширення прийме a 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примірників, і не використовуєте його постійно, пам'ять, що споживається кешем, може перевершити переваги.


4
Продуктивність для 'return type.IsValueType? Activator.CreateInstance (тип): 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

Це оптимізоване рішення Flem:

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

2
Коротка ручна версія повернення:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Марк Вітфельд

3
А як щодо змінних конструкцій? Чи знаєте ви, що можна (і законно) змінювати поля в структурі в коробці, щоб змінити дані?
IllidanS4 хоче повернути Моніку

@ IllidanS4, оскільки ім'я методу означає, що це лише для значень ValueType за замовчуванням.
адереш

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

14
правда, але це справедливо і за замовчуванням (рядок) також, як і всі інші типи посилань ...
TDaver

рядок - це непарний птах - це тип значення, який також може повернути нуль. Якщо ви хочете, щоб код повернув string.empty, просто додайте до нього спеціальний випадок
Dror Helper

15
@Dror - рядок є незмінним еталонним типом, а не типовим типом.
ljs

@kronoz Ви маєте рацію - я мав на увазі, що рядок можна обробляти, повертаючи string.empty або null відповідно до потреби.
Dror Helper

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це тип значення, то він буде мати конструктор без параметрів за замовчуванням, який ви можете зателефонувати, щоб отримати "порожнє" значення.


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

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

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

2
Не працює для Nullable<T> типів: не повертає еквівалент, default(Nullable<T>)який має бути null. Прийнята відповідь від Dror працює краще.
Cœur

можна перевірити, чи є нульовим за допомогою рефлексії ...
dancer42

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.