Transmitindo uma variável usando uma variável Type

280

Em C #, posso converter uma variável do tipo objeto em uma variável do tipo T, onde T é definido na variável Type?

theringostarrs
fonte
12
Não estritamente no tópico, mas você parece confuso o suficiente sobre o que "conversão" significa que pode ser uma boa idéia entender com precisão qual é o objetivo e a semântica do operador de conversão. Aqui está um bom começo: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert
2
Eu pensei que tinha pensado em alguma coisa. Se você tiver uma Typevariável, poderá usar a reflexão para criar uma instância desse tipo. E então você pode usar um método genérico para retornar o tipo desejado, deduzindo-o de um parâmetro desse tipo. Infelizmente, qualquer método de reflexão que crie uma instância de um tipo terá um tipo de retorno object; portanto, seu CastByExamplemétodo genérico também será usado object. Portanto, não há realmente nenhuma maneira de fazer isso, e mesmo que houvesse, o que você faria com o objeto recém-lançado? Você não pode usar seus métodos ou qualquer coisa, porque você não conhece seu tipo.
Kyle Delaney
@KyleDelaney Obrigado, concordo plenamente! Como tentei explicar em minha resposta, não é realmente útil converter algo para uma coisa diferente sem, em algum momento, definir o Tipo que você está realmente usando. O ponto principal dos tipos é a verificação do tipo de tempo do compilador. Se você apenas precisar fazer chamadas no objeto, poderá usar objectou dynamic. Se você deseja carregar dinamicamente módulos externos, é possível que as classes compartilhem uma interface comum e convertam o objeto para isso. Se você não controlar o código de terceiros, crie pequenos wrappers e implemente a interface.
Zyphrax 11/01

Respostas:

203

Aqui está um exemplo de elenco e de conversão:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Editar:

Algumas pessoas nos comentários dizem que esta resposta não responde à pergunta. Mas a linha (T) Convert.ChangeType(input, typeof(T))fornece a solução. O Convert.ChangeTypemétodo tenta converter qualquer objeto no tipo fornecido como o segundo argumento.

Por exemplo:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Eu escrevi a resposta com os genéricos, porque eu acho que é muito provável sinal de cheiro de código quando você quer elenco a somethingpara a something elsesem lidar com um tipo real. Com interfaces adequadas que não devem ser necessárias 99,9% das vezes. Talvez existam alguns casos extremos quando se trata de reflexão que possa fazer sentido, mas eu recomendaria evitar esses casos.

Edição 2:

Algumas dicas extras:

  • Tente manter seu código o mais seguro possível. Se o compilador não souber o tipo, não será possível verificar se seu código está correto e itens como o preenchimento automático não funcionarão. Simplesmente disse: se você não pode prever o (s) tipo (s) em tempo de compilação, como o compilador seria capaz ?
  • Se as classes com as quais você está trabalhando implementarem uma interface comum , você poderá converter o valor nessa interface. Caso contrário, considere criar sua própria interface e faça com que as classes implementem essa interface.
  • Se você estiver trabalhando com bibliotecas externas que está importando dinamicamente, verifique também se há uma interface comum. Caso contrário, considere criar pequenas classes de wrapper que implementam a interface.
  • Se você deseja fazer chamadas para o objeto, mas não se importa com o tipo, armazene o valor em uma objectou dynamicvariável.
  • Os genéricos podem ser uma ótima maneira de criar código reutilizável que se aplica a vários tipos diferentes, sem precisar saber os tipos exatos envolvidos.
  • Se você estiver preso, considere uma abordagem diferente ou um refator de código. Seu código realmente precisa ser dinâmico? Tem que dar conta de qualquer tipo que exista?
Zyphrax
fonte
145
Não sei como isso está ajudando o OP. Ela tem uma variável de tipo, não Tcomo tal.
Nawfal
12
@nawfal, basicamente a linha Convert.ChangeType(input, typeof(T));dá a solução. Você pode substituir facilmente typeof(T)por uma variável de tipo existente. Uma solução melhor (se possível) seria impedir o tipo dinâmico todos juntos.
Zyphrax
59
@Zyphrax, não, ainda requer um elenco para o Tqual não está disponível.
Nawfal
4
Eu sei que o objeto resultante é realmente do tipo, Tmas ainda assim você recebe um objectcomo referência. hmm, achei a pergunta interessante na premissa de que o OP tem apenas a Typevariável e nenhuma outra informação. Como se a assinatura do método é Convert(object source, Type destination):) No entanto eu recebo ur ponto
Nawfal
10
Como isso é uma solução para esta pergunta? Eu tenho o mesmo problema e não tenho um <T> genérico. Eu só tenho uma variável de tipo.
Nuri Tasdemir
114

Outras respostas não mencionam o tipo "dinâmico". Portanto, para adicionar mais uma resposta, você pode usar o tipo "dinâmico" para armazenar o objeto resultante sem precisar converter o objeto convertido com um tipo estático.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Lembre-se de que, com o uso de "dinâmico", o compilador está ignorando a verificação de tipo estático, que pode introduzir possíveis erros de tempo de execução se você não tomar cuidado.

maulik13
fonte
19
Essa é a resposta correta. Sem a palavra-chave dinâmica, typeof (alteradoObj) é "objeto". Com a palavra-chave dinâmica, ela funciona perfeitamente e typeof (modifiedObject) reflete corretamente o mesmo tipo que typeVar. Além disso, você não precisa (T) transmitir o que não pode fazer se não souber o tipo.
rushinge
5
Eu tenho a exceção "O objeto deve implementar o IConvertible" enquanto estiver usando esta solução. Qualquer ajuda?
Nuri Tasdemir
@NuriTasdemir Difícil dizer, mas acredito que a conversão que você está fazendo não é possível sem o IConvertible. Quais são os tipos envolvidos na sua conversão?
precisa saber é o seguinte
Enquanto isso funciona, há uma penalidade no desempenho com o uso da dinâmica. Eu recomendaria não usá-los, a menos que você esteja trabalhando com outros tempos de execução (que é para isso que a dinâmica foi projetada).
Bolo
19

Aqui está o meu método para converter um objeto, mas não para uma variável de tipo genérico, e sim para uma System.Typedinâmica:

Crio uma expressão lambda em tempo de execução usando System.Linq.Expressions, do tipo Func<object, object>, que desmarca sua entrada, executa a conversão de tipo desejada e, em seguida, fornece o resultado em caixa. Um novo é necessário não apenas para todos os tipos que são convertidos, mas também para os tipos que são convertidos (devido à etapa de remoção da caixa de seleção). Criar essas expressões consome muito tempo, devido à reflexão, à compilação e à construção dinâmica de métodos que são feitas sob o capô. Felizmente, uma vez criadas, as expressões podem ser invocadas repetidamente e sem sobrecarga, então eu cache cada uma.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Note que isso não é mágico. A transmissão não ocorre no código, como ocorre com a dynamicpalavra - chave, apenas os dados subjacentes do objeto são convertidos. No momento da compilação, ainda temos que descobrir com precisão qual o tipo de nosso objeto, tornando essa solução impraticável. Eu escrevi isso como um hack para chamar operadores de conversão definidos por tipos arbitrários, mas talvez alguém por aí possa encontrar um melhor caso de uso.

equilibrar
fonte
2
Requerusing System.Linq.Expressions;
Aaron D
4
Para mim, isso sofre do mesmo problema que a resposta de Zyphrax. Não consigo invocar métodos no objeto retornado porque ele ainda é do tipo "objeto". Se eu uso o método dele ("a" abaixo) ou o método ("b" abaixo), recebo o mesmo erro no elenco (t) - "'t' é uma variável, mas é usado como um tipo.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
@muusbolla A resposta original do Zyphrax usa genéricos e variáveis ​​de tipo, não Type. Você não pode transmitir usando a sintaxe de transmissão normal se tudo o que tiver é o objeto Type. Se você quiser usar o objeto como algum tipo T no tempo de compilação, não no tempo de execução, precisará convertê-lo usando uma variável de tipo ou apenas o nome do tipo real. Você pode fazer o primeiro usando a resposta de Zaphrax.
Ashley
8

Pondo de lado o boxe e o unboxing para simplificar, não há nenhuma ação específica de tempo de execução envolvida na transmissão ao longo da hierarquia de herança. É principalmente uma coisa de tempo de compilação. Essencialmente, uma conversão diz ao compilador para tratar o valor da variável como outro tipo.

O que você poderia fazer depois do elenco? Você não conhece o tipo, portanto não poderá chamar nenhum método. Não haveria nada de especial que você pudesse fazer. Especificamente, ele pode ser útil apenas se você conhecer os tipos possíveis em tempo de compilação, convertê-lo manualmente e manipular cada caso separadamente com ifinstruções:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Mehrdad Afshari
fonte
1
Poderia explicar isso mais claramente em relação à minha pergunta?
theringostarrs
O que estou tentando explicar é: o que você seria capaz de fazer depois disso? Você não pode fazer tanto quanto o compilador C # requer tipagem estática para ser capaz de fazer uma coisa útil com o objeto
Mehrdad Afshari
Você está certo. Eu sei os tipos esperados de duas variáveis ​​que são enviadas para o método como tipo 'objeto'. Quero converter para os tipos esperados armazenados em variáveis ​​e adicioná-los à coleção. Muito mais fácil ramificar no tipo e tentar um elenco normal e capturar erros.
theringostarrs
4
Sua resposta é boa, mas apenas para ser exigente, observo que os lançamentos nunca afetam variáveis . Nunca é legal converter uma variável para uma variável de outro tipo; tipos de variáveis ​​são invariantes em C #. Você só pode converter o valor armazenado na variável em outro tipo.
9119 Eric Lippert
A introdução de digitação dinâmica do C # 4.0 altera essa resposta?
Daniel T.
6

Como você pode fazer aquilo? Você precisa de uma variável ou campo do tipo T em que possa armazenar o objeto após a conversão, mas como pode ter essa variável ou campo se conhecer T somente em tempo de execução? Então, não, não é possível.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?
Daniel Brückner
fonte
3
Se você estiver usando uma classe genérica, que define um método com valor de retorno do tipo T, pode ser necessário fazer isso. Por exemplo, analisando uma string para uma instância de T e retornando isso.
Oliver Friedrich
7
Felizmente, essa não é a resposta correta. Veja a resposta de maulik13.
rushinge
3
Onde, em nome do céu, você encontra um CastTométodo Object?
ProfK
3

Quando se trata de transmitir para o tipo Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

E você vai chamar assim:

var enumValue = GetEnum(typeof(YourEnum), foo);

Isso foi essencial para mim no caso de obter o valor do atributo Description de vários tipos de enumeração pelo valor int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

e depois:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

Como alternativa (melhor abordagem), esse elenco poderia ser assim:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }
krzyski
fonte
1

Depois de não encontrar nada para contornar a exceção "O objeto deve implementar o IConvertible" ao usar a resposta do Zyphrax (exceto para implementar a interface) .. Tentei algo um pouco não convencional e trabalhei para a minha situação.

Usando o pacote nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);
Curt
fonte
1

Dano, o problema é que você não tem um T.

você só tem uma variável Type.

Dica para a MS, se você pudesse fazer algo como

TryCast<typeof(MyClass)>

se resolveria todos os nossos problemas.

user2825546
fonte
0

Eu nunca vou entender por que você precisa de até 50 reputação para deixar um comentário, mas eu apenas tinha que dizer que a resposta @Curt é exatamente o que eu estava procurando e, espero, outra pessoa.

No meu exemplo, tenho um ActionFilterAttribute que estava usando para atualizar os valores de um documento de patch json. Eu não sabia qual era o modelo T para o documento de correção, pois precisava serializá-lo e desserializá-lo para um JsonPatchDocument simples, modificá-lo e, em seguida, porque eu tinha o tipo, serializar e desserializar novamente para o tipo.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );
Leye Eltee Taiwo
fonte
-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}
user2008563
fonte
2
Você poderia apontar como essa resposta difere das outras respostas e onde essa solução é apropriada?
Klaus Gütter
-2

ainda mais limpo:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }
Harm Salomons
fonte
-2

Se você precisar converter objetos em tempo de execução sem saber o tipo de destino, poderá usar a reflexão para criar um conversor dinâmico.

Esta é uma versão simplificada (sem o método gerado em cache):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

então você pode chamá-lo:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
marianop
fonte