2010-03-22 4 views
131

Для любого заданного типа я хочу узнать его значение по умолчанию.Значение по умолчанию для типа в Runtime

В C# есть ключевое слово называется по умолчанию для этого, как

object obj = default(Decimal); 

, но у меня есть экземпляр типа (называемого MyType), и если я говорю об этом,

object obj = default(myType); 

это Безразлично 't work

Есть ли хороший способ сделать это? Я знаю, что огромный коммутационный блок будет работать, но это не очень хороший выбор.

+0

Можете ли вы объяснить, почему это не работает? Возникли ошибки? Он просто не возвращает то, что вы ожидаете? – Gabe

+0

@Josh, Спасибо! вам понравилось. – viky

+1

@gabe, он работает с именем типа, но не с экземпляром типа этого типа, я имею в виду, что значение по умолчанию (десятичное) работает, но значение по умолчанию (typeof (Decimal)) не равно – viky

ответ

214

Там действительно только две возможности: null для ссылочных типов и new myType() для типов значений (что соответствует 0 для int, float и т. Д.) Таким образом, вам нужно всего лишь учитывать два случая:

object GetDefaultValue(Type t) 
{ 
    if (t.IsValueType) 
     return Activator.CreateInstance(t); 

    return null; 
} 

(Поскольку типы значений всегда имеют конструктор по умолчанию, этот вызов Activator.CreateInstance никогда не завершится).

+12

Я отправил одно и то же решение, но я не был уверен в том, как были обработаны ошибки. Оказывается, nullables попадают в первую ветвь, но Activator.CreateInstance (typeof (int?)) Фактически возвращает null, так что все работает. – Josh

+0

@Josh: Интересно ... Я быстро проверил свое решение и «int?» Вышел с ожидаемым «нулем», но на самом деле я не проверял, является ли typeof (int?). IsValueType' возвращает true или ложный. –

+0

Да. Это приведет вас к волосатому бизнесу по повторному внедрению оператора по умолчанию (C#) компилятора C# во время выполнения. Это довольно просто сделать, но если правила дефолта должны быть расширены, чтобы учесть новый сценарий, вам необходимо обновить свой код. – 2010-03-22 06:11:08

9

Как о чем-то вроде ...

class Program 
{ 
    static void Main(string[] args) 
    { 
    PrintDefault(typeof(object)); 
    PrintDefault(typeof(string)); 
    PrintDefault(typeof(int)); 
    PrintDefault(typeof(int?)); 
    } 

    private static void PrintDefault(Type type) 
    { 
    Console.WriteLine("default({0}) = {1}", type, 
     DefaultGenerator.GetDefaultValue(type)); 
    } 
} 

public class DefaultGenerator 
{ 
    public static object GetDefaultValue(Type parameter) 
    { 
    var defaultGeneratorType = 
     typeof(DefaultGenerator<>).MakeGenericType(parameter); 

    return defaultGeneratorType.InvokeMember(
     "GetDefault", 
     BindingFlags.Static | 
     BindingFlags.Public | 
     BindingFlags.InvokeMethod, 
     null, null, new object[0]); 
    } 
} 

public class DefaultGenerator<T> 
{ 
    public static T GetDefault() 
    { 
    return default(T); 
    } 
} 

Он производит следующий вывод:

default(System.Object) = 
default(System.String) = 
default(System.Int32) = 0 
default(System.Nullable`1[System.Int32]) = 
+0

Довольно сложно. См. Решение codeka для более сжатого метода. – Josh

+0

+1 Это хороший пример, но на самом деле мне это не нужно. – viky

+0

Я думаю, это зависит от вашего определения сложного. Если двадцать четыре строки кода с общим количеством двух классов и трех инструкций «сложны», то, я думаю, вы правы ... Пример Codeka также имеет три инструкции, поэтому я могу только предположить, что это «лишний» код, класс, то? – 2010-03-22 07:18:42

2

Что вы подразумеваете под "значением по умолчанию"? Все ссылочные типы («класс») имеют значение null в качестве значения по умолчанию, тогда как все типы значений будут иметь свои значения по умолчанию в соответствии с this table.

+5

Существует заметное исключение из правил, о которых вы упомянули. То есть, nullUE ValueType всегда имеет значение по умолчанию null, а не значение по умолчанию для его базового ValueType. Нонизируемый ValueType, тем не менее, является ValueType. Также помните, что хотя определение общего класса (type.ContainsGenericParameters == true) технически считается ссылочным типом, оно не имеет значения по умолчанию, поскольку оно не может быть напрямую инстанцировано. Дополнительную информацию и примеры см. В моем решении по адресу http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481. –

+1

... Больше похоже на тип Nullable - это общий тип ValueType, а его значением по умолчанию является экземпляр Nullable с его значением Value, установленным в значение по умолчанию типа T, и его свойство HasValue установлено в false. Моя точка зрения заключается в том, что нулевой тип _is_ никогда не является нулевым, это просто магия компилятора, чтобы сделать nullables более простым в использовании при написании кода. Что вы пишете: 'object a = myNullable;' то, что видит компилятор: 'object a = myNullable.HasValue? myNullable.Value: null; '. Таким образом, технически дефолт Nullables _does_ имеет значение по умолчанию своего «базового» (общего) типа, но оно не отображается. – AnorZaken

1

Вот функция, которая возвращает значение по умолчанию для обнуляемого типа (другими словами, она возвращает 0 для обоих Decimal и Decimal?):

public static object DefaultValue(Type maybeNullable) 
{ 
    Type underlying = Nullable.GetUnderlyingType(maybeNullable); 
    if (underlying != null) 
     return Activator.CreateInstance(underlying); 
    return Activator.CreateInstance(maybeNullable); 
} 
+2

Вы не хотите возвращать значение ValueType по умолчанию для типа NULL, так как это неправильное значение по умолчанию. Правильное значение по умолчанию для nullable Type равно null. Итак, в отношении вашего примера, Decimal должен иметь значение по умолчанию 0, но Decimal? должен иметь значение по умолчанию null. Подробное описание см. В http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481, который корректно работает и для всех типов с нулевым значением. –

24

Вы также можете добавить его в качестве метода расширения к системе .Type:

public static class TypeExtensions 
{ 
    public static object GetDefaultValue(this Type t) 
    { 
     if (t.IsValueType && Nullable.GetUnderlyingType(t) == null) 
      return Activator.CreateInstance(t); 
     else 
      return null; 
    } 
} 
+6

Это «еще» сводит меня с ума :) – sam

+0

return (t.IsValueType && Nullable.GetUnderlyingType (t) == null)? Activator.CreateInstance (t): null; если вам это не нравится, там! – coalvilledave

+0

Это не о том, чтобы иметь только один «возврат», это сделает код грязным в этом случае. На самом деле просто удалите слово «else», поскольку если вы вернетесь. If (....) return Activator.CreateInstance (t); return null; – sam

15

Решив эту проблему в своих собственных системах, здесь это метод для правильного определения значения по умолчанию для любого типа во время выполнения, которая была протестирована с тысячами типов:

/// <summary> 
    /// [ <c>public static object GetDefault(this Type type)</c> ] 
    /// <para></para> 
    /// Retrieves the default value for a given Type 
    /// </summary> 
    /// <param name="type">The Type for which to get the default value</param> 
    /// <returns>The default value for <paramref name="type"/></returns> 
    /// <remarks> 
    /// If a null Type, a reference Type, or a System.Void Type is supplied, this method always returns null. If a value type 
    /// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an 
    /// exception. 
    /// </remarks> 
    /// <example> 
    /// To use this method in its native, non-extension form, make a call like: 
    /// <code> 
    ///  object Default = DefaultValue.GetDefault(someType); 
    /// </code> 
    /// To use this method in its Type-extension form, make a call like: 
    /// <code> 
    ///  object Default = someType.GetDefault(); 
    /// </code> 
    /// </example> 
    /// <seealso cref="GetDefault&lt;T&gt;"/> 
    public static object GetDefault(this Type type) 
    { 
     // If no Type was supplied, if the Type was a reference type, or if the Type was a System.Void, return null 
     if (type == null || !type.IsValueType || type == typeof(void)) 
      return null; 

     // If the supplied Type has generic parameters, its default value cannot be determined 
     if (type.ContainsGenericParameters) 
      throw new ArgumentException(
       "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type + 
       "> contains generic parameters, so the default value cannot be retrieved"); 

     // If the Type is a primitive type, or if it is another publicly-visible value type (i.e. struct/enum), return a 
     // default instance of the value type 
     if (type.IsPrimitive || !type.IsNotPublic) 
     { 
      try 
      { 
       return Activator.CreateInstance(type); 
      } 
      catch (Exception e) 
      { 
       throw new ArgumentException(
        "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe Activator.CreateInstance method could not " + 
        "create a default instance of the supplied value type <" + type + 
        "> (Inner Exception message: \"" + e.Message + "\")", e); 
      } 
     } 

     // Fail with exception 
     throw new ArgumentException("{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type + 
      "> is not a publicly-visible type, so the default value cannot be retrieved"); 
    } 

В этих примерах метод GetDefault реализован в статическом классе DefaultValue. Вызовите этот метод с заявлением, как:

 object Default = DefaultValue.GetDefault(someType); 

Чтобы использовать метод GetDefault как метод расширения для типа, назовем его так:

 object Default = someType.GetDefault(); 

Это второй, типа расширения подход является более простой клиент синтаксис кода, так как он устраняет необходимость ссылки на содержащийся в классе классификатор класса DefaultValue.

Вышеупомянутая форма времени выполнения GetDefault работает с идентичной семантикой как примитивное ключевое слово C# по умолчанию и дает те же результаты.

Чтобы использовать общую форму GetDefault, вы можете получить доступ к следующей функции:

/// <summary> 
    /// [ <c>public static T GetDefault&lt; T &gt;()</c> ] 
    /// <para></para> 
    /// Retrieves the default value for a given Type 
    /// </summary> 
    /// <typeparam name="T">The Type for which to get the default value</typeparam> 
    /// <returns>The default value for Type T</returns> 
    /// <remarks> 
    /// If a reference Type or a System.Void Type is supplied, this method always returns null. If a value type 
    /// is supplied which is not publicly visible or which contains generic parameters, this method will fail with an 
    /// exception. 
    /// </remarks> 
    /// <seealso cref="GetDefault(Type)"/> 
    public static T GetDefault<T>() 
    { 
     return (T) GetDefault(typeof(T)); 
    } 

Звонки в общей форму может быть что-то вроде:

 int? inDefaultVal = DefaultValue.GetDefault<int?>(); 

Конечно, выше общего форма GetDefault не нужна для C#, так как она работает так же, как и по умолчанию (T). Это полезно только для языка .NET, который не поддерживает ключевое слово default, но поддерживает общие типы. В большинстве случаев общая форма не нужна.

Полезным методом следствия является определение того, содержит ли объект значение по умолчанию для его Тип. Я также полагаться на следующий IsObjectSetToDefault метод для этой цели:

/// <summary> 
    /// [ <c>public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue)</c> ] 
    /// <para></para> 
    /// Reports whether a value of type T (or a null reference of type T) contains the default value for that Type 
    /// </summary> 
    /// <remarks> 
    /// Reports whether the object is empty or unitialized for a reference type or nullable value type (i.e. is null) or 
    /// whether the object contains a default value for a non-nullable value type (i.e. int = 0, bool = false, etc.) 
    /// <para></para> 
    /// NOTE: For non-nullable value types, this method introduces a boxing/unboxing performance penalty. 
    /// </remarks> 
    /// <param name="ObjectType">Type of the object to test</param> 
    /// <param name="ObjectValue">The object value to test, or null for a reference Type or nullable value Type</param> 
    /// <returns> 
    /// true = The object contains the default value for its Type. 
    /// <para></para> 
    /// false = The object has been changed from its default value. 
    /// </returns> 
    public static bool IsObjectSetToDefault(this Type ObjectType, object ObjectValue) 
    { 
     // If no ObjectType was supplied, attempt to determine from ObjectValue 
     if (ObjectType == null) 
     { 
      // If no ObjectValue was supplied, abort 
      if (ObjectValue == null) 
      { 
       MethodBase currmethod = MethodInfo.GetCurrentMethod(); 
       string ExceptionMsgPrefix = currmethod.DeclaringType + " {" + currmethod + "} Error:\n\n"; 
       throw new ArgumentNullException(ExceptionMsgPrefix + "Cannot determine the ObjectType from a null Value"); 
      } 

      // Determine ObjectType from ObjectValue 
      ObjectType = ObjectValue.GetType(); 
     } 

     // Get the default value of type ObjectType 
     object Default = ObjectType.GetDefault(); 

     // If a non-null ObjectValue was supplied, compare Value with its default value and return the result 
     if (ObjectValue != null) 
      return ObjectValue.Equals(Default); 

     // Since a null ObjectValue was supplied, report whether its default value is null 
     return Default == null; 
    } 

выше IsObjectSetToDefault метод может либо назвать в своей нативной форме или доступ в качестве расширения типа класса.

Смежные вопросы