Bagaimana saya bisa mengembalikan NULL dari metode generik dalam C #?

546

Saya punya metode generik dengan kode (dummy) ini (ya saya sadar IList memiliki predikat, tetapi kode saya tidak menggunakan IList tetapi beberapa koleksi lain, toh ini tidak relevan untuk pertanyaan ...)

static T FindThing<T>(IList collection, int id) where T : IThing, new()
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;  // ERROR: Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead.
}

Ini memberi saya kesalahan build

"Tidak dapat mengonversi nol untuk mengetik parameter 'T' karena itu bisa menjadi tipe nilai. Sebagai gantinya gunakan 'default (T)'."

Bisakah saya menghindari kesalahan ini?

edosoft
sumber
Apakah tipe referensi yang dapat dibatalkan (dalam C # 8) menjadi solusi yang lebih baik untuk ini sekarang? docs.microsoft.com/en-us/dotnet/csharp/nullable-references Kembali nullterlepas dari apakah Tini Objectatau intatau char.
Alexander - Pasang kembali Monica

Jawaban:

969

Dua pilihan:

  • Kembali default(T)yang berarti Anda akan kembali nulljika T adalah tipe referensi (atau tipe nilai yang dapat dibatalkan), 0untuk int, '\0'untuk char, dll. ( Tabel nilai default (C # Referensi) )
  • Batasi T untuk menjadi tipe referensi dengan where T : classkendala dan kemudian kembali nullseperti biasa
Jon Skeet
sumber
3
Bagaimana jika tipe pengembalian saya adalah enum, bukan kelas? Saya tidak dapat menentukan T: enum :(
Justin
1
Dalam. NET enum adalah pembungkus yang sangat tipis (dan agak bocor) di sekitar tipe integer. Konvensi ini menggunakan nol untuk nilai enum "default" Anda.
Mike Chamberlain
27
Saya pikir masalah dengan ini adalah bahwa jika Anda menggunakan metode generik ini untuk mengatakan, konversikan objek Database dari DbNull ke Int dan mengembalikan default (T) di mana T adalah int, itu akan mengembalikan 0. Jika angka ini adalah sebenarnya bermakna, maka Anda akan memberikan data buruk jika bidang itu nol. Atau contoh yang lebih baik adalah DateTime. Jika bidang itu seperti "DateClosed" dan dikembalikan sebagai nol karena dan akun masih terbuka, sebenarnya akan default (DateTime) ke 1/1/0000, menyiratkan bahwa akun ditutup sebelum komputer ditemukan.
Sinaesthetic
21
@Sinesthetic: Jadi Anda akan pindah ke Nullable<int>atau Nullable<DateTime>sebaliknya. Jika Anda menggunakan jenis yang tidak dapat dibatalkan dan harus mewakili nilai nol, Anda hanya meminta masalah.
Jon Skeet
1
Saya setuju, saya hanya ingin membicarakannya. Saya pikir apa yang saya lakukan lebih seperti MyMethod <T> (); untuk menganggap itu adalah tipe yang tidak dapat dibatalkan dan MyMethod <T?> (); untuk menganggap itu adalah tipe nullable. Jadi dalam skenario saya, saya bisa menggunakan variabel temp untuk menangkap nol dan pergi dari sana.
Sinaesthetic
84
return default(T);
Ricardo Villamil
sumber
Tautan ini: msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx harus menjelaskan alasannya.
Harper Shelby
1
Sialan, aku akan menghemat banyak waktu seandainya aku tahu tentang kata kunci ini - terima kasih Ricardo!
Ana Betts
1
Saya terkejut ini tidak mendapatkan suara lebih banyak karena kata kunci 'default' adalah solusi yang lebih komprehensif, memungkinkan penggunaan tipe non-referensi dalam hubungannya dengan tipe numerik dan struct. Sementara jawaban yang diterima memecahkan masalah (dan memang sangat membantu), jawaban yang lebih baik adalah bagaimana membatasi tipe pengembalian ke tipe nullable / referensi.
Steve Jackson
33

Anda bisa menyesuaikan batasan Anda:

where T : class

Kemudian mengembalikan nol diizinkan.

TheSoftwareJedi
sumber
Terima kasih. Saya tidak dapat memilih 2 jawaban sebagai solusi yang diterima, jadi saya memilih John Skeet karena jawabannya memiliki dua solusi.
edosoft
@ Molig tergantung pada kebutuhan Anda. Mungkin proyek mereka memang membutuhkannya IDisposable. Ya, sebagian besar waktu tidak harus begitu. System.Stringtidak menerapkan IDisposable, misalnya. Penjawab seharusnya menjelaskan itu, tetapi itu tidak membuat jawaban salah. :)
ahwm
@Migol Saya tidak tahu mengapa saya punya IDisposable di sana. Dihapus.
TheSoftwareJedi
13

Tambahkan batasan kelas sebagai kendala pertama untuk tipe generik Anda.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()
Min
sumber
Terima kasih. Saya tidak dapat memilih 2 jawaban sebagai solusi yang diterima, jadi saya memilih John Skeet karena jawabannya memiliki dua solusi.
edosoft
7
  1. Jika Anda memiliki objek maka perlu mengetikkan

    return (T)(object)(employee);
  2. jika Anda perlu mengembalikan nol.

    return default(T);
pengguna725388
sumber
Hai user725388, harap verifikasi opsi pertama
Jogi Joseph George
7

Di bawah ini adalah dua opsi yang dapat Anda gunakan

return default(T);

atau

where T : class, IThing
 return null;
Jaydeep Shil
sumber
6

Pilihan Anda yang lain adalah menambahkan ini di akhir deklarasi Anda:

    where T : class
    where T: IList

Dengan begitu itu akan memungkinkan Anda untuk mengembalikan nol.

BFree
sumber
Jika kedua kendala memiliki tipe yang sama, Anda menyebutkan jenisnya sekali dan menggunakan koma, seperti where T : class, IList. Jika Anda memiliki kendala untuk berbagai jenis, Anda mengulangi token where, seperti pada where TFoo : class where TBar : IList.
Jeppe Stig Nielsen
3

solusi dari karya TheSoftwareJedi,

Anda juga dapat mengarsipkannya dengan menggunakan beberapa tipe nilai dan nullable:

static T? FindThing<T>(IList collection, int id) where T : struct, IThing
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;
}
devi
sumber
1

Ambil rekomendasi kesalahan ... dan baik pengguna default(T)atau new T.

Anda harus menambahkan perbandingan dalam kode Anda untuk memastikan bahwa itu adalah kecocokan yang valid jika Anda menggunakan rute itu.

Kalau tidak, berpotensi mempertimbangkan parameter output untuk "kecocokan ditemukan".

Penjual Mitchel
sumber
1

Berikut adalah contoh yang berfungsi untuk nilai pengembalian Nullable Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}
Luke
sumber
Sejak C # 7.3 (Mei 2018), Anda dapat meningkatkan batasan untuk where TEnum : struct, Enum. Ini memastikan bahwa penelepon tidak sengaja memberikan tipe nilai yang bukan enum (seperti a intatau a DateTime).
Jeppe Stig Nielsen
0

Alternatif lain untuk 2 jawaban yang disajikan di atas. Jika Anda mengubah jenis pengembalian menjadi object, Anda dapat kembali null, sementara pada saat yang sama memberikan pengembalian non-nol.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}
Jeson Martajaya
sumber
Kelemahan: ini akan membutuhkan pemanggil metode untuk melemparkan objek yang dikembalikan (dalam kasus non-nol), yang menyiratkan tinju -> kurang kinerja. Apakah saya benar?
Csharpest
0

Demi kelengkapan, ada baiknya mengetahui Anda juga bisa melakukan ini:

return default;

Ini mengembalikan sama dengan return default(T);

LCIII
sumber
0

Bagi saya ini berfungsi sebagaimana adanya. Di mana tepatnya masalahnya?

public static T FindThing<T>(this IList collection, int id) where T : IThing, new()
{
    foreach (T thing in collection)
    {
        if (thing.Id == id)
            return thing;
        }
    }

    return null; //work
    return (T)null; //work
    return null as T; //work
    return default(T); //work


}
Mertuarez
sumber