Pregunta

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

¿Fue útil?

Solución

  • En el caso de un tipo de valor, use Activator.CreateInstance y debería funcionar bien.
  • Cuando se usa el tipo de referencia, simplemente devuelve 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.IsValueType debe escribirse como type.GetTypeInfo().IsValueType

Otros consejos

¿Por qué no llamar al método que devuelve el valor predeterminado (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);
    }

Puede 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 .

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 un Expresión , compílelo y ejecútelo en -fly.

El siguiente método de extensión tomará un Tipo y obtenga el valor devuelto por default (T) a través de Método predeterminado en la clase Expression :

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 Tipo , pero tenga en cuenta si está llamando a esto para una gran cantidad de instancias de Tipo , y no Úselo constantemente, la memoria consumida por el caché podría superar los beneficios.

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

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

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.");

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 "tipeado" nulos para tipos de referencia ..

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

Hago la misma tarea de esta manera.

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

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

Ajustes leves a Solución de @Rob Fonseca-Ensor : El siguiente método de extensión también funciona en .Net Standard desde 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));
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top