Impossible de convertir implicitement le type «Int» en «T»

90

Je peux appeler Get<int>(Stat);ouGet<string>(Name);

Mais lors de la compilation, j'obtiens:

Impossible de convertir implicitement le type 'int' en 'T'

et la même chose pour string.

public T Get<T>(Stats type) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        int t = Convert.ToInt16(PlayerStats[type]);
        return t;
    }
    if (typeof(T) == typeof(string))
    {
        string t = PlayerStats[type].ToString();
        return t;
    }
}
David W
la source
6
Vous pensez probablement que le bloc if a vérifié que T est int, donc dans le bloc, vous savez que T est int et vous devriez être capable de convertir implicitement int en T.Mais le compilateur n'est pas conçu pour suivre ce raisonnement, il sait juste que généralement T ne dérive pas de int, donc il n'autorise pas la conversion implicite. (Et si le compilateur le supportait, le vérificateur ne le ferait pas, donc l'assemblage compilé serait invérifiable.)
JGWeissman

Réponses:

132

Chaque fois que vous vous trouvez en train d'activer un type dans un générique, vous faites certainement quelque chose de mal . Les génériques doivent être génériques ; ils doivent fonctionner de manière identique complètement indépendamment du type .

Si T ne peut être qu'un entier ou une chaîne, n'écrivez pas du tout votre code de cette façon en premier lieu. Écrivez deux méthodes, une qui renvoie un int et une qui renvoie une chaîne.

Eric Lippert
la source
1
Obtenez <Car> où la voiture implémente IConvertible provoquera une casse. Quand quelqu'un voit que vous avez une méthode générique, il supposera qu'il peut transmettre tout ce qui implémente IConvertible.
Tjaart
10
Je ne peux que partiellement être d'accord avec vous, @Eric .J'ai une situation où je dois analyser des tableaux stockés dans des balises XML.Le problème est que la spécification que le document XML suit (COLLADA dans mon cas) dit que de tels tableaux peuvent être non seulement float, int et bool, mais aussi certains types personnalisés.Cependant, si vous obtenez un float [] (les balises de tableau contiennent le type de données stockées dans leurs noms: float_array stocke des floats), vous devez analyser la chaîne comme un floats, qui nécessite l'utilisation de IFormatProvider) .Je ne peux évidemment pas utiliser "T.Parse (...)". Donc, pour un petit sous-ensemble de cas, je dois utiliser une telle commutation.
rbaleksandar
1
Cette réponse vous gardera hors du terrier du lapin. Je voulais créer une fonction générique pour int, int?, bool, bool?, string, et c'était apparemment impossible.
Jess
Cela rend pratique un commutateur sur un type énuméré générique.
David A. Gray
1
Je ne voulais pas utiliser cela comme réponse. Mais il a raison. Je voulais vérifier le type et, si c'est spécifique, définir une propriété dessus. La solution était de créer une méthode qui prenait un paramètre fortement typé.
Matt Dawdy
141

Vous devriez pouvoir utiliser simplement à la Convert.ChangeType()place de votre code personnalisé:

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}
Verre brisé
la source
21
Que diriez-vousreturn (T)(object)PlayerStats[type];
maxp
11
public T Get<T>(Stats type ) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        int t = Convert.ToInt16(PlayerStats[type]);
        return (T)t;
    }
    if (typeof(T) == typeof(string))
    {
        string t = PlayerStats[type].ToString();
        return (T)t;
    }
}
Reza ArabQaeni
la source
2
return (T) t;car aucune vérification nulle n'est nécessaire.
BoltClock
Ce ci-dessus ne compilera pas pour moi. T doit être un type de référence pour "as" à compiler.
Robert Schmidt
9

ChangeTypeest probablement votre meilleure option. Ma solution est similaire à celle fournie par BrokenGlass avec un peu de logique try catch.

static void Main(string[] args)
{
    object number = "1";
    bool hasConverted;
    var convertedValue = DoConvert<int>(number, out hasConverted);

    Console.WriteLine(hasConverted);
    Console.WriteLine(convertedValue);
}

public static TConvertType DoConvert<TConvertType>(object convertValue, out bool hasConverted)
{
    hasConverted = false;
    var converted = default(TConvertType);
    try
    {
        converted = (TConvertType) 
            Convert.ChangeType(convertValue, typeof(TConvertType));
        hasConverted = true;
    }
    catch (InvalidCastException)
    {
    }
    catch (ArgumentNullException)
    {
    }
    catch (FormatException)
    {
    }
    catch (OverflowException)
    {
    }

    return converted;
}
Michael Ciba
la source
Mon cas d'utilisation est une classe concrète dérivée d'une classe abstraite générique. La classe est marquée abstraite car elle définit une méthode abstraite qui opère sur le membre privé générique de la classe de base. Le générique utilise la contrainte C # 7.3 Enum sur son type générique. Je viens de terminer avec succès un test et cela fonctionne exactement comme je l'espérais.
David A. Gray
8

Essaye ça:

public T Get<T>(Stats type ) where T : IConvertible
{
    if (typeof(T) == typeof(int))
    {
        return (T)(object)Convert.ToInt16(PlayerStats[type]);

    }
    if (typeof(T) == typeof(string))
    {

        return (T)(object)PlayerStats[type];
    }
}
Michael Kalinovich
la source
Merci pour cette aide, mon besoin est différent. J'écris une méthode fictive pour une méthode statique existante afin de pouvoir la tester. Utilisation de ce osherove.com/blog/2012/7/8/…
Esen
8

En fait, vous pouvez simplement le convertir en object, puis en T.

T var = (T)(object)42;

Un exemple pour bool:

public class Program
{
    public static T Foo<T>()
    {
        if(typeof(T) == typeof(bool)) {
            return (T)(object)true;
        }

        return default(T);
    }

    public static void Main()
    {
        bool boolValue = Foo<bool>(); // == true
        string stringValue = Foo<string>(); // == null
    }
}

Parfois, ce comportement est souhaitable. Par exemple, lorsque vous implémentez ou remplacez une méthode générique à partir d'une classe ou d'une interface de base et que vous souhaitez ajouter des fonctionnalités différentes en fonction du Ttype.

GregorMohorko
la source
6

Considérant que @BrokenGlass logic ( Convert.ChangeType) ne prend pas en charge le type GUID.

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}

Erreur : conversion non valide de «System.String» vers «System.Guid».

Au lieu de cela, utilisez la logique ci-dessous TypeDescriptor.GetConverteren ajoutant un System.ComponentModelespace de noms.

public T Get<T>(Stats type) where T : IConvertible
{
    (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromInvariantString(PlayerStats[type])
}

Lisez ceci .

Prasad Kanaparthi
la source
3

Il semble que vous ayez besoin d'un TypeConverter, voir cette entrée de blog .

Ian Mercer
la source
0

Vous pouvez simplement lancer comme ci-dessous,

public T Get<T>(Stats type) where T : IConvertible
{
  if (typeof(T) == typeof(int))
  {
    int t = Convert.ToInt16(PlayerStats[type]);
    return t as T;
  }
 if (typeof(T) == typeof(string))
 {
    string t = PlayerStats[type].ToString();
    return t as T;
 }
}
Vijayanath Viswanathan
la source