El valor del tipo 'T' no se puede convertir a

146

Esta es probablemente una pregunta novata, pero Google sorprendentemente no proporcionó una respuesta.

Tengo este método bastante artificial

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

    return t;
}

Viniendo de un fondo de C ++, esperaba que esto funcionara. Sin embargo, no se puede compilar con "No se puede convertir implícitamente el tipo 'T' a cadena" y "No se puede convertir el tipo 'T' a cadena" para las dos asignaciones anteriores.

Estoy haciendo algo conceptualmente incorrecto o simplemente tengo la sintaxis incorrecta. Por favor, ayúdame a resolver esto.

¡Gracias!

Alex
fuente
20
OMI, si está verificando tipos en su código genérico, entonces los genéricos probablemente no sean la solución correcta para su problema.
Austin Salonen, el
La expresión typeof(T) == typeof(string)se resuelve en tiempo de ejecución, no en tiempo de compilación. Por lo tanto, la siguiente línea en el bloque no es válida.
Steve Guidi
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, solo funciona si el objeto implementa IConvertible. Aunque dulzura si lo hace.
ouflak

Respuestas:

285

A pesar de que hay dentro de un ifbloque, el compilador no sabe que Tes string.
Por lo tanto, no te permite lanzar. (Por la misma razón que no se puede echar DateTimea string)

Debe lanzar a object, (a lo que cualquiera Tpuede lanzar), y de allí a string(ya que objectpuede lanzar a string).
Por ejemplo:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
fuente
2
¡Esto funciona! Supongo que el segundo me gusta también debería ser T newT2 = (T) (objeto) t; aunque eso es un no op.
Alex
2
Adición: las plantillas C ++ son esencialmente cortar y pegar en tiempo de compilación con los valores correctos sustituidos. En C #, la plantilla genérica real (no una "creación de instancias") existe después de la compilación y, por lo tanto, (perdón por el juego de palabras) debe ser genérica a través de los límites de tipo especificados.
(cadena) (objeto) t; no hace nada aquí, bien podría dejar eso fuera, el (string) (objeto) que es
Doggett
66
¿Por qué no simplemente emitir usando "como cadena"? Por ejemplo, esto se compila bien (literalmente lo compilé sin errores) cuando userDefinedValue es de tipo T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko
1
Esto se siente como un error por parte de los diseñadores del compilador. Si toda la T se puede convertir explícitamente en un objeto y todo el objeto se puede convertir explícitamente en una cadena, entonces debería existir una regla transitiva de que T se puede convertir explícitamente en una cadena. Si realiza la conversión de forma incorrecta, se debe producir un error de ejecución.
P.Brian.Mackey
10

Ambas líneas tienen el mismo problema.

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

El compilador no sabe que T es una cadena y, por lo tanto, no tiene forma de saber cómo asignar eso. Pero como lo has verificado, puedes forzarlo con

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

no necesita emitir la t ya que ya es una cadena, también necesita agregar la restricción

where T : class
Doggett
fuente
2
Incorrecto. Esto no se compilará. Mira mi respuesta.
SLaks el
2
Compila muy bien (con el lugar donde está, agregó que unos segundos después de que publiqué, podría haberlo pasado por alto). Ups nm olvidó cambiar el elenco
Doggett
2

Sé un código similar al OP publicado en esta pregunta de analizadores genéricos. Desde una perspectiva de rendimiento, debe usar Unsafe.As<TFrom, TResult>(ref TFrom source), que se puede encontrar en el paquete System.Runtime.CompilerServices.Unsafe NuGet. Evita el boxeo para los tipos de valor en estos escenarios. También creo que eso Unsafe.Asproduce menos código de máquina producido por el JIT que lanzar dos veces (usando (TResult) (object) actualString), pero no lo he comprobado.

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á reemplazado por el JIT con instrucciones de código de máquina eficientes, como puede ver en el repositorio oficial de CoreFX:

Código fuente de inseguros.

feO2x
fuente
1

Si está buscando tipos explícitos, ¿por qué declara esas variables 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
fuente
66
La segunda línea crea una variable de tipo T.
SLaks el
Usted pregunta por qué verificar el tipo? Supongamos que tiene un tipo de campo base que almacena objectvalores, con tipos derivados que almacenan stringvalores. Supongamos que estos campos también tienen un valor "DefaultIfNotProvided", por lo que debe verificar si el valor proporcionado por el usuario (que podría ser un objeto o una cadena o incluso una primitiva numérica) es equivalente a default(T). La cadena se puede tratar como un caso especial donde una cadena vacía / espacio en blanco se trata de la misma manera que la predeterminada (T), por lo que es posible que desee verificar si T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko
0

También recibirá este error si tiene una declaración genérica tanto para su clase como para su método. Por ejemplo, el código que se muestra a continuación da este error de compilación.

public class Foo <T> {

    T var;

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

}

Este código se compila (nota T eliminada de la declaración del método):

public class Foo <T> {

    T var;

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

}
Juan
fuente
-5

Cambia esta línea:

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

Para esta línea:

if (t.GetType() == typeof(string))
Serch
fuente
1
son lo mismo
bigworld12
Ambos son lo mismo ... solo usando la palabra clave del lenguaje frente a las API de la biblioteca de clases.
Abdulhameed