Equivalente programático do padrão (Tipo)

514

Estou usando a reflexão para percorrer Typeas propriedades de a e definir certos tipos como padrão. Agora, eu poderia mudar o tipo e definir default(Type)explicitamente, mas prefiro fazê-lo em uma linha. Existe um equivalente programático do padrão?

tags2k
fonte
Isso deve funcionar: Nullable <T> a = new Nullable <T> () .GetValueOrDefault ();
dancer42 5/06

Respostas:

694
  • No caso de um tipo de valor, use Activator.CreateInstance e deve funcionar bem.
  • Ao usar o tipo de referência, basta retornar null
public static object GetDefault(Type type)
{
   if(type.IsValueType)
   {
      return Activator.CreateInstance(type);
   }
   return null;
}

Na versão mais recente do .net, como o padrão .net, type.IsValueTypeprecisa ser escrita comotype.GetTypeInfo().IsValueType

Dror Helper
fonte
22
Isso retornará um tipo de valor em caixa e, portanto, não é o equivalente exato do padrão (Tipo). No entanto, é o mais próximo que você conseguirá sem genéricos.
Russell Giddings 07/07
8
E daí? Se você encontrar um tipo default(T) != (T)(object)default(T) && !(default(T) != default(T))com argumento, caso contrário, não importa se está em caixa ou não, pois são equivalentes.
Miguel Angelo
7
A última parte do predicado é evitar trapacear com sobrecarga do operador ... pode-se tornar o default(T) != default(T)retorno falso, e isso é trapaça! =)
Miguel Angelo
4
Isso me ajudou muito, mas achei que deveria acrescentar algo que pode ser útil para algumas pessoas que pesquisam essa pergunta - há também um método equivalente se você deseja uma matriz do tipo especificado e pode obtê-la usando Array.CreateInstance(type, length).
Darrel Hoffman
4
Não se preocupe em criar uma instância de um tipo de valor desconhecido? Isso pode ter efeitos colaterais.
Ygormutti 14/10
103

Por que não chamar o método que retorna o padrão (T) com reflexão? Você pode usar GetDefault de qualquer tipo com:

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

    public T GetDefaultGeneric<T>()
    {
        return default(T);
    }
drake7707
fonte
7
Isso é brilhante porque é muito simples. Embora não seja a melhor solução aqui, é uma solução importante a ter em mente, porque essa técnica pode ser útil em muitas circunstâncias semelhantes.
Configurator
Se você chamar o método genérico "getDefault" em vez (sobrecarga), faça o seguinte:.. This.GetType () GetMethod ( "getDefault", novo tipo [0]) <AS_IS>
Stefan Steiger
2
Lembre-se de que essa implementação é muito mais lenta (devido à reflexão) do que a resposta aceita. Ainda é viável, mas você precisará configurar algum cache para as chamadas GetMethod () / MakeGenericMethod () para melhorar o desempenho.
Doug
1
É possível que o argumento de tipo seja nulo. Por exemplo, MethodBase.ResultType () de um método void retornará um objeto Type com o nome "Void" ou com o nome completo "System.Void". Portanto, coloquei uma guarda: if (t.FullName == "System.Void") return null; Obrigado pela solução.
Valo 23/03
8
Melhor usar nameof(GetDefaultGeneric)se puder, em vez de"GetDefaultGeneric"
Mugen
87

Você pode usar PropertyInfo.SetValue(obj, null) . Se chamado em um tipo de valor, ele fornecerá o padrão. Esse comportamento está documentado no .NET 4.0 e no .NET 4.5 .

JoelFan
fonte
7
Para esta questão específica - fazer um loop pelas propriedades de um tipo E configurá-las para "padrão" - isso funciona de maneira brilhante. Eu o uso ao converter de um SqlDataReader para um objeto usando reflexão.
Arno Peters
57

Se você estiver usando o .NET 4.0 ou superior e desejar uma versão programática que não seja uma codificação de regras definidas fora do código , poderá criar umExpression , compilar e executá-lo rapidamente.

O seguinte método de extensão utilizará ae Typeobterá o valor retornado default(T)pelo Defaultmétodo na 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()();
}

Você também deve armazenar em cache o valor acima com base em Type, mas esteja ciente de que, se você está chamando isso para um grande número de Typeinstâncias, e não o usa constantemente, a memória consumida pelo cache pode superar os benefícios.

casperOne
fonte
4
Desempenho para 'return type.IsValueType? Activator.CreateInstance (type): null; ' é 1000x mais rápido que e.Compile () ();
Cyrus
1
@ Cyrus Tenho certeza de que seria o contrário, se você o cache e.Compile(). Esse é o objetivo das expressões.
Nawfal
2
Executou uma referência. Obviamente, o resultado de e.Compile()deve ser armazenado em cache, mas supondo que esse método seja aproximadamente 14x mais rápido, por exemplo long. Consulte gist.github.com/pvginkel/fed5c8512b9dfefc2870c6853bbfbf8b para obter o benchmark e os resultados.
Pieter van Ginkel
3
Fora de interesse, por que armazenar e.Compile()em cache e não e.Compile()()? ou seja, o tipo padrão de um tipo pode ser alterado em tempo de execução? Caso contrário (como acredito que seja o caso), você pode simplesmente armazenar em cache o resultado, em vez da expressão compilada, o que deve melhorar ainda mais o desempenho.
21419 JohnLBevan
3
@ JohnLBevan - sim, e então não importa qual técnica você usa para obter o resultado - todos terão um desempenho amortizado extremamente rápido (uma pesquisa no dicionário).
Daniel Earwicker
38

Por que você diz que os genéricos estão fora de cena?

    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
fonte
Não é possível resolver o símbolo Método. Usando uma PCL para Windows.
Cœur
1
qual é o custo de criar o método genérico em tempo de execução e usá-lo milhares de vezes seguidas?
C. Tewalt
1
Eu estava pensando em algo assim. A melhor e mais elegante solução para mim. Funciona mesmo no Compact Framework 2.0. Se você está preocupado com o desempenho, sempre pode armazenar em cache o método genérico, não é?
27415 Bart
Esta solução combina exatamente! Obrigado!
Lachezar Lalov 12/10
25

Esta é a solução otimizada da 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
fonte
2
Uma versão curta do retorno:return type.IsValueType ? typeDefaults.GetOrAdd(type, Activator.CreateInstance) : null;
Mark Whitfeld 23/01
3
E quanto a estruturas mutáveis? Você sabia que é possível (e legal) modificar os campos de uma estrutura em caixa, para que os dados sejam alterados?
IllidanS4 quer Monica de volta 26/04/2015
@ IllidanS4, como o nome do método implica, isso é apenas para os valores padrão do ValueType.
aderesh
8

A resposta escolhida é uma boa resposta, mas tenha cuidado com o objeto retornado.

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
fonte
14
verdade, mas isso vale para padrão (string), bem como, como qualquer outro tipo de referência ...
TDaver
string é um pássaro estranho - sendo um tipo de valor que pode retornar nulo também. Se quiser que o código para retornar String.Empty basta adicionar um caso especial para ele
Dror Helper
15
@Dror - string é um tipo de referência imutável, não um tipo de valor.
Ljs 18/08/11
@kronoz Você está certo - eu quis dizer que a string pode ser manipulada retornando string.empty ou null de acordo com a necessidade.
quer
5

As expressões podem ajudar aqui:

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

Não testei esse trecho, mas acho que ele deve produzir nulos "digitados" para tipos de referência.

Konstantin Isaev
fonte
1
"typed" nulls- explicar. Que objeto você está retornando? Se você retornar um objeto do tipo type, mas seu valor for null, ele não poderá - não poderá - ter nenhuma outra informação além dessa null. Você não pode consultar um nullvalor e descobrir que tipo ele supostamente é. Se você NÃO retornar nulo, mas retornar .. Eu não sei o que .., então não funcionará como null.
Home
3

Ainda não consigo encontrar nada simples e elegante, mas tenho uma ideia: se você conhece o tipo de propriedade que deseja definir, pode escrever sua própria default(T). Existem dois casos - Té um tipo de valor e Té um tipo de referência. Você pode ver isso verificando T.IsValueType. Se Té um tipo de referência, você pode simplesmente configurá-lo para null. Se Tfor um tipo de valor, ele terá um construtor sem parâmetros padrão que você pode chamar para obter um valor "em branco".

Vilx-
fonte
3

Eu faço a mesma tarefa assim.

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

Equivalente à resposta de Dror, mas como método de extensão:

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

Pequenos ajustes na solução do @Rob Fonseca-Ensor : O método de extensão a seguir também funciona no .Net Standard desde que eu uso GetRuntimeMethod em vez 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);
    }
}

... e o teste de unidade correspondente para aqueles que se preocupam com a qualidade:

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

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

    // Assert
    defaultValue.Should().Be(default(DateTime));
}
thomasgalliker
fonte
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
fonte
2
Não funciona para Nullable<T>tipos: não retorna o equivalente ao default(Nullable<T>)que deveria ser null. A resposta aceita pelo Dror funciona melhor.
Cœur
pode verificar se anulável usando reflexão ...
dancer42
0

Isso deve funcionar: Nullable<T> a = new Nullable<T>().GetValueOrDefault();

dancer42
fonte