batal di C # generik?

94

Saya memiliki metode umum yang menerima permintaan dan memberikan tanggapan.

public Tres DoSomething<Tres, Treq>(Tres response, Treq request)
{/*stuff*/}

Tetapi saya tidak selalu menginginkan tanggapan untuk permintaan saya, dan saya tidak selalu ingin memberi makan data permintaan untuk mendapatkan tanggapan. Saya juga tidak ingin harus menyalin dan menempelkan metode secara keseluruhan untuk membuat perubahan kecil. Yang saya inginkan, adalah bisa melakukan ini:

public Tre DoSomething<Tres>(Tres response)
{
    return DoSomething<Tres, void>(response, null);
}

Apakah ini layak dalam beberapa hal? Tampaknya secara khusus menggunakan void tidak berfungsi, tetapi saya berharap menemukan sesuatu yang serupa.

arahan
sumber
1
Mengapa tidak menggunakan System.Object saja dan melakukan pemeriksaan null di DoSomething (Tres response, Treq request)?
Yakobus
Perhatikan bahwa Anda memang perlu menggunakan nilai kembali. Anda dapat memanggil fungsi seperti prosedur. DoSomething(x);bukannyay = DoSomething(x);
Olivier Jacot-Descombes
1
Saya pikir Anda bermaksud mengatakan, "Perhatikan bahwa Anda tidak perlu menggunakan nilai pengembalian." @ Olivierot-
Descombes

Jawaban:

95

Anda tidak dapat menggunakan void, tetapi Anda dapat menggunakan object: ini adalah sedikit ketidaknyamanan karena calon voidfungsi Anda harus dikembalikan null, tetapi jika itu menyatukan kode Anda, itu harus menjadi harga yang kecil untuk dibayar.

Ketidakmampuan untuk menggunakan voidsebagai tipe pengembalian setidaknya sebagian bertanggung jawab atas pemisahan antara Func<...>dan Action<...>keluarga delegasi umum: seandainya dimungkinkan untuk kembali void, semua Action<X,Y,Z>akan menjadi sederhana Func<X,Y,Z,void>. Sayangnya, ini tidak mungkin.

dasblinkenlight
sumber
45
(Lelucon) Dan dia masih bisa kembali voiddari metode yang akan dibatalkan itu, dengan return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Ini akan menjadi kekosongan kotak.
Jeppe Stig Nielsen
Karena C # mendukung lebih banyak fitur pemrograman fungsional, Anda dapat melihat Unit yang mewakili voidFP. Dan ada alasan bagus untuk menggunakannya. Di F #, masih .NET, kami memiliki unitbuilt-in.
joe
88

Tidak, sayangnya tidak. Jika voidadalah tipe "nyata" (seperti unitdi F # , misalnya) hidup akan jauh lebih sederhana dalam banyak hal. Secara khusus, kita tidak perlu kedua Func<T>dan Action<T>keluarga - ada hanya akan Func<void>bukan Action, Func<T, void>bukan Action<T>dll

Ini juga akan membuat async lebih sederhana - sama sekali tidak diperlukan jenis non-generik Task- yang hanya akan kita miliki Task<void>.

Sayangnya, cara kerja sistem tipe C # atau .NET tidak seperti itu ...

Jon Skeet
sumber
4
Unfortunately, that's not the way the C# or .NET type systems work...Anda membuat saya berharap bahwa mungkin semuanya akan berjalan seperti itu pada akhirnya. Apakah poin terakhir Anda berarti bahwa kita tidak akan pernah mengalami hal-hal seperti itu?
Dave Cousineau
2
@Sahuagin: Saya kira tidak - ini akan menjadi perubahan yang cukup besar pada saat ini.
Jon Skeet
1
@stannius: Tidak, ini lebih merupakan ketidaknyamanan daripada sesuatu yang mengarah ke kode yang salah, saya kira.
Jon Skeet
2
Apakah ada keuntungan memiliki tipe referensi Unit dibandingkan tipe nilai kosong untuk merepresentasikan "void"? Jenis nilai kosong tampaknya lebih cocok untuk saya, tidak memiliki nilai dan tidak membutuhkan ruang. Saya bertanya-tanya mengapa void tidak diterapkan seperti ini. Popping atau tidak popping dari stack tidak akan membuat perbedaan (berbicara kode native, di IL mungkin berbeda).
Ondrej Petrzilka
1
@Ondrej: Saya sudah mencoba menggunakan struct kosong untuk hal-hal sebelumnya, dan akhirnya tidak kosong di CLR ... Ini bisa menjadi kasing khusus, tentu saja. Di luar itu, saya tidak tahu mana yang akan saya sarankan; Saya belum terlalu memikirkannya.
Jon Skeet
27

Inilah yang dapat Anda lakukan. Seperti yang dikatakan @JohnSkeet tidak ada tipe unit di C #, jadi buat sendiri!

public sealed class ThankYou {
   private ThankYou() { }
   private readonly static ThankYou bye = new ThankYou();
   public static ThankYou Bye { get { return bye; } }
}

Sekarang Anda selalu dapat menggunakan Func<..., ThankYou>sebagai penggantiAction<...>

public ThankYou MethodWithNoResult() {
   /* do things */
   return ThankYou.Bye;
}

Atau gunakan sesuatu yang sudah dibuat oleh tim Rx: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx

Trident D'Gao
sumber
System.Reactive.Unit adalah saran yang bagus. Mulai November 2016, jika Anda tertarik untuk mendapatkan sekecil mungkin bagian dari kerangka kerja Reaktif, karena Anda tidak menggunakan apa pun selain kelas Unit, gunakan manajer paket Install-Package System.Reactive.Core
nuget
6
Ubah nama ThankYou menjadi "KThx", dan itu adalah pemenangnya. ^ _ ^Kthx.Bye;
LexH
Hanya untuk memeriksa Saya tidak melewatkan sesuatu .. Bye getter tidak menambahkan sesuatu yang signifikan atas akses langsung di sini bukan?
Andrew
1
@Andrew Anda tidak perlu bye getter, kecuali Anda ingin mengikuti semangat pengkodean C #, yang mengatakan Anda tidak boleh mengekspos bidang kosong
Trident D'Gao
16

Anda bisa menggunakan Objectseperti yang disarankan orang lain. Atau Int32yang saya lihat ada gunanya. Menggunakan Int32memperkenalkan nomor "dummy" (penggunaan 0), tetapi setidaknya Anda tidak dapat memasukkan objek besar dan eksotis ke dalam Int32referensi (struct disegel).

Anda juga dapat menulis jenis "void" Anda sendiri:

public sealed class MyVoid
{
  MyVoid()
  {
    throw new InvalidOperationException("Don't instantiate MyVoid.");
  }
}

MyVoidreferensi diperbolehkan (ini bukan kelas statis) tetapi hanya bisa null. Konstruktor instance bersifat privat (dan jika seseorang mencoba memanggil konstruktor privat ini melalui refleksi, pengecualian akan dilemparkan ke mereka).


Sejak tupel nilai diperkenalkan (2017, .NET 4.7), mungkin wajar untuk menggunakan structValueTuple (0-tupel, varian non-generik) daripada file MyVoid. Instance-nya memiliki ToString()pengembalian yang "()", jadi terlihat seperti tupel-nol. Pada versi C # saat ini, Anda tidak dapat menggunakan token ()dalam kode untuk mendapatkan sebuah instance. Anda dapat menggunakan default(ValueTuple)atau hanya default(jika tipe dapat disimpulkan dari konteks) sebagai gantinya.

Jeppe Stig Nielsen
sumber
2
Nama lain untuk ini adalah pola objek nol (pola desain).
Aelphaeis
@Aelphaeis Konsep ini sedikit berbeda dari pola objek null. Di sini, intinya adalah memiliki beberapa tipe yang bisa kita gunakan dengan generik. Tujuan dengan pola objek null adalah untuk menghindari penulisan metode yang mengembalikan null untuk menunjukkan kasus khusus dan sebaliknya mengembalikan objek sebenarnya dengan perilaku default yang sesuai.
Andrew Palmer
8

Saya suka ide dari Aleksey Bykov di atas, tetapi ide tersebut bisa sedikit disederhanakan

public sealed class Nothing {
    public static Nothing AtAll { get { return null; } }
}

Karena saya tidak melihat alasan yang jelas mengapa Nothing.AtAll tidak bisa begitu saja memberikan null

Ide yang sama (atau yang dibuat oleh Jeppe Stig Nielsen) juga bagus untuk digunakan dengan kelas yang diketik.

Misalnya, jika tipe hanya digunakan untuk mendeskripsikan argumen ke prosedur / fungsi yang dikirimkan sebagai argumen ke beberapa metode, dan itu sendiri tidak mengambil argumen apa pun.

(Anda masih perlu membuat bungkus tiruan atau mengizinkan "Tidak Ada" opsional. Tapi IMHO penggunaan kelas terlihat bagus dengan myClass <Nothing>)

void myProcWithNoArguments(Nothing Dummy){
     myProcWithNoArguments(){
}

atau

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}
Eske Rahn
sumber
1
Nilainya nullmemiliki konotasi hilang atau tidak ada object. Memiliki hanya satu nilai untuk suatu Nothingsarana tidak pernah terlihat seperti yang lain.
LexH
Itu ide yang bagus, tapi saya setuju dengan @LexieHankins. Saya pikir lebih baik untuk "tidak ada sama sekali" menjadi contoh unik dari kelas yang Nothingdisimpan dalam bidang statis pribadi dan menambahkan konstruktor pribadi. Fakta bahwa nol masih mungkin mengganggu, tapi itu akan diselesaikan, mudah-mudahan dengan C # 8.
Kirk Woll
Secara akademis saya memahami perbedaannya, tetapi itu akan menjadi kasus yang sangat khusus di mana perbedaan itu penting. (Bayangkan sebuah fungsi dari tipe nullable generik, di mana kembalinya "null" digunakan sebagai penanda khusus, misalnya sebagai semacam penanda kesalahan)
Eske Rahn
4

void, meskipun sebuah tipe, hanya valid sebagai tipe kembalian dari sebuah metode.

Tidak ada jalan keluar dari batasan ini void.

Oded
sumber
1

Apa yang saat ini saya lakukan adalah membuat tipe tersegel khusus dengan konstruktor pribadi. Ini lebih baik daripada melemparkan pengecualian di c-tor karena Anda tidak perlu menunggu waktu proses untuk mengetahui situasinya salah. Ini secara halus lebih baik daripada mengembalikan instance statis karena Anda tidak perlu mengalokasikannya sekali pun. Ini secara halus lebih baik daripada mengembalikan statis null karena kurang bertele-tele di sisi panggilan. Satu-satunya hal yang dapat dilakukan pemanggil adalah memberikan null.

public sealed class Void {
    private Void() { }
}

public sealed class None {
    private None() { }
}
Gru
sumber