2010-03-22 18 views
131

Per qualsiasi tipo di dati, desidero conoscere il suo valore predefinito.Valore predefinito di un tipo in Runtime

In C#, c'è una parola chiave chiamato default per fare questo come

object obj = default(Decimal); 

ma ho un'istanza di tipo (chiamato myType) e se lo dico,

object obj = default(myType); 

doesn 't work

C'è qualche buon modo per farlo? So che un enorme switch switch funzionerà ma non è una buona scelta.

+0

Puoi spiegare perché non funziona? Ricevi un errore? Non restituisce semplicemente ciò che ti aspetti? – Gabe

+0

@Josh, grazie! ti è piaciuto. – viky

+1

@gabe, Funziona con il nome del tipo, ma non con l'istanza Type del nome di quel tipo, voglio dire default (Decimal) funziona ma default (typeof (Decimal)) non è – viky

risposta

214

Non c'è davvero solo due possibilità: null per i tipi di riferimento e new myType() per i tipi di valore (che corrisponde a 0 per int, float, ecc. Quindi devi solo tenere conto di due casi:

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

    return null; 
} 

(Poiché i tipi di valore hanno sempre un costruttore predefinito, quella chiamata a Activator.CreateInstance non fallirà mai).

+12

Ho pubblicato la stessa soluzione, ma non ero sicuro di come venivano considerati i valori nulli. Si scopre che il nullable ricade nel primo ramo ma Activator.CreateInstance (typeof (int?)) Restituisce effettivamente null in modo che tutto funzioni. – Josh

+0

@Josh: Interessante ... Ho fatto un rapido test della mia soluzione e 'int?' È uscito con l'attesa 'null', ma in realtà non ho verificato se' typeof (int?). IsValueType 'ha restituito true o falsa. –

+0

Sì. Questo ti porta nella faticosa attività di reimplementare l'operatore default() del compilatore C# in fase di esecuzione. È piuttosto semplice da fare ma, se le regole per l'inadempienza dovessero essere estese per tenere conto di un nuovo scenario, devi aggiornare il tuo codice. –

9

ne dite qualcosa di simile ...

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

Si produce il seguente output:

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

Abbastanza complicato. Vedi la soluzione di codeka per un metodo molto più conciso. – Josh

+0

+1 è un bell'esempio, ma in realtà non ne ho più bisogno. – viky

+0

Suppongo che dipenda dalla tua definizione di complicato. Se ventiquattro linee di codice con un totale complessivo di due classi e tre istruzioni sono "complicate", allora penso che tu abbia ragione ... L'esempio di Codeka ha anche tre istruzioni, quindi posso solo supporre che sia "extra" classe, allora? –

2

Che cosa intendete per "Valore predefinito"? Tutti i tipi di riferimento ("classe") hanno valore null come valore predefinito, mentre tutti i tipi di valore avranno i loro valori predefiniti in base a this table.

+5

C'è una notevole eccezione alle regole che hai menzionato. Vale a dire, un ValueType nullable ha sempre un valore predefinito di null, non un valore predefinito del relativo ValueType sottostante. Un ValueType nullable è comunque ancora ValueType. Ricorda anche che sebbene una definizione di classe generica (type.ContainsGenericParameters == true) sia tecnicamente considerata un Tipo di riferimento, non ha valore predefinito, poiché non può essere istanziata direttamente. Per ulteriori dettagli ed esempi, consultare la mia soluzione all'indirizzo http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481. –

+1

... Più simile al tipo Nullable è un valore generico ValueType e il suo valore predefinito è un'istanza di Nullable con la proprietà Value impostata sul valore predefinito di tipo T e la relativa proprietà HasValue impostata su false. Il mio punto è che un tipo nullable _is_ mai nullo, che è solo un compiler magic per rendere nullable più facile da usare quando si scrive codice. Cosa scrivi: 'object a = myNullable;' cosa vede il compilatore: 'object a = myNullable.HasValue? myNullable.Value: null; '. Quindi tecnicamente il valore predefinito di Nullables _does_ ha un valore predefinito del suo tipo "sottostante" (generico), ma non è esposto. – AnorZaken

1

Ecco una funzione che restituirà il valore di default per un tipo nullable (in altre parole, restituisce 0 per entrambi Decimal e Decimal?):

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

Non si desidera restituire il valore ValueType predefinito per un tipo nullable, poiché questo non è il valore predefinito corretto. Il valore predefinito corretto per un tipo nullable è null. Quindi, per quanto riguarda il tuo esempio, Decimal dovrebbe avere un valore predefinito di 0, ma Decimale? dovrebbe avere un valore predefinito null. Vedi la mia soluzione in dettaglio all'indirizzo http://stackoverflow.com/questions/2490244/default-value-of-a-type/7881481#7881481, che funziona correttamente anche per tutti i tipi nullable. –

24

Si potrebbe anche aggiungere come un metodo di estensione del sistema .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

Questo 'altro' mi fa impazzire :) – sam

+0

return (t.IsValueType && Nullable.GetUnderlyingType (t) == null)? Activator.CreateInstance (t): null; se non ti piace ti ritrovi lì! – coalvilledave

+0

Non si tratta di avere un solo 'ritorno', renderebbe il codice disordinato in questo caso. In realtà rimuovi solo la parola "else" poiché nel caso tu ritorni .. if (....) return Activator.CreateInstance (t); return null; – sam

15

Dopo aver risolto questo problema nei miei sistemi, ecco un metodo per determinare correttamente il valore di default di un tipo arbitrario in fase di esecuzione, che è stato testato contro le migliaia di tipi:

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

In questi esempi, il metodo GetDefault è implementato nella classe statica DefaultValue. Chiamare questo metodo con una dichiarazione come:

 object Default = DefaultValue.GetDefault(someType); 

Per utilizzare il metodo getDefault come un metodo di estensione per il tipo, lo chiamano così:

 object Default = someType.GetDefault(); 

Questo secondo approccio di tipo estensione è un client semplice -codice di codice, poiché rimuove la necessità di fare riferimento al qualificatore di classe DefaultValue che contiene la chiamata.

La precedente forma di esecuzione di GetDefault funziona con la semantica identica come la parola chiave di default C# "predefinita" e produce gli stessi risultati.

Per utilizzare una forma generica di getDefault, è possibile accedere alle seguenti funzioni:

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

Una chiamata alla forma generica potrebbe essere qualcosa di simile:

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

Naturalmente, quanto sopra generica la forma di GetDefault non è necessaria per C#, poiché funziona allo stesso modo di default (T). È utile solo per un linguaggio .NET che non supporta la parola chiave 'default' ma che supporta tipi generici. Nella maggior parte dei casi, la forma generica non è necessaria.

Un metodo corollario utile è uno per determinare se un oggetto contiene il valore predefinito per il suo Tipo. Ho anche contare sulla seguente metodo IsObjectSetToDefault a tal fine:

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

Il metodo sopra IsObjectSetToDefault può o essere richiamato nella sua forma nativa o accessibile come un'estensione di tipo classe.

Problemi correlati