Programowy odpowiednik wartości domyślnej (typ)

514

Korzystam z odbicia, aby przeglądać Typewłaściwości a i ustawiać niektóre typy na domyślne. Teraz mógłbym zmienić typ i ustawić default(Type)jawnie, ale wolałbym to zrobić w jednym wierszu. Czy istnieje programowy odpowiednik wartości domyślnej?

tags2k
źródło
To powinno działać: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
tancerz42

Odpowiedzi:

694
  • W przypadku typu wartości użyj Activator.CreateInstance i powinno działać dobrze.
  • Używając typu referencji, po prostu zwróć null
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

W nowszej wersji .net, takiej jak .net standard, type.IsValueTypenależy zapisać jakotype.GetTypeInfo().IsValueType

Dror Helper
źródło
22
Spowoduje to zwrócenie typu wartości w pudełku, a zatem nie jest dokładnym odpowiednikiem wartości domyślnej (Typ). Jest jednak tak blisko, jak to możliwe, bez leków generycznych.
Russell Giddings,
8
Więc co? Jeśli znajdziesz typ, który default(T) != (T)(object)default(T) && !(default(T) != default(T))masz argument, w przeciwnym razie nie ma znaczenia, czy jest on zapakowany, czy nie, ponieważ są one równoważne.
Miguel Angelo,
7
Ostatnim fragmentem predykatu jest unikanie oszustwa z przeciążeniem operatora ... można sprawić, że default(T) != default(T)zwrot będzie fałszywy, a to jest oszustwo! =)
Miguel Angelo
4
Bardzo mi to pomogło, ale pomyślałem, że powinienem dodać jedną rzecz, która może być przydatna dla niektórych osób szukających tego pytania - istnieje również równoważna metoda, jeśli chcesz tablicę danego typu i możesz ją uzyskać, używając Array.CreateInstance(type, length).
Darrel Hoffman
4
Nie martwisz się utworzeniem wystąpienia o nieznanym typie wartości? Może to mieć skutki uboczne.
ygormutti
103

Dlaczego nie wywołać metody, która zwraca wartość domyślną (T) z odbiciem? Możesz używać GetDefault dowolnego typu z:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
źródło
7
To jest genialne, ponieważ jest takie proste. Chociaż nie jest to najlepsze rozwiązanie, należy o tym pamiętać, ponieważ ta technika może być przydatna w wielu podobnych okolicznościach.
konfigurator
Jeśli zamiast tego wywołasz metodę ogólną „GetDefault” (przeciążenie), zrób to: this.GetType (). GetMethod („GetDefault”, nowy typ [0]). <AS_IS>
Stefan Steiger
2
Należy pamiętać, że ta implementacja jest znacznie wolniejsza (z powodu refleksji) niż zaakceptowana odpowiedź. Nadal jest to wykonalne, ale aby poprawić wydajność, musisz ustawić buforowanie dla wywołań GetMethod () / MakeGenericMethod ().
Doug
1
Możliwe jest, że argument typu jest nieważny. Np. MethodBase.ResultType () metody void zwróci obiekt Type o nazwie „Void” lub o pełnej nazwie „System.Void”. Dlatego stawiam ostrożność: if (t.FullName == "System.Void") return null; Dzięki za rozwiązanie.
Valo
8
Lepiej użyj, nameof(GetDefaultGeneric)jeśli możesz, zamiast"GetDefaultGeneric"
Mugen
87

Możesz użyć PropertyInfo.SetValue(obj, null). Wywołanie typu wartości daje wartość domyślną. To zachowanie jest udokumentowane w .NET 4.0 i .NET 4.5 .

JoelFan
źródło
7
W przypadku tego konkretnego pytania - zapętlenia właściwości typu ORAZ ustawienia ich na „domyślne” - działa to doskonale. Używam go podczas konwersji z SqlDataReader do obiektu za pomocą odbicia.
Arno Peters
57

Jeśli używasz .NET 4.0 lub nowszej wersji i chcesz mieć wersję programową, która nie jest kodyfikacją reguł zdefiniowanych poza kodem , możesz utworzyć Expression, skompilować i uruchomić go w locie.

Poniższa metoda rozszerzenie będzie trwać Typei uzyskać wartość zwracana z default(T)pośrednictwem Defaultmetody w Expressionklasie:

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

Powinieneś również buforować powyższą wartość na podstawie Type, ale pamiętaj, jeśli wywołujesz to dla dużej liczby Typeinstancji, i nie używaj jej stale, pamięć zajęta przez pamięć podręczną może przeważyć korzyści.

casperOne
źródło
4
Wydajność dla typu zwracanego.IsValueType? Activator.CreateInstance (type): null; ' jest 1000 razy szybszy niż e.Compile () ();
Cyrus
1
@ Cyrus Jestem całkiem pewien, że byłoby odwrotnie, gdybyś buforował e.Compile(). To jest cały punkt wyrażeń.
nawfal
2
Przebiegł test. Oczywiście wynik e.Compile()należy zapisać w pamięci podręcznej, ale przy założeniu, że metoda ta jest około 14 razy szybsza np long. Zobacz test i wyniki na gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b .
Pieter van Ginkel
3
Zainteresowanie, dlaczego e.Compile()zamiast pamięci podręcznej e.Compile()()? tzn. Czy domyślny typ typu może się zmieniać w czasie wykonywania? Jeśli nie (jak sądzę), możesz po prostu zapisać wynik w pamięci podręcznej zamiast skompilowanego wyrażenia, co powinno jeszcze bardziej poprawić wydajność.
JohnLBevan
3
@JohnLBevan - tak, a wtedy nie będzie miało znaczenia, jakiej techniki użyjesz, aby uzyskać wynik - wszystko będzie miało wyjątkowo szybką amortyzowaną wydajność (wyszukiwanie w słowniku).
Daniel Earwicker
38

Dlaczego mówisz, że generyczne są poza obrazem?

    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
źródło
Nie można rozwiązać symbolu Metoda. Korzystanie z PCL dla Windows.
Cœur
1
ile kosztuje stworzenie ogólnej metody w czasie wykonywania, a następnie użycie jej kilka tysięcy razy z rzędu?
C. Tewalt
1
Myślałem o czymś takim. Najlepsze i najbardziej eleganckie rozwiązanie dla mnie. Działa nawet w Compact Framework 2.0. Jeśli martwisz się wydajnością, zawsze możesz buforować metodę ogólną, prawda?
Bart
To rozwiązanie dokładnie pasuje! Dzięki!
Lachezar Lalov
25

Jest to zoptymalizowane rozwiązanie 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;
        }
    }
}
kufel
źródło
2
Krótka wersja zwrotu:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld
3
Co z modyfikowalnymi strukturami? Czy wiesz, że można (i legalnie) modyfikować pola struktury pudełkowanej, aby zmienić dane?
IllidanS4 chce, aby Monica wróciła
@ IllidanS4, ponieważ nazwa metody implikuje, że jest to tylko domyślne wartości ValueType.
aderesh
8

Wybrana odpowiedź jest dobrą odpowiedzią, ale uważaj na zwrócony obiekt.

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

Ekstrapolacja ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
B Wybierz 7
źródło
14
prawda, ale dotyczy to również wartości domyślnej (ciąg znaków), jak każdego innego typu odwołania ...
TDaver 21.01.11
string jest nieparzystym ptakiem - będącym typem wartości, który może również zwrócić null. Jeśli chcesz, aby kod zwrócił ciąg znaków. Pusty po prostu dodaj do niego specjalny przypadek
Dror Helper
15
@Dror - ciąg znaków jest niezmiennym typem odniesienia, a nie typem wartości.
ljs
@ kronoz Masz rację - miałem na myśli, że ciąg może być obsługiwany przez zwrócenie string.pusty lub null w zależności od potrzeb.
Dror Helper
5

Wyrażenia mogą tutaj pomóc:

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

Nie testowałem tego fragmentu, ale myślę, że powinien on wygenerować „typowane” wartości zerowe dla typów referencyjnych.

Konstantin Isaev
źródło
1
"typed" nulls- wyjaśnić. Jaki przedmiot zwracasz? Jeśli zwrócisz obiekt typu type, ale jego wartość to null, to nie będzie - nie może - mieć żadnych innych informacji poza tym, czym jest null. Nie możesz zapytać o nullwartość i dowiedzieć się, jaki to typ. Jeśli NIE zwrócisz null, ale wrócisz ... Nie wiem co ..., to nie będzie tak null.
ToolmakerSteve
3

Nie mogę jeszcze znaleźć niczego prostego i eleganckiego, ale mam jeden pomysł: jeśli znasz typ nieruchomości, którą chcesz ustawić, możesz napisać własną default(T). Istnieją dwa przypadki - Tjest to typ wartości i Tjest typem referencyjnym. Możesz to zobaczyć, zaznaczając T.IsValueType. Jeśli Tjest to typ odniesienia, możesz go po prostu ustawić na null. Jeśli Tjest typem wartości, będzie miał domyślny konstruktor bez parametrów, który można wywołać, aby uzyskać „pustą” wartość.

Vilx-
źródło
3

Wykonuję takie samo zadanie jak to.

//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
źródło
2

Odpowiednik odpowiedzi Drora, ale jako metoda rozszerzenia:

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
źródło
2

Nieznaczne zmiany w rozwiązaniu @Rob Fonseca-Ensor : Następująca metoda rozszerzenia działa również w .Net Standard, ponieważ używam GetRuntimeMethod zamiast 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);
    }
}

... i odpowiedni test jednostkowy dla tych, którym zależy na jakości:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
źródło
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
źródło
2
Nie działa dla Nullable<T>typów: nie zwraca odpowiednika, default(Nullable<T>)który powinien być null. Odpowiedź zaakceptowana przez Drora działa lepiej.
Cœur
można sprawdzić, czy zerowalne za pomocą odbicia ...
dancer42
0

To powinno działać: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

tancerz42
źródło