Nilai tipe 'T' tidak dapat dikonversi ke

146

Ini mungkin pertanyaan pemula, tetapi secara mengejutkan google tidak memberikan jawaban.

Saya memiliki metode yang agak buatan ini

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

    return t;
}

Berasal dari latar belakang C ++ saya berharap ini berfungsi. Namun, gagal mengkompilasi dengan "Tidak dapat secara implisit mengonversi tipe 'T' ke string" dan "Tidak dapat mengonversi tipe 'T' ke string" untuk kedua tugas di atas.

Saya melakukan sesuatu yang secara konseptual salah atau hanya memiliki sintaks yang salah. Tolong bantu saya menyelesaikan masalah ini.

Terima kasih!

Alex
sumber
20
IMO, jika Anda memeriksa jenis dalam kode obat generik, maka obat generik mungkin bukan solusi yang tepat untuk masalah Anda.
Austin Salonen
Ekspresi typeof(T) == typeof(string)diselesaikan pada saat runtime, bukan waktu kompilasi. Dengan demikian, baris berikut dalam blok tidak valid.
Steve Guidi
8
(T) Convert.ChangeType (newT1, typeof (T))
vsapiha
2
@vsapiha, Hanya berfungsi jika objek mengimplementasikan IConvertible. Manisnya jika itu terjadi.
ouflak

Jawaban:

285

Meskipun itu dalam sebuah ifblok, compiler tidak tahu bahwa Tadalah string.
Karena itu, ia tidak membiarkan Anda bermain. (Untuk alasan yang sama yang Anda tidak bisa melemparkan DateTimeke string)

Anda perlu dilemparkan ke object, (yang Tdapat dilemparkan ke mana pun ), dan dari sana ke string(karena objectdapat dilemparkan ke string).
Sebagai contoh:

T newT1 = (T)(object)"some text";
string newT2 = (string)(object)t;
Slaks
sumber
2
Ini bekerja! Saya menduga seperti kedua juga harus T newT2 = (T) (objek) t; meskipun itu bukan op.
Alex
2
Tambahan: C ++ templat pada dasarnya cut-and-paste pada waktu kompilasi dengan nilai yang benar diganti. Dalam C # templat generik aktual (bukan "instantiasi" itu) ada setelah kompilasi dan karenanya (maaf pun) harus generik melintasi batas tipe yang ditentukan.
(string) (objek) t; tidak melakukan apa pun di sini, mungkin juga meninggalkan itu, (string) (objek) yaitu
Doggett
6
Mengapa tidak hanya menggunakan "sebagai string"? Sebagai contoh, ini mengkompilasi dengan baik (saya benar-benar hanya mengkompilasinya tanpa kesalahan) ketika userDefinedValue adalah tipe T:var isBlank = (userDefinedValue is string) && String.IsNullOrWhiteSpace(userDefinedValue as string);
Triynko
1
Ini terasa seperti kesalahan oleh desainer kompiler. Jika semua T dapat secara eksplisit dilemparkan ke objek dan semua objek dapat secara eksplisit dilemparkan ke string maka harus ada aturan transitif yang T dapat secara eksplisit dilemparkan ke string. Jika Anda salah melakukan gips maka kesalahan runime akan terjadi.
P.Brian.Mackey
10

Kedua jalur memiliki masalah yang sama

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

Kompiler tidak tahu bahwa T adalah sebuah string dan karenanya tidak memiliki cara untuk mengetahui cara menetapkan itu. Tetapi karena Anda memeriksa Anda bisa memaksanya

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

Anda tidak perlu membuang t karena ini sudah berupa string, juga perlu menambahkan batasan

where T : class
Doggett
sumber
2
Salah. Ini tidak akan dikompilasi. Lihat jawaban saya.
SLaks
2
Mengkompilasi dengan baik (dengan di mana itu, menambahkan bahwa beberapa detik setelah saya diposting, mungkin telah melewatkan itu). Ups nm lupa ganti gips
Doggett
2

Saya tahu kode serupa yang OP posting dalam pertanyaan ini dari parser generik. Dari perspektif kinerja, Anda harus menggunakan Unsafe.As<TFrom, TResult>(ref TFrom source), yang dapat ditemukan dalam paket System.Runtime.CompilerServices.Unsafe NuGet. Ini menghindari tinju untuk tipe nilai dalam skenario ini. Saya juga berpikir bahwa Unsafe.Asmenghasilkan lebih sedikit kode mesin yang dihasilkan oleh JIT daripada melakukan casting dua kali (menggunakan (TResult) (object) actualString), tapi saya belum memeriksanya.

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 akan digantikan oleh JIT dengan instruksi kode mesin yang efisien, seperti yang dapat Anda lihat di repo CoreFX resmi:

Kode Sumber Unsafe.As

feO2x
sumber
1

Jika Anda memeriksa tipe eksplisit, mengapa Anda mendeklarasikan variabel tersebut sebagai variabel T?

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
sumber
6
Baris kedua menciptakan variabel tipe T.
SLaks
Anda bertanya mengapa memeriksa tipenya? Misalkan Anda memiliki tipe bidang dasar yang menyimpan objectnilai, dengan tipe turunan yang menyimpan stringnilai. Misalkan bidang ini juga memiliki nilai "DefaultIfNotProvided", jadi Anda perlu memeriksa apakah nilai yang disediakan pengguna (yang bisa berupa objek atau string atau bahkan primitif numerik) setara dengan default(T). String dapat diperlakukan sebagai kasus khusus di mana string kosong / spasi diperlakukan sama dengan default (T), jadi Anda mungkin ingin memeriksa apakah T userValue; var isBlank = (userValue is string) && String.IsNullOrWhitespace(userValue as string);.
Triynko
0

Anda juga akan mendapatkan kesalahan ini jika Anda memiliki deklarasi generik untuk kelas Anda dan metode Anda. Misalnya kode yang ditunjukkan di bawah ini memberikan kesalahan kompilasi ini.

public class Foo <T> {

    T var;

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

}

Kode ini dikompilasi (catatan T dihapus dari deklarasi metode):

public class Foo <T> {

    T var;

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

}
John
sumber
-5

Ubah baris ini:

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

Untuk baris ini:

if (t.GetType() == typeof(string))
Serch
sumber
1
mereka sama
bigworld12
Keduanya sama ... hanya menggunakan kata kunci bahasa vs menggunakan API perpustakaan kelas.
Abdulhameed