Equivalente programático de defecto (Tipo)

514

Estoy usando la reflexión para recorrer Typelas propiedades de una y establecer ciertos tipos a sus valores predeterminados. Ahora, podría cambiar el tipo y configurarlo default(Type)explícitamente, pero prefiero hacerlo en una línea. ¿Existe un equivalente programático del incumplimiento?

etiquetas2k
fuente
Esto debería funcionar: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42

Respuestas:

694
  • En el caso de un tipo de valor, use Activator.CreateInstance y debería funcionar bien.
  • Cuando use el tipo de referencia, simplemente devuelva nulo
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

En la versión más reciente de .net, como .net standard, type.IsValueTypedebe escribirse comotype.GetTypeInfo().IsValueType

Dror Helper
fuente
22
Esto devolverá un tipo de valor en recuadro y, por lo tanto, no es el equivalente exacto del valor predeterminado (Tipo). Sin embargo, es lo más cerca que estarás sin genéricos.
Russell Giddings
8
¿Y qué? Si encuentra un tipo que default(T) != (T)(object)default(T) && !(default(T) != default(T))tiene un argumento, de lo contrario, no importa si está encuadrado o no, ya que son equivalentes.
Miguel Angelo
77
La última parte del predicado es evitar hacer trampa con la sobrecarga del operador ... ¡uno podría hacer que default(T) != default(T)return sea falso, y eso es hacer trampa! =)
Miguel Angelo
44
Esto me ayudó mucho, pero pensé que debería agregar algo que podría ser útil para algunas personas que buscan esta pregunta: también hay un método equivalente si desea una matriz del tipo dado, y puede obtenerla usando Array.CreateInstance(type, length).
Darrel Hoffman
44
¿No te preocupa crear una instancia de un tipo de valor desconocido? Esto puede tener efectos colaterales.
ygormutti
103

¿Por qué no llamar al método que devuelve default (T) con reflexión? Puede usar GetDefault de cualquier tipo con:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
fuente
77
Esto es brillante porque es muy simple. Si bien no es la mejor solución aquí, es una solución importante a tener en cuenta porque esta técnica puede ser útil en muchas circunstancias similares.
configurador
Si en su lugar llama al método genérico "GetDefault" (sobrecarga), haga esto: this.GetType (). GetMethod ("GetDefault", nuevo tipo [0]). <AS_IS>
Stefan Steiger
2
Tenga en cuenta que esta implementación es mucho más lenta (debido a la reflexión) que la respuesta aceptada. Todavía es viable, pero necesitaría configurar un poco de almacenamiento en caché para las llamadas GetMethod () / MakeGenericMethod () para mejorar el rendimiento.
Doug
1
Es posible que el argumento tipo sea nulo. Por ejemplo, MethodBase.ResultType () de un método void devolverá un objeto Type con Nombre "Void" o con FullName "System.Void". Por lo tanto, pongo una protección: if (t.FullName == "System.Void") return null; Gracias por la solucion.
Valo
8
Mejor uso nameof(GetDefaultGeneric)si puedes, en lugar de"GetDefaultGeneric"
Mugen
87

Puedes usar PropertyInfo.SetValue(obj, null). Si se llama a un tipo de valor, le dará el valor predeterminado. Este comportamiento está documentado en .NET 4.0 y en .NET 4.5 .

JoelFan
fuente
77
Para esta pregunta específica, recorrer las propiedades de un tipo Y configurarlas como "predeterminadas", esto funciona de manera brillante. Lo uso cuando convierto de un SqlDataReader a un objeto usando la reflexión.
Arno Peters
58

Si está utilizando .NET 4.0 o superior y desea una versión programática que no sea una codificación de reglas definidas fuera del código , puede crear Expression, compilar y ejecutar sobre la marcha.

El siguiente método de extensión tomará ay Typeobtendrá el valor devuelto a default(T)través del Defaultmétodo en la Expressionclase:

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

También debe almacenar en caché el valor anterior en función del Type, pero tenga en cuenta que si llama a esto para una gran cantidad de Typeinstancias, y no lo usa constantemente, la memoria consumida por la memoria caché podría superar los beneficios.

casperOne
fuente
44
Rendimiento para 'return type.IsValueType? Activator.CreateInstance (type): null; ' es 1000 veces más rápido que e.Compile () ();
Cyrus
1
@ Cyrus Estoy bastante seguro de que sería al revés si almacena el caché e.Compile(). Ese es todo el punto de las expresiones.
nawfal
2
Corrió un punto de referencia. Obviamente, el resultado de e.Compile()debe almacenarse en caché, pero suponiendo que este método sea aproximadamente 14 veces más rápido, por ejemplo long. Consulte gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b para obtener el punto de referencia y los resultados.
Pieter van Ginkel
3
Fuera de interés, ¿por qué caché en e.Compile()lugar de e.Compile()()? es decir, ¿puede cambiar el tipo predeterminado de un tipo en tiempo de ejecución? Si no (como creo que es el caso), simplemente puede almacenar el resultado en caché en lugar de la expresión compilada, lo que debería mejorar aún más el rendimiento.
JohnLBevan
3
@JohnLBevan: sí, y no importará la técnica que utilice para obtener el resultado; todos tendrán un rendimiento amortizado extremadamente rápido (una búsqueda en el diccionario).
Daniel Earwicker
38

¿Por qué dice que los genéricos están fuera de la imagen?

    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
fuente
No se puede resolver el método del símbolo. Usando una PCL para Windows.
Cœur
1
¿Qué tan costoso es crear el método genérico en tiempo de ejecución y luego usarlo miles de veces seguidas?
C. Tewalt
1
Estaba pensando en algo como esto. La mejor y más elegante solución para mí. Funciona incluso en Compact Framework 2.0. Si le preocupa el rendimiento, siempre puede almacenar en caché el método genérico, ¿no?
Bart
¡Esta solución se adapta exactamente! ¡Gracias!
Lachezar Lalov
25

Esta es la solución optimizada 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
fuente
2
Una versión abreviada del regreso:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld
3
¿Qué pasa con las estructuras mutables? ¿Sabe que es posible (y legal) modificar los campos de una estructura en caja para que los datos cambien?
IllidanS4 quiere que Mónica regrese el
@ IllidanS4 como el nombre del método implica que esto es solo para los valores predeterminados de ValueType.
aderesh
8

La respuesta elegida es una buena respuesta, pero tenga cuidado con el objeto devuelto.

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

Extrapolando ...

string test = GetDefault(typeof(string));
if (test is string)
     Console.WriteLine("This will never be hit.");
BSick7
fuente
14
cierto, pero eso ocurre con defecto (cadena), así como cualquier otro tipo de referencia ...
TDaver
string es un pájaro extraño, siendo un tipo de valor que también puede devolver nulo. Si desea que el código devuelva string.empty, simplemente agregue un caso especial para él
Dror Helper el
15
@Dror: la cadena es un tipo de referencia inmutable, no un tipo de valor.
ljs
@kronoz Tienes razón: quise decir que la cadena se puede manejar devolviendo string.empty o null según la necesidad.
Dror Helper
5

Las expresiones pueden ayudar aquí:

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

No probé este fragmento, pero creo que debería producir nulos "tipados" para los tipos de referencia.

Konstantin Isaev
fuente
1
"typed" nulls- explique. ¿Qué objeto estás devolviendo? Si devuelve un objeto de tipo type, pero su valor es null, entonces no tiene, no puede tener, ninguna otra información que no sea esa null. No puede consultar un nullvalor y averiguar qué tipo es supuestamente. Si NO devuelve nulo, pero regresa ... No sé qué ..., entonces no actuará como null.
ToolmakerSteve
3

Todavía no puedo encontrar nada simple y elegante, pero tengo una idea: si conoce el tipo de propiedad que desea establecer, puede escribir la suya default(T). Hay dos casos: Tes un tipo de valor y Tes un tipo de referencia. Puedes ver esto marcando T.IsValueType. Si Tes un tipo de referencia, simplemente puede establecerlo en null. Si Tes un tipo de valor, tendrá un constructor sin parámetros predeterminado al que puede llamar para obtener un valor "en blanco".

Vilx-
fuente
3

Hago la misma tarea así.

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

Equivalente a la respuesta de Dror pero como método de extensión:

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

Pequeños ajustes a la solución de @Rob Fonseca-Ensor : El siguiente método de extensión también funciona en .Net Standard ya que uso GetRuntimeMethod en lugar 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);
    }
}

... y la prueba de unidad correspondiente para aquellos que se preocupan por la calidad:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
fuente
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
fuente
2
No funciona para los Nullable<T>tipos: no devuelve el equivalente de lo default(Nullable<T>)que debería ser null. La respuesta aceptada por Dror funciona mejor.
Cœur
puede comprobar si se pueden anular mediante reflexión ...
dancer42
0

Esto debería funcionar: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

bailarín42
fuente