Programmatisches Äquivalent zum Standard (Typ)

514

Ich verwende Reflection, um die TypeEigenschaften von a zu durchlaufen und bestimmte Typen auf ihre Standardeinstellungen zu setzen. Jetzt könnte ich den Typ umschalten und default(Type)explizit festlegen , aber ich würde es lieber in einer Zeile tun. Gibt es ein programmatisches Äquivalent zum Standard?

tags2k
quelle
Dies sollte funktionieren: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42

Antworten:

694
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

In der neueren Version von .net wie .net Standard type.IsValueTypemuss geschrieben werden alstype.GetTypeInfo().IsValueType

Dror Helfer
quelle
22
Dies gibt einen Boxed-Value-Typ zurück und entspricht daher nicht genau dem Standardwert (Type). Es ist jedoch so nah wie möglich ohne Generika.
Russell Giddings
8
Na und? Wenn Sie einen Typ finden, für den default(T) != (T)(object)default(T) && !(default(T) != default(T))Sie dann ein Argument haben, spielt es keine Rolle, ob es eingerahmt ist oder nicht, da sie gleichwertig sind.
Miguel Angelo
7
Das letzte Stück des Prädikats besteht darin, Betrug durch Überlastung des Bedieners zu vermeiden ... man könnte die default(T) != default(T)Rückgabe falsch machen, und das ist Betrug! =)
Miguel Angelo
4
Das hat mir sehr geholfen, aber ich dachte, ich sollte eine Sache hinzufügen, die für einige Leute, die diese Frage suchen, nützlich sein könnte - es gibt auch eine äquivalente Methode, wenn Sie ein Array des angegebenen Typs möchten, und Sie können es mithilfe von erhalten Array.CreateInstance(type, length).
Darrel Hoffman
4
Machen Sie sich keine Sorgen darüber, eine Instanz eines unbekannten Werttyps zu erstellen? Dies kann Sicherheiteneffekte haben.
Ygormutti
103

Warum nicht die Methode aufrufen, die Standard (T) mit Reflektion zurückgibt? Sie können GetDefault eines beliebigen Typs verwenden mit:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
quelle
7
Das ist großartig, weil es so einfach ist. Obwohl dies hier nicht die beste Lösung ist, ist es eine wichtige Lösung, die Sie berücksichtigen sollten, da diese Technik unter vielen ähnlichen Umständen nützlich sein kann.
Konfigurator
Wenn Sie stattdessen die generische Methode "GetDefault" aufrufen (Überladen), gehen Sie folgendermaßen vor: this.GetType (). GetMethod ("GetDefault", neuer Typ [0]). <AS_IS>
Stefan Steiger
2
Beachten Sie, dass diese Implementierung (aufgrund von Überlegungen) viel langsamer ist als die akzeptierte Antwort. Es ist immer noch möglich, aber Sie müssen ein Caching für die Aufrufe GetMethod () / MakeGenericMethod () einrichten, um die Leistung zu verbessern.
Doug
1
Möglicherweise ist das Argument type nichtig. Beispielsweise gibt MethodBase.ResultType () einer void-Methode ein Type-Objekt mit dem Namen "Void" oder mit dem FullName "System.Void" zurück. Deshalb habe ich eine Wache gesetzt: if (t.FullName == "System.Void") gibt null zurück; Danke für die Lösung.
Valo
8
Besser verwenden, nameof(GetDefaultGeneric)wenn Sie können, anstatt"GetDefaultGeneric"
Mugen
87

Sie können verwenden PropertyInfo.SetValue(obj, null). Wenn ein Werttyp aufgerufen wird, erhalten Sie die Standardeinstellung. Dieses Verhalten ist in .NET 4.0 und in .NET 4.5 dokumentiert .

JoelFan
quelle
7
Für diese spezielle Frage - Durchlaufen der Eigenschaften eines Typs UND Festlegen auf "Standard" - funktioniert dies hervorragend. Ich verwende es beim Konvertieren von einem SqlDataReader in ein Objekt mithilfe von Reflektion.
Arno Peters
57

Wenn Sie .NET 4.0 oder höher verwenden und eine programmatische Version wünschen, bei der es sich nicht um eine Kodifizierung von Regeln handelt, die außerhalb des Codes definiert sind , können Sie eine erstellen Expression, kompilieren und im laufenden Betrieb ausführen.

Die folgende Erweiterungsmethode verwendet a Typeund ruft den Wert ab, der default(T)über die DefaultMethode für die ExpressionKlasse zurückgegeben wird:

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

Sie sollten den oben genannten Wert auch basierend auf dem Typezwischenspeichern. Beachten Sie jedoch Type, dass der vom Cache verbrauchte Speicher möglicherweise die Vorteile überwiegt, wenn Sie ihn für eine große Anzahl von Instanzen aufrufen und ihn nicht ständig verwenden.

casperOne
quelle
4
Leistung für 'return type.IsValueType? Activator.CreateInstance (Typ): null; ' ist 1000x schneller als e.Compile () ();
Cyrus
1
@ Cyrus Ich bin mir ziemlich sicher, dass es umgekehrt wäre, wenn Sie das zwischenspeichern e.Compile(). Das ist der springende Punkt bei Ausdrücken.
Nawfal
2
Lief einen Benchmark. Natürlich sollte das Ergebnis von e.Compile()zwischengespeichert werden, aber unter der Annahme, dass diese Methode ungefähr 14x so schnell ist, z long. Benchmark und Ergebnisse finden Sie unter gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b .
Pieter van Ginkel
3
Aus Interesse, warum e.Compile()eher zwischenspeichern als e.Compile()()? dh Kann sich der Standardtyp eines Typs zur Laufzeit ändern? Wenn nicht (wie ich glaube), können Sie nur das Ergebnis und nicht den kompilierten Ausdruck im Cache speichern, was die Leistung weiter verbessern sollte.
JohnLBevan
3
@JohnLBevan - ja, und dann spielt es keine Rolle, mit welcher Technik Sie das Ergebnis erzielen - alle haben eine extrem schnelle amortisierte Leistung (eine Wörterbuchsuche).
Daniel Earwicker
38

Warum sagen Sie, dass Generika nicht im Bilde sind?

    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
quelle
Symbolmethode kann nicht aufgelöst werden. Verwenden einer PCL für Windows.
Cœur
1
Wie teuer ist es, die generische Methode zur Laufzeit zu erstellen und dann mehrere tausend Mal hintereinander zu verwenden?
C. Tewalt
1
Ich habe über so etwas nachgedacht. Beste und eleganteste Lösung für mich. Funktioniert sogar mit Compact Framework 2.0. Wenn Sie sich Sorgen um die Leistung machen, können Sie die generische Methode immer zwischenspeichern, nicht wahr?
Bart
Diese Lösung passt genau! Vielen Dank!
Lachezar Lalov
25

Dies ist die optimierte Flem-Lösung:

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
quelle
2
Eine kurze return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Handversion
3
Was ist mit veränderlichen Strukturen? Wissen Sie, dass es möglich (und legal) ist, Felder einer Boxstruktur so zu ändern, dass sich die Daten ändern?
IllidanS4 will Monica zurück
@ IllidanS4, wie der Name der Methode impliziert, gilt dies nur für die Standardwerte von ValueType.
Aderesh
8

Die gewählte Antwort ist eine gute Antwort, aber seien Sie vorsichtig mit dem zurückgegebenen Objekt.

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

Extrapolieren ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
BSick7
quelle
14
wahr, aber das gilt auch für Standard (Zeichenfolge), wie jeder andere Referenztyp ...
TDaver
string ist ein seltsamer Vogel - ein Werttyp, der auch null zurückgeben kann. Wenn Sie möchten, dass der Code string.empty zurückgibt, fügen Sie einfach einen Sonderfall hinzu
Dror Helper
15
@Dror - string ist ein unveränderlicher Referenztyp, kein Werttyp.
ljs
@kronoz Du hast Recht - ich meinte, dass String behandelt werden kann, indem string.empty oder null je nach Bedarf zurückgegeben wird.
Dror Helper
5

Die Ausdrücke können hier helfen:

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

Ich habe dieses Snippet nicht getestet, aber ich denke, es sollte "typisierte" Nullen für Referenztypen erzeugen.

Konstantin Isaev
quelle
1
"typed" nulls- erklären. Welches Objekt geben Sie zurück? Wenn Sie ein Objekt vom Typ zurückgeben type, dessen Wert jedoch lautet null, kann es keine anderen Informationen als die vorhandenen enthalten null. Sie können einen nullWert nicht abfragen und herausfinden, um welchen Typ es sich angeblich handelt. Wenn Sie NICHT null zurückgeben, sondern zurückkehren .. Ich weiß nicht was .., dann wird es nicht so handeln null.
ToolmakerSteve
3

Ich kann noch nichts Einfaches und Elegantes finden, aber ich habe eine Idee: Wenn Sie den Typ der Immobilie kennen, die Sie festlegen möchten, können Sie Ihre eigene schreiben default(T). Es gibt zwei Fälle - Tist ein Werttyp und Tein Referenztyp. Sie können dies sehen, indem Sie überprüfen T.IsValueType. Wenn Tes sich um einen Referenztyp handelt, können Sie ihn einfach auf einstellen null. Wenn Tes sich um einen Werttyp handelt, verfügt er über einen standardmäßigen parameterlosen Konstruktor, den Sie aufrufen können, um einen "leeren" Wert zu erhalten.

Vilx-
quelle
3

Ich mache die gleiche Aufgabe wie diese.

//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
quelle
2

Entspricht Drors Antwort, jedoch als Erweiterungsmethode:

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
quelle
2

Leichte Anpassungen an der Lösung von @Rob Fonseca-Ensor : Die folgende Erweiterungsmethode funktioniert auch mit .Net Standard, da ich GetRuntimeMethod anstelle von GetMethod verwende.

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

... und der entsprechende Unit-Test für diejenigen, denen Qualität am Herzen liegt:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
quelle
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
quelle
2
Funktioniert nicht für Nullable<T>Typen: Es wird nicht das Äquivalent zurückgegeben, default(Nullable<T>)das sein sollte null. Akzeptierte Antwort von Dror funktioniert besser.
Cœur
kann mit Reflection überprüfen, ob
nullbar ist
0

Das sollte funktionieren: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

Tänzer42
quelle