Équivalent programmatique par défaut (Type)

514

J'utilise la réflexion pour parcourir Typeles propriétés de a et définir certains types par défaut. Maintenant, je pourrais faire un changement sur le type et définir default(Type)explicitement, mais je préfère le faire sur une seule ligne. Existe-t-il un équivalent programmatique par défaut?

tags2k
la source
Cela devrait fonctionner: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42

Réponses:

694
  • Dans le cas d'un type de valeur, utilisez Activator.CreateInstance et cela devrait fonctionner correctement.
  • Lorsque vous utilisez un type de référence, renvoyez simplement null
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

Dans la version plus récente de .net telle que .net standard, type.IsValueTypedoit être écrite commetype.GetTypeInfo().IsValueType

Dror Helper
la source
22
Cela renverra un type de valeur encadrée et n'est donc pas l'équivalent exact de la valeur par défaut (Type). Cependant, c'est aussi proche que possible sans génériques.
Russell Giddings
8
Et alors? Si vous trouvez un type auquel default(T) != (T)(object)default(T) && !(default(T) != default(T))vous avez un argument, sinon peu importe qu'il soit encadré ou non, car ils sont équivalents.
Miguel Angelo
7
Le dernier élément du prédicat est d'éviter de tricher avec une surcharge d'opérateur ... on pourrait faire un default(T) != default(T)retour faux, et c'est de la triche! =)
Miguel Angelo
4
Cela m'a beaucoup aidé, mais j'ai pensé que je devrais ajouter une chose qui pourrait être utile à certaines personnes qui recherchent cette question - il existe également une méthode équivalente si vous vouliez un tableau du type donné, et vous pouvez l'obtenir en utilisant Array.CreateInstance(type, length).
Darrel Hoffman
4
Ne vous inquiétez-vous pas de créer une instance d'un type de valeur inconnue? Cela peut avoir des effets collatéraux.
ygormutti
103

Pourquoi ne pas appeler la méthode qui renvoie default (T) avec réflexion? Vous pouvez utiliser GetDefault de tout type avec:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
la source
7
C'est génial parce que c'est si simple. Bien que ce ne soit pas la meilleure solution ici, c'est une solution importante à garder à l'esprit car cette technique peut être utile dans de nombreuses circonstances similaires.
configurateur
Si vous appelez la méthode générique "GetDefault" à la place (surcharge), procédez comme suit: this.GetType (). GetMethod ("GetDefault", nouveau Type [0]). <AS_IS>
Stefan Steiger
2
Gardez à l'esprit que cette implémentation est beaucoup plus lente (en raison de la réflexion) que la réponse acceptée. Il est toujours viable, mais vous devez configurer une mise en cache pour les appels GetMethod () / MakeGenericMethod () pour améliorer les performances.
Doug
1
Il est possible que l'argument type soit nul. Par exemple, MethodBase.ResultType () d'une méthode void retournera un objet Type avec le nom "Void" ou avec le nom complet "System.Void". Par conséquent, je mets un garde: if (t.FullName == "System.Void") retourne null; Merci pour la solution.
Valo
8
Mieux vaut utiliser nameof(GetDefaultGeneric)si vous le pouvez, au lieu de"GetDefaultGeneric"
Mugen
87

Vous pouvez utiliser PropertyInfo.SetValue(obj, null). S'il est appelé sur un type de valeur, il vous donnera la valeur par défaut. Ce comportement est documenté dans .NET 4.0 et .NET 4.5 .

JoelFan
la source
7
Pour cette question spécifique - boucler à travers les propriétés d'un type ET les définir sur "par défaut" - cela fonctionne parfaitement. Je l'utilise lors de la conversion d'un SqlDataReader en un objet à l'aide de la réflexion.
Arno Peters
58

Si vous utilisez .NET 4.0 ou supérieur et que vous souhaitez une version programmatique qui n'est pas une codification de règles définies en dehors du code , vous pouvez créer unExpression , le compiler et l'exécuter à la volée.

La méthode d'extension suivante prendra un Typeet obtiendra la valeur renvoyée default(T)par le biais de la Defaultméthode sur la Expressionclasse:

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

Vous devez également mettre en cache la valeur ci-dessus en fonction de la Type, mais sachez que si vous appelez cela pour un grand nombre d' Typeinstances et que vous ne l'utilisez pas constamment, la mémoire consommée par le cache peut l'emporter sur les avantages.

casperOne
la source
4
Performances pour 'return type.IsValueType? Activator.CreateInstance (type): null; ' est 1000 fois plus rapide que e.Compile () ();
Cyrus
1
@Cyrus Je suis presque sûr que ce serait l'inverse si vous mettez le cache en e.Compile(). C'est tout l'intérêt des expressions.
nawfal
2
Ran une référence. Évidemment, le résultat de e.Compile()devrait être mis en cache, mais en supposant que cette méthode soit environ 14 fois plus rapide, par exemple long. Voir gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b pour le benchmark et les résultats.
Pieter van Ginkel
3
Par intérêt, pourquoi mettre en cache e.Compile()plutôt que e.Compile()()? Par exemple, le type par défaut d'un type peut-il changer lors de l'exécution? Sinon (comme je pense que c'est le cas), vous pouvez simplement stocker en cache le résultat plutôt que l'expression compilée, ce qui devrait améliorer encore les performances.
JohnLBevan
3
@JohnLBevan - oui, et peu importe la technique que vous utilisez pour obtenir le résultat - tous auront des performances amorties extrêmement rapides (une recherche dans le dictionnaire).
Daniel Earwicker
38

Pourquoi dites-vous que les génériques sont hors de propos?

    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);
    }
Rob Fonseca-Ensor
la source
Impossible de résoudre la méthode des symboles. Utilisation d'un PCL pour Windows.
Cœur
1
combien coûte la création de la méthode générique au moment de l'exécution, puis son utilisation plusieurs milliers de fois de suite?
C. Tewalt du
1
Je pensais à quelque chose comme ça. La meilleure solution et la plus élégante pour moi. Fonctionne même sur Compact Framework 2.0. Si vous êtes préoccupé par les performances, vous pouvez toujours mettre en cache une méthode générique, n'est-ce pas?
Bart
Cette solution convient parfaitement! Merci!
Lachezar Lalov
25

C'est la solution optimisée de 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;
        }
    }
}
cuft
la source
2
Une version courte du retour:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld
3
Qu'en est-il des structures mutables? Savez-vous qu'il est possible (et légal) de modifier les champs d'une structure encadrée, afin que les données changent?
IllidanS4 veut que Monica revienne le
@ IllidanS4 car le nom de la méthode implique que ce n'est que pour les valeurs par défaut de ValueType.
aderesh
8

La réponse choisie est une bonne réponse, mais soyez prudent avec l'objet retourné.

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

Extrapoler ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
BSick7
la source
14
vrai, mais cela vaut aussi pour la chaîne par défaut, comme tous les autres types de référence ...
TDaver
chaîne est un oiseau impair - étant un type de valeur qui peut également retourner null. Si vous voulez que le code retourne string.empty, ajoutez simplement un cas spécial pour lui
Dror Helper
15
@Dror - la chaîne est un type de référence immuable, pas un type de valeur.
ljs
@kronoz Vous avez raison - je voulais dire que la chaîne peut être gérée en renvoyant string.empty ou null selon les besoins.
Dror Helper
5

Les expressions peuvent aider ici:

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

Je n'ai pas testé cet extrait, mais je pense qu'il devrait produire des valeurs nulles "typées" pour les types de référence ..

Konstantin Isaev
la source
1
"typed" nulls- Explique. Quel objet renvoyez-vous? Si vous renvoyez un objet de type type, mais que sa valeur est null, alors il ne - ne peut pas - avoir d'autre information que celle qu'il est null. Vous ne pouvez pas interroger une nullvaleur et découvrir de quel type il s'agit. Si vous ne retournez PAS null, mais revenez .. Je ne sais pas quoi .., alors cela ne fonctionnera pas comme null.
ToolmakerSteve
3

Je ne trouve rien de simple et d'élégant pour l'instant, mais j'ai une idée: si vous connaissez le type de propriété que vous souhaitez définir, vous pouvez écrire le vôtre default(T). Il y a deux cas - Test un type de valeur et Test un type de référence. Vous pouvez le voir en cochant T.IsValueType. S'il Ts'agit d'un type de référence, vous pouvez simplement le définir sur null. Si Test un type de valeur, alors il aura un constructeur sans paramètre par défaut que vous pouvez appeler pour obtenir une valeur "vide".

Vilx-
la source
3

Je fais la même tâche comme ça.

//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
la source
2

Équivalent à la réponse de Dror mais comme méthode d'extension:

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;
        }
    }
}
Paul Fleming
la source
2

Légers ajustements à la solution @Rob Fonseca-Ensor : La méthode d'extension suivante fonctionne également sur .Net Standard puisque j'utilise GetRuntimeMethod au lieu de 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);
    }
}

... et le test unitaire correspondant pour ceux qui se soucient de la qualité:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
la source
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;
    }
Kaz-LA
la source
2
Ne fonctionne pas pour les Nullable<T>types: il ne retourne pas l'équivalent default(Nullable<T>)qui devrait l'être null. La réponse acceptée par Dror fonctionne mieux.
Cœur
peut vérifier si nullable en utilisant la réflexion ...
dancer42
0

Cela devrait fonctionner: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

danseur42
la source