Nie można niejawnie przekonwertować typu „Int” na „T”

90

Mogę zadzwonić Get<int>(Stat);lubGet<string>(Name);

Ale podczas kompilacji otrzymuję:

Nie można niejawnie przekonwertować typu „int” na „T”

i to samo dla 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.
źródło
6
Prawdopodobnie myślisz, że blok if sprawdził, czy T jest int, więc wewnątrz bloku wiesz, że T jest int i powinieneś być w stanie niejawnie przekonwertować int na T. Ale kompilator nie jest zaprojektowany do podążania za tym rozumowaniem, po prostu wie że generalnie T nie pochodzi od int, więc nie zezwala na niejawną konwersję. (A jeśli kompilator to obsługiwał, weryfikator nie byłby
możliwy

Odpowiedzi:

132

Za każdym razem, gdy włączasz typ w rodzaju ogólnym , prawie na pewno robisz coś złego . Generyczne powinny być generyczne ; powinny działać identycznie, całkowicie niezależnie od typu .

Jeśli T może mieć tylko wartość int lub string, nie pisz swojego kodu w ten sposób w ogóle. Napisz dwie metody, jedną zwracającą int, a drugą zwracającą ciąg.

Eric Lippert
źródło
1
Uzyskaj <Samochód>, gdzie samochód implementuje IConvertible, który spowoduje uszkodzenie. Kiedy ktoś zobaczy, że masz ogólną metodę, założy, że może przekazać wszystko, co implementuje IConvertible.
Tjaart
10
Mogę tylko częściowo się z Tobą zgodzić, @Eric. Mam sytuację, w której muszę parsować tablice zapisane w tagach XML, Problem w tym, że specyfikacja, za którą odpowiada dokument XML (w moim przypadku COLLADA) mówi, że takie tablice mogą być nie tylko float, int i bool, ale także niektóre niestandardowe typy. Jednak w przypadku, gdy otrzymasz float [] (tagi tablicowe zawierają typ przechowywanych danych w ich nazwach: float_array przechowuje zmiennoprzecinkowe), musisz przeanalizować ciąg jako tablicę floats, co wymaga użycia jakiegoś IFormatProvider). Oczywiście nie mogę użyć "T.Parse (...)". Więc dla małego podzbioru przypadków potrzebuję takiego przełączania.
rbaleksandar
1
Ta odpowiedź utrzyma cię z dala od króliczej nory. Chciałem stworzyć funkcję ogólną dla int, int?, bool, bool?, stringi wydawało się, że jest to niemożliwe.
Jess
To sprawia, że ​​przełączenie na ogólny typ wyliczeniowy jest praktyczne.
David A. Grey
1
Nie chciałem tego używać jako odpowiedzi. Ale on ma rację. Chciałem sprawdzić typ i, jeśli jest konkretny, ustawić na nim właściwość. Rozwiązaniem było stworzenie metody wymagającej silnie wpisanego parametru.
Matt Dawdy,
141

Powinieneś móc po prostu użyć Convert.ChangeType()zamiast niestandardowego kodu:

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}
Rozbite szkło
źródło
21
Co powiesz nareturn (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
źródło
2
return (T) t;ponieważ nie są konieczne żadne sprawdzenia zerowe.
BoltClock
To powyżej nie skompiluje się dla mnie. T musi być typem referencyjnym dla „as” do kompilacji.
Robert Schmidt
9

ChangeTypeto prawdopodobnie najlepsza opcja. Moje rozwiązanie jest podobne do tego dostarczonego przez BrokenGlass z odrobiną logiki 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
źródło
Mój przypadek użycia to konkretna klasa wywodząca się z ogólnej klasy abstrakcyjnej. Klasa jest oznaczona jako abstrakcyjna, ponieważ definiuje metodę abstrakcyjną, która działa na generycznym prywatnym elemencie członkowskim klasy bazowej. Rodzaj ogólny używa ograniczenia wyliczeniowego C # 7,3 dla jego typu ogólnego. Właśnie pomyślnie ukończyłem test i działa dokładnie tak, jak się spodziewałem.
David A. Gray,
8

Spróbuj tego:

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
źródło
Dziękuję, że pomogło, moja potrzeba jest inna. Piszę metodę próbną dla istniejącej metody statycznej, aby móc ją przetestować. Używając tego osherove.com/blog/2012/7/8/…
Esen
8

Właściwie możesz po prostu przekonwertować go na, objecta następnie na T.

T var = (T)(object)42;

Przykład dla 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
    }
}

Czasami takie zachowanie jest pożądane. Na przykład podczas implementowania lub zastępowania metody ogólnej z klasy bazowej lub interfejsu i chcesz dodać różne funkcje w zależności od Ttypu.

GregorMohorko
źródło
6

Biorąc pod uwagę, że logika @BrokenGlass ( Convert.ChangeType) nie obsługuje typu GUID.

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

Błąd : nieprawidłowe przesyłanie z „System.String” na „System.Guid”.

Zamiast tego użyj poniższej logiki TypeDescriptor.GetConverter, dodając System.ComponentModelprzestrzeń nazw.

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

Przeczytaj to .

Prasad Kanaparthi
źródło
3

Wygląda na to, że potrzebujesz TypeConverter, zobacz ten wpis na blogu .

Ian Mercer
źródło
0

Możesz po prostu rzucić jak poniżej,

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
źródło