2010-03-22 23 views
131

Dla każdego określonego typu chcę znać jego domyślną wartość.Wartość domyślna typu w Runtime

W języku C#, jest kluczowe nazywane domyślną robi to jak

object obj = default(Decimal); 

ale mam instancję typu (zwanego myType), a jeśli to powiem,

object obj = default(myType); 

nie robi 't pracy

Czy jest jakiś dobry sposób to zrobić? Wiem, że wielki blok przełączników będzie działał, ale to nie jest dobry wybór.

+0

Czy możesz wyjaśnić, dlaczego to nie działa? Czy pojawia się błąd? Czy po prostu nie zwraca tego, czego oczekujesz? – Gabe

+0

@Josh, Dzięki! lubisz to. – viky

+1

@gabe, działa z nazwą typu, ale nie z instancją Typ tego typu nazwy, mam na myśli domyślny (dziesiętny) działa, ale domyślny (typeof (dziesiętny)) nie jest – viky

Odpowiedz

214

tam naprawdę tylko dwie możliwości: null dla typów referencyjnych i new myType() dla typów wartości (co odpowiada 0 dla int, float, itp.) Tak naprawdę musisz tylko uwzględnić dwa przypadki:

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

    return null; 
} 

(Ponieważ typy wartości zawsze mają domyślny konstruktor, wywołanie funkcji Activator.CreateInstance nigdy się nie powiedzie).

+12

Umieściłem to samo rozwiązanie, ale nie byłem pewien, jak traktowano kwasy nullables. Okazuje się, że nullable należą do pierwszej gałęzi, ale Activator.CreateInstance (typeof (int?)) Zwraca wartość null, więc wszystko działa. – Josh

+0

@Josh: Interesujące ... Zrobiłem szybki test mojego rozwiązania i 'int?' Wyszedł z oczekiwanym 'null', ale tak naprawdę nie przetestowałem, czy' typeof (int?). IsValueType' zwrócił true lub fałszywy. –

+0

Tak. To przenosi cię do owłosionej firmy polegającej na ponownym wdrożeniu domyślnego() kompilatora kompilatora C# w czasie wykonywania. Jest to dość proste, ale jeśli reguły dotyczące domyślnie miały zostać rozszerzone, aby uwzględnić nowy scenariusz, musisz zaktualizować swój kod. –

9

Jak o coś takiego ...

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

Produkuje następujący wynik:

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

Bardzo skomplikowany. Zobacz rozwiązanie codeka dla znacznie bardziej zwięzłej metody. – Josh

+0

+1 to dobry przykład, ale tak naprawdę nie potrzebuję tak dużo. – viky

+0

Myślę, że to zależy od twojej definicji skomplikowanej. Jeśli dwadzieścia cztery linie kodu z łączną liczbą dwóch klas i trzema instrukcjami są "skomplikowane", to myślę, że masz rację ... Przykład Codeki ma również trzy instrukcje, więc mogę tylko założyć, że jest to "dodatkowe" klasa, a następnie? –

2

Co rozumiesz przez "wartość domyślną"? Wszystkie typy odniesienia ("klasa") mają zerową wartość domyślną, podczas gdy wszystkie typy wartości będą miały domyślne wartości zgodnie z this table.

+5

Istnieje wyjątek od wspomnianych zasad. Oznacza to, że wartość zerowa ValueType ma zawsze wartość domyślną równą null, a nie wartość domyślną jej podstawowego ValueType. Nullowalny ValueType wciąż jest jednak ValueType. Pamiętaj również, że chociaż ogólna definicja klasy (type.ContainsGenericParameters == true) jest technicznie uważana za typ referencyjny, nie ma wartości domyślnej, ponieważ nie można jej bezpośrednio utworzyć. Proszę zapoznać się z moim rozwiązaniem pod adresem http://stackoverflow.com/questions/2490244/default-value-of-a-pepe/7881481#7881481 w celu uzyskania dalszych szczegółów i przykładów. –

+1

... Więcej niż Nullable jest typowym ValueType, a jego wartością domyślną jest instancja Nullable z właściwością Value ustawioną na wartość domyślną typu T i jego właściwość HasValue ustawioną na false. Chodzi mi o to, że zerowy typ _is_ never null, to jest po prostu magia kompilatora, aby nullable były łatwiejsze w użyciu podczas pisania kodu. Co piszesz: 'object a = myNullable;' co widzi kompilator: 'object a = myNullable.HasValue? myNullable.Value: null; '. Tak więc technicznie domyślny Nullables _does_ ma domyślną wartość typu "basis" (rodzajowy) - ale nie jest odsłonięty. – AnorZaken

1

Oto funkcja, która zwróci wartość domyślną dla typu wartości pustych (innymi słowy, to zwraca 0 dla obu Decimal i Decimal?):

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

Nie chcesz zwracać wartości ValueType dla typu zerowego, ponieważ nie jest to poprawna wartość domyślna. Poprawną wartością domyślną dla typu nullable jest null. Jeśli chodzi o Twój przykład, Decimal powinien mieć wartość domyślną 0, ale dziesiętną? powinien mieć wartość domyślną null. Zobacz moje rozwiązanie w szczegółach na http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481, który działa poprawnie również dla wszystkich typów zerowalnych. –

24

Można także dodać ją jako metodę rozszerzenia do Systemu .typ:

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

To "jeszcze" doprowadza mnie do szału :) – sam

+0

powrót (t.IsValueType && Nullable.GetUnderlyingType (t) == null)? Activator.CreateInstance (t): null; jeśli nie podoba ci się to, że tam jest! – coalvilledave

+0

Nie chodzi o to, by mieć tylko jeden "powrót", który sprawiłby, że kod byłby brudny w tym przypadku. Naprawdę po prostu usuń słowo "else", ponieważ jeśli zwrócisz ... jeśli (....) zwróci Activator.CreateInstance (t); return null; – sam

15

po rozwiązać ten problem w swoich własnych systemów, tutaj jest sposób prawidłowo określania wartości domyślnej dowolnego typu w czasie wykonywania, który został przetestowany przed tysiącami typów:

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

W tych przykładach metoda GetDefault jest zaimplementowana w klasie statycznej DefaultValue. Wywołanie tej metody z oświadczeniem, takich jak:

 object Default = DefaultValue.GetDefault(someType); 

Aby użyć metody getDefault jako metodę rozszerzenia za typ, nazwać tak:

 object Default = someType.GetDefault(); 

To drugie podejście typu rozszerzenie jest prostszy klient -code, ponieważ eliminuje potrzebę odwoływania się do kwalifikatora klasy DefaultValue w wywołaniu.

Powyższa metoda wykonywania GetDefault działa z identyczną semantyką jak pierwotne słowo kluczowe "#" i daje takie same wyniki.

Aby użyć rodzajowe formę getDefault, można uzyskać dostęp do następujących funkcji:

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

Wezwanie do postaci generycznej może być coś takiego:

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

Oczywiście powyższe generic forma GetDefault jest niepotrzebna dla języka C#, ponieważ działa tak samo jak domyślna (T). Jest to użyteczne tylko w przypadku języka .NET, który nie obsługuje słowa kluczowego "default", ale obsługuje typy ogólne. W większości przypadków forma generyczna jest niepotrzebna.

Przydatną metodą uzupełniającą jest metoda określania, czy obiekt zawiera domyślną wartość dla jego typu. Ja również polegać na następującej metody IsObjectSetToDefault do tego celu:

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

Powyższy IsObjectSetToDefault metoda może być wywołana zarówno w postaci natywnej lub dostępne jako rozszerzenie typu klasy.