La valeur de type «T» ne peut pas être convertie en

146

C'est probablement une question novice, mais Google n'a étonnamment pas fourni de réponse.

J'ai cette méthode plutôt artificielle

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

    return t;
}

Venant d'un fond C ++, je m'attendais à ce que cela fonctionne. Cependant, il ne parvient pas à compiler avec "Impossible de convertir implicitement le type 'T' en chaîne" et "Impossible de convertir le type 'T' en chaîne" pour les deux affectations ci-dessus.

Soit je fais quelque chose de mal conceptuel, soit j'ai simplement la mauvaise syntaxe. S'il vous plaît, aidez-moi à régler celui-ci.

Je vous remercie!

Alex
la source
20
IMO, si vous vérifiez les types dans votre code générique, alors les génériques ne sont probablement pas la bonne solution à votre problème.
Austin Salonen
L'expression typeof(T) == typeof(string)est résolue au moment de l'exécution, pas au moment de la compilation. Ainsi, la ligne suivante du bloc n'est pas valide.
Steve Guidi
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, ne fonctionne que si l'objet implémente IConvertible. Douceur si c'est le cas.
ouflak

Réponses:

285

Même si c'est à l'intérieur d'un ifbloc, le compilateur ne sait pas que Tc'est string.
Par conséquent, il ne vous permet pas de lancer. (Pour la même raison que vous ne pouvez pas lancer DateTimeà string)

Vous devez lancer vers object, (vers lequel n'importe qui Tpeut lancer), et de là vers string(puisque objectpeut être lancé vers string).
Par exemple:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
SLaks
la source
2
Cela marche! Je suppose que le second devrait également être T newT2 = (T) (object) t; bien que ce soit un non op.
Alex
2
Ajout: les modèles C ++ sont essentiellement copier-coller au moment de la compilation avec les valeurs correctes remplacées. En C #, le modèle générique réel (pas une "instanciation" de celui-ci) existe après la compilation et doit donc (pardonnez le jeu de mots) être générique à travers les limites de type spécifiées.
(chaîne) (objet) t; ne fait rien ici cependant, pourrait aussi bien laisser cela de côté, la (chaîne) (objet) qui est
Doggett
6
Pourquoi ne pas simplement lancer en utilisant "as string"? Par exemple, cela compile bien (je viens de le compiler littéralement sans erreurs) lorsque userDefinedValue est de type T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko
1
Cela ressemble à une erreur des concepteurs du compilateur. Si tout T peut être explicitement converti en objet et que tout objet peut être explicitement converti en chaîne, alors il devrait exister une règle transitive selon laquelle T peut être explicitement converti en chaîne. Si vous effectuez une conversion incorrecte, une erreur d'exécution doit se produire.
P.Brian.Mackey
10

Les deux lignes ont le même problème

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

Le compilateur ne sait pas que T est une chaîne et n'a donc aucun moyen de savoir comment l'affecter. Mais puisque vous avez vérifié, vous pouvez simplement le forcer avec

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

vous n'avez pas besoin de transtyper le t car c'est déjà une chaîne, vous devez également ajouter la contrainte

where T : class
Doggett
la source
2
Faux. Cela ne compilera pas. Voyez ma réponse.
SLaks
2
Compile très bien (avec le où c'est, ajouté que quelques secondes après avoir posté, cela aurait pu manquer). Oups nm a oublié de changer la distribution
Doggett
2

Je connais un code similaire que l'OP a publié dans cette question à partir d'analyseurs génériques. Du point de vue des performances, vous devez utiliser Unsafe.As<TFrom, TResult>(ref TFrom source), qui se trouve dans le package NuGet System.Runtime.CompilerServices.Unsafe . Cela évite de boxer pour les types valeur dans ces scénarios. Je pense aussi que cela Unsafe.Asentraîne moins de code machine produit par le JIT que de lancer deux fois (en utilisant (TResult) (object) actualString), mais je ne l'ai pas vérifié.

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 sera remplacé par le JIT avec des instructions de code machine efficaces, comme vous pouvez le voir dans le repo officiel CoreFX:

Code source de Unsafe.As

feO2x
la source
1

Si vous recherchez des types explicites, pourquoi déclarez-vous ces variables comme Tdes 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
la source
6
La deuxième ligne crée une variable de type T.
SLaks
Vous demandez pourquoi vérifier le type? Supposons que vous ayez un type de champ de base qui stocke les objectvaleurs, avec des types dérivés qui stockent les stringvaleurs. Supposons que ces champs aient également une valeur "DefaultIfNotProvided", vous devez donc vérifier si la valeur fournie par l'utilisateur (qui peut être un objet ou une chaîne ou même une primitive numérique) est équivalente à default(T). La chaîne peut être traitée comme un cas spécial où une chaîne vide / d'espacement est traitée de la même manière que la valeur par défaut (T), vous pouvez donc vérifier si T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko du
0

Vous obtiendrez également cette erreur si vous avez une déclaration générique pour votre classe et votre méthode. Par exemple, le code ci-dessous donne cette erreur de compilation.

public class Foo <T> {

    T var;

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

}

Ce code compile (note T retiré de la déclaration de méthode):

public class Foo <T> {

    T var;

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

}
John
la source
-5

Changez cette ligne:

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

Pour cette ligne:

if (t.GetType() == typeof(string))
Serch
la source
1
ils sont les mêmes
bigworld12
Les deux sont identiques ... il suffit d'utiliser le mot-clé de langue plutôt que d'utiliser les API de bibliothèque de classes.
Abdulhameed