O valor do tipo 'T' não pode ser convertido em

146

É provável que seja uma pergunta iniciante, mas o Google surpreendentemente não forneceu uma resposta.

Eu tenho esse método bastante artificial

T HowToCast<T>(T t)
{
    if (typeof(T) == typeof(string))
    {
        T newT1 = "some text";
        T newT2 = (string)t;
    }

    return t;
}

Vindo de um background em C ++, esperava que isso funcionasse. No entanto, ele falha ao compilar com "Não é possível converter implicitamente o tipo 'T' em cadeia de caracteres" e "Não é possível converter o tipo 'T' em cadeia de caracteres" para as duas atribuições acima.

Ou estou fazendo algo conceitualmente errado ou apenas tenho a sintaxe errada. Por favor, ajude-me a resolver este.

Obrigado!

Alex
fonte
20
IMO, se você estiver verificando tipos no seu código genérico, provavelmente os genéricos não serão a solução correta para o seu problema.
Austin Salonen
A expressão typeof(T) == typeof(string)é resolvida no tempo de execução, não no tempo de compilação. Portanto, a seguinte linha no bloco é inválida.
Steve Guidi
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, Funciona apenas se o objeto implementar IConvertible. Doçura se isso acontecer.
Ouockk 17/08/19

Respostas:

285

Mesmo que seja dentro de um ifbloco, o compilador não sabe que Té string.
Portanto, não permite que você faça o elenco. (Pela mesma razão que você não pode lançar DateTimea string)

Você precisa converter para object, (para o qual qualquer um Tpode converter) e de lá para string(já que objectpode ser convertido para string).
Por exemplo:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
fonte
2
Isso funciona! Eu estou supondo que o segundo como também deve ser T newT2 = (T) (objeto) t; embora isso não seja um op.
Alex
2
Adição: modelos C ++ são essencialmente recortar e colar em tempo de compilação com os valores corretos substituídos. Em C #, o modelo genérico real (não uma "instanciação" dele) existe após a compilação e, portanto, deve (perdoar o trocadilho) ser genérico nos limites de tipo especificados.
(string) (objeto) t; não faz nada aqui, porém, poderia muito bem deixar isso para fora, o (string) (objeto) que é
Doggett
6
Por que não apenas transmitir usando "como string"? Por exemplo, isso compila bem (eu literalmente o compilei sem erros) quando userDefinedValue é do tipo T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko
1
Isso parece um erro dos designers do compilador. Se todo o T puder ser convertido explicitamente em objeto e todo o objeto puder ser convertido explicitamente na cadeia, deve existir uma regra transitiva em que T possa ser explicitamente convertido em cadeia. Se você executar incorretamente a conversão, ocorrerá um erro de runime.
P.Brian.Mackey
10

Ambas as linhas têm o mesmo problema

T newT1 = "some text";
T newT2 = (string)t;

O compilador não sabe que T é uma string e, portanto, não tem como saber como atribuir isso. Mas desde que você verificou, basta forçá-lo com

T newT1 = "some text" as T;
T newT2 = t; 

você não precisa converter ot, pois já é uma string, também precisa adicionar a restrição

where T : class
Doggett
fonte
2
Errado. Isso não será compilado. Veja minha resposta.
Slaks
2
Compila muito bem (com o local onde está, adicionado que alguns segundos depois que eu postei, pode ter perdido isso). Oops nm se esqueceu de mudar o elenco #
314 Doggett
2

Eu sei código semelhante que o OP postou nesta pergunta a partir de analisadores genéricos. De uma perspectiva de desempenho, você deve usar Unsafe.As<TFrom, TResult>(ref TFrom source), que pode ser encontrado no pacote System.Runtime.CompilerServices.Unsafe NuGet. Evita boxe para tipos de valor nesses cenários. Também acho que Unsafe.Asresulta em menos código de máquina produzido pelo JIT do que em duas vezes (usando (TResult) (object) actualString), mas ainda não o verifiquei.

public TResult ParseSomething<TResult>(ParseContext context)
{
    if (typeof(TResult) == typeof(string))
    {
        var token = context.ParseNextToken();
        string parsedString = token.ParseToDotnetString();
        return Unsafe.As<string, TResult>(ref parsedString);
    }
    else if (typeof(TResult) == typeof(int))
    {
        var token = context.ParseNextToken();
        int parsedInt32 = token.ParseToDotnetInt32();
        // This will not box which might be critical to performance
        return Unsafe.As<int, TResult>(ref parsedInt32); 
    }
    // other cases omitted for brevity's sake
}

Unsafe.As será substituído pelo JIT por instruções eficientes do código da máquina, como você pode ver no repo oficial do CoreFX:

Código-fonte de Unsafe.As

feO2x
fonte
1

Se você está verificando tipos explícitos, por que está declarando essas variáveis ​​como T's'?

T HowToCast<T>(T t)
{
    if (typeof(T) == typeof(string))
    {
        var newT1 = "some text";
        var newT2 = t;  //this builds but I'm not sure what it does under the hood.
        var newT3 = t.ToString();  //for sure the string you want.
    }

    return t;
}
Austin Salonen
fonte
6
A segunda linha cria uma variável do tipo T.
Slaks
Você pergunta por que verificar o tipo? Suponha que você tenha um tipo de campo base que armazena objectvalores, com tipos derivados que armazenam stringvalores. Suponha que esses campos também tenham um valor "DefaultIfNotProvided", portanto, é necessário verificar se o valor fornecido pelo usuário (que pode ser um objeto ou uma string ou mesmo uma primitiva numérica) é equivalente adefault(T) . Seqüência de caracteres pode ser tratada como um caso especial em que uma seqüência de caracteres em branco / espaço em branco é tratada da mesma forma que o padrão (T); portanto, você pode verificar se T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko
0

Você também receberá esse erro se tiver uma declaração genérica para sua classe e seu método. Por exemplo, o código mostrado abaixo fornece esse erro de compilação.

public class Foo <T> {

    T var;

    public <T> void doSomething(Class <T> cls) throws InstantiationException, IllegalAccessException {
        this.var = cls.newInstance();
    }

}

Este código é compilado (note T removido da declaração do método):

public class Foo <T> {

    T var;

    public void doSomething(Class <T> cls) throws InstantiationException, IllegalAccessException {
        this.var = cls.newInstance();
    }

}
John
fonte
-5

Mude esta linha:

if (typeof(T) == typeof(string))

Para esta linha:

if (t.GetType() == typeof(string))
Serch
fonte
1
Eles são iguais
bigworld12
Ambos são os mesmos ... apenas usando a palavra-chave language vs usando APIs da biblioteca de classes.
Abdulhameed