Tidak dapat secara implisit mengonversi jenis 'Int' menjadi 'T'

90

Saya dapat menelepon Get<int>(Stat);atauGet<string>(Name);

Tetapi ketika menyusun saya mendapatkan:

Tidak dapat secara implisit mengubah tipe 'int' menjadi 'T'

dan hal yang sama untuk 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.
sumber
6
Anda mungkin berpikir jika blok if memeriksa bahwa T adalah int, jadi di dalam blok, Anda tahu T adalah int dan Anda harus dapat secara implisit mengonversi int ke T. Tetapi kompilator tidak dirancang untuk mengikuti alasan itu, ia hanya tahu yang umumnya T tidak berasal dari int, jadi tidak memungkinkan konversi implisit. (Dan jika kompiler mendukungnya, pemverifikasi tidak akan melakukannya, sehingga perakitan yang dikompilasi tidak akan dapat diverifikasi.)
JGWeissman

Jawaban:

132

Setiap kali Anda menemukan diri Anda beralih pada tipe generik, Anda hampir pasti melakukan sesuatu yang salah . Generik harus generik ; mereka harus beroperasi secara identik sepenuhnya terlepas dari jenisnya .

Jika T hanya bisa berupa int atau string maka jangan menulis kode Anda dengan cara ini sama sekali. Tulis dua metode, satu yang mengembalikan int dan satu lagi yang mengembalikan string.

Eric Lippert
sumber
1
Dapatkan <Car> di mana mobil mengimplementasikan IConvertible akan menyebabkan kerusakan. Ketika seseorang melihat Anda memiliki metode umum, mereka akan berasumsi bahwa mereka dapat meneruskan apa pun yang mengimplementasikan IConvertible.
Tjaart
10
Saya hanya dapat sebagian setuju dengan Anda, @Eric. Saya memiliki situasi di mana saya harus mengurai array yang disimpan dalam tag XML. Masalahnya adalah spesifikasi yang diikuti dokumen XML (COLLADA dalam kasus saya) mengatakan bahwa array seperti itu dapat tidak hanya float, int dan bool tetapi juga beberapa tipe kustom. Namun jika Anda mendapatkan float [] (tag array berisi tipe data yang disimpan dalam namanya: float_array menyimpan floats) Anda perlu mengurai string sebagai array dari floats, yang memerlukan beberapa IFormatProvider untuk digunakan). Saya jelas tidak dapat menggunakan "T.Parse (...)". Jadi untuk sebagian kecil kasus saya perlu menggunakan pengalihan seperti itu.
rbaleksandar
1
Jawaban ini akan menjauhkan Anda dari lubang kelinci. Saya ingin membuat suatu fungsi menjadi generik int, int?, bool, bool?, string, dan sepertinya tidak mungkin.
Jess
Ini membuat saklar pada tipe enumerasi generik menjadi praktis.
David A. Gray
1
Saya tidak ingin menggunakan ini sebagai jawabannya. Tapi dia benar. Saya ingin memeriksa tipe dan, jika spesifik, menetapkan properti di atasnya. Solusinya adalah membuat metode yang mengambil parameter yang diketik dengan kuat.
Matt Dawdy
141

Anda seharusnya dapat menggunakan Convert.ChangeType()alih-alih kode khusus Anda:

public T Get<T>(Stats type) where T : IConvertible
{
    return (T) Convert.ChangeType(PlayerStats[type], typeof(T));
}
Gelas pecah
sumber
21
Bagaimana denganreturn (T)(object)PlayerStats[type];
maks.
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
sumber
2
return (T) t;karena tidak diperlukan pemeriksaan nol.
BoltClock
Ini di atas tidak akan dikompilasi untuk saya. T perlu menjadi jenis referensi untuk "sebagai" untuk dikompilasi.
Robert Schmidt
9

ChangeTypemungkin adalah pilihan terbaik Anda. Solusi saya mirip dengan yang disediakan oleh BrokenGlass dengan sedikit logika coba tangkap.

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
sumber
Kasus penggunaan saya adalah kelas beton yang diturunkan dari kelas abstrak generik. Kelas ditandai abstrak karena mendefinisikan metode abstrak yang beroperasi pada anggota pribadi umum dari kelas dasar. Generik menggunakan batasan Enum C # 7.3 pada tipe generiknya. Saya baru saja berhasil menyelesaikan tes, dan itu bekerja persis seperti yang saya harapkan.
David A. Gray
8

Coba ini:

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
sumber
Terima kasih ini membantu, kebutuhan saya berbeda. Saya menulis metode tiruan untuk metode statis yang ada sehingga saya dapat mengujinya. Menggunakan osherove.com/blog/2012/7/8/…
Esen
8

Sebenarnya, Anda dapat mengubahnya menjadi objectdan kemudian menjadi T.

T var = (T)(object)42;

Contoh untuk 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
    }
}

Terkadang, perilaku ini diinginkan. Misalnya, saat mengimplementasikan atau mengganti metode umum dari kelas atau antarmuka dasar dan Anda ingin menambahkan beberapa fungsi berbeda berdasarkan Ttipenya.

GregorMohorko
sumber
6

Mengingat logika @BrokenGlass ( Convert.ChangeType) tidak mendukung tipe GUID.

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

Kesalahan : Transmisi tidak valid dari 'System.String' ke 'System.Guid'.

Sebagai gantinya, gunakan logika di bawah ini TypeDescriptor.GetConverterdengan menambahkan System.ComponentModelnamespace.

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

Baca ini .

Prasad Kanaparthi
sumber
3

Sepertinya Anda memerlukan TypeConverter, lihat entri blog ini .

Ian Mercer
sumber
0

Anda cukup melakukan cast seperti di bawah ini,

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
sumber