เป็นโมฆะใน C # generics?


97

ฉันมีวิธีการทั่วไปที่รับคำขอและให้การตอบกลับ

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

แต่ฉันไม่ต้องการคำตอบสำหรับคำขอของฉันเสมอไปและฉันไม่ต้องการป้อนข้อมูลคำขอเพื่อรับคำตอบเสมอไป ฉันไม่ต้องการคัดลอกและวางวิธีการทั้งหมดเพื่อทำการเปลี่ยนแปลงเล็กน้อย สิ่งที่ฉันต้องการคือสามารถทำได้:

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

เป็นไปได้ในบางลักษณะหรือไม่? ดูเหมือนว่าการใช้ void โดยเฉพาะจะไม่ได้ผล แต่ฉันหวังว่าจะพบบางสิ่งที่คล้ายคลึงกัน


1
ทำไมไม่ใช้ System.Object และทำการตรวจสอบค่าว่างใน DoSomething (การตอบสนอง Tres คำขอ Treq)
James

โปรดทราบว่าคุณจำเป็นต้องใช้ค่าส่งคืน คุณสามารถเรียกใช้ฟังก์ชันเช่นโพรซีเดอร์ DoSomething(x);แทนy = DoSomething(x);
Olivier Jacot-Descombes

1
ฉันคิดว่าคุณตั้งใจจะพูดว่า "โปรดทราบว่าคุณไม่จำเป็นต้องใช้มูลค่าคืน" @ OlivierJacot-Descombes
zanedp

คำตอบ:


100

คุณไม่สามารถใช้งานได้voidแต่คุณสามารถใช้ได้object: มันเป็นความไม่สะดวกเล็กน้อยเนื่องจากvoidฟังก์ชันที่คุณต้องการจะต้องส่งคืนnullแต่หากรวมรหัสของคุณเข้าด้วยกันก็ควรเป็นราคาที่ต้องจ่ายเล็กน้อย

ไร้ความสามารถนี้จะใช้voidเป็นชนิดกลับเป็นอย่างน้อยบางส่วนรับผิดชอบในการแยกระหว่างการFunc<...>และAction<...>ครอบครัวของผู้ได้รับมอบหมายทั่วไป: มันมีความเป็นไปได้ที่จะกลับมาvoidทั้งหมดจะกลายเป็นเพียงAction<X,Y,Z> Func<X,Y,Z,void>น่าเสียดายที่ไม่สามารถทำได้


48
(โจ๊ก)และเขายังคงสามารถกลับมาจากผู้ที่วิธีการจะเป็นโมฆะด้วยvoid return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));แม้ว่ามันจะเป็นโมฆะแบบบรรจุกล่องก็ตาม
Jeppe Stig Nielsen

1
เนื่องจาก C # รองรับคุณสมบัติการเขียนโปรแกรมที่ใช้งานได้มากขึ้นคุณสามารถดูหน่วยที่แสดงvoidใน FP ได้ และมีเหตุผลที่ดีที่จะใช้มัน ใน F # ยังคงเป็น. NET เรามีunitในตัว
joe

88

ไม่น่าเสียดายที่ไม่ ถ้าvoidเป็นคนประเภท "จริง" (เช่นunitใน F #เป็นต้น) ชีวิตจะง่ายกว่านี้มากในหลาย ๆ ด้าน โดยเฉพาะอย่างยิ่งเราจะไม่จำเป็นต้องใช้ทั้งFunc<T>และAction<T>ครอบครัว - มีเพียงแค่ต้องการจะFunc<void>แทนAction, Func<T, void>แทนAction<T>ฯลฯ

นอกจากนี้ยังจะทำให้ async ง่าย - มันน่าจะมีความจำเป็นในการที่ไม่ใช่ทั่วไปไม่มีTaskประเภทที่ทั้งหมด - Task<void>เราต้องการเพียงแค่มี

น่าเสียดายที่นั่นไม่ใช่วิธีการทำงานของระบบประเภท C # หรือ. NET ...


4
Unfortunately, that's not the way the C# or .NET type systems work...คุณทำให้ฉันมีความหวังว่าในที่สุดสิ่งต่างๆอาจจะเป็นเช่นนั้น ประเด็นสุดท้ายของคุณหมายความว่าเราไม่น่าจะมีสิ่งที่เป็นไปได้หรือ
Dave Cousineau

2
@Sahuagin: ฉันไม่สงสัย - มันจะเป็นการเปลี่ยนแปลงครั้งใหญ่ในตอนนี้
Jon Skeet

1
@stannius: ไม่มันเป็นความไม่สะดวกมากกว่าสิ่งที่ทำให้รหัสไม่ถูกต้องฉันคิดว่า
Jon Skeet

2
มีข้อดีหรือไม่ที่จะมีประเภทการอ้างอิงหน่วยเหนือประเภทค่าว่างเพื่อแสดงถึง "โมฆะ" ประเภทค่าว่างดูเหมือนจะเหมาะกับฉันมากกว่าไม่มีค่าและไม่ต้องใช้พื้นที่ ฉันสงสัยว่าทำไมโมฆะไม่ถูกนำมาใช้เช่นนี้ การเปิดหรือไม่โผล่จากสแต็กจะไม่สร้างความแตกต่าง (การพูดรหัสเนทีฟใน IL อาจแตกต่างกัน)
Ondrej Petrzilka

1
@Ondrej: ฉันเคยลองใช้โครงสร้างว่างสำหรับสิ่งต่าง ๆ มาก่อนแล้วและมันก็ไม่ว่างเปล่าใน CLR ... แน่นอนว่ามันอาจจะเป็นแบบพิเศษก็ได้ นอกเหนือจากนั้นฉันไม่รู้ว่าฉันจะแนะนำอะไร ยังไม่ได้คิดเรื่องนี้มาก
Jon Skeet

27

นี่คือสิ่งที่คุณสามารถทำได้ อย่างที่ @JohnSkeet กล่าวว่าไม่มียูนิตใน C # ดังนั้นจงสร้างมันขึ้นมาเอง!

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

ตอนนี้คุณสามารถใช้Func<..., ThankYou>แทนAction<...>

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

หรือใช้สิ่งที่สร้างโดยทีม Rx: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx


System.Reactive.Unit เป็นคำแนะนำที่ดี ณ เดือนพฤศจิกายน 2016 หากคุณสนใจที่จะใช้ Reactive framework ขนาดเล็กที่สุดเท่าที่จะเป็นไปได้เพราะคุณไม่ได้ใช้อะไรเลยนอกจากคลาส Unit ให้ใช้ nuget package managerInstall-Package System.Reactive.Core
DannyMeister

6
เปลี่ยนชื่อ ThankYou เป็น "KThx" และเป็นผู้ชนะ ^ _ ^Kthx.Bye;
LexH

เพียงเพื่อตรวจสอบว่าฉันไม่ได้พลาดอะไรไป .. Bye getter ไม่ได้เพิ่มอะไรที่สำคัญในการเข้าถึงโดยตรงที่นี่หรือไม่?
Andrew

1
@ แอนดรูว์คุณไม่จำเป็นต้องลาก่อนเว้นแต่คุณต้องการทำตามเจตนารมณ์ของการเข้ารหัส C # ซึ่งบอกว่าคุณไม่ควรเปิดเผยทุ่งเปล่า
Trident D'Gao

16

คุณสามารถใช้Objectตามที่คนอื่นแนะนำได้ หรือInt32ที่เคยเห็นมาบ้าง การใช้Int32แนะนำหมายเลข "จำลอง" (ใช้0) แต่อย่างน้อยคุณไม่สามารถใส่วัตถุขนาดใหญ่และแปลกใหม่ลงในInt32ข้อมูลอ้างอิงได้ (โครงสร้างถูกปิดผนึก)

คุณยังสามารถเขียนประเภท "โมฆะ" ของคุณเองได้:

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

MyVoidลำดับที่ได้รับอนุญาต (มันไม่ได้เป็นระดับคงที่) nullแต่จะไม่สามารถ ตัวสร้างอินสแตนซ์เป็นแบบส่วนตัว (และหากมีคนพยายามเรียกตัวสร้างไพรเวตนี้ผ่านการสะท้อนกลับจะมีข้อยกเว้นเกิดขึ้น)


เนื่องจากมีการนำค่า tuples มาใช้ (2017, .NET 4.7) จึงอาจเป็นเรื่องปกติที่จะใช้โครงสร้างValueTuple (0-tuple ซึ่งเป็นตัวแปรที่ไม่ใช่ตัวแปรทั่วไป) แทน a MyVoid. อินสแตนซ์มีค่าToString()ที่ส่งกลับ"()"ดังนั้นจึงดูเหมือนศูนย์ทูเพิล ในเวอร์ชันปัจจุบันของ C # คุณไม่สามารถใช้โทเค็น()ในโค้ดเพื่อรับอินสแตนซ์ได้ คุณสามารถใช้default(ValueTuple)หรือเพียงแค่default(เมื่อสามารถอนุมานประเภทจากบริบทได้) แทน


2
ชื่ออื่นสำหรับสิ่งนี้คือรูปแบบวัตถุว่าง (รูปแบบการออกแบบ)
Aelphaeis

@Aelphaeis แนวคิดนี้แตกต่างจากรูปแบบวัตถุว่างเล็กน้อย ที่นี่ประเด็นก็คือมีบางประเภทที่เราสามารถใช้กับทั่วไปได้ เป้าหมายที่มีรูปแบบวัตถุว่างคือการหลีกเลี่ยงการเขียนเมธอดที่ส่งคืนค่าว่างเพื่อระบุกรณีพิเศษและส่งคืนวัตถุจริงด้วยพฤติกรรมเริ่มต้นที่เหมาะสมแทน
Andrew Palmer

8

ฉันชอบความคิดของ Aleksey Bykov ข้างต้น แต่อาจทำให้ง่ายขึ้นเล็กน้อย

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

เนื่องจากฉันไม่เห็นเหตุผลที่ชัดเจนว่าทำไม Nothing AtAll ไม่สามารถให้ null

แนวคิดเดียวกันนี้ (หรือแนวคิดโดย Jeppe Stig Nielsen) ยังเหมาะสำหรับการใช้งานกับคลาสที่พิมพ์

เช่นหากใช้ type เพื่ออธิบายอาร์กิวเมนต์ของโพรซีเดอร์ / ฟังก์ชันที่ส่งผ่านเป็นอาร์กิวเมนต์ของเมธอดบางอย่างเท่านั้นและตัวมันเองจะไม่ใช้อาร์กิวเมนต์ใด ๆ

(คุณยังคงต้องสร้าง dummy wrapper หรืออนุญาตตัวเลือก "Nothing" แต่ IMHO การใช้คลาสดูดีเมื่อใช้ myClass <Nothing>)

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

หรือ

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}

1
ค่าที่มีความหมายของการเป็นหายไปหรือขาดnull objectการมีค่าเดียวสำหรับNothingวิธีการนั้นไม่เคยดูเหมือนสิ่งอื่นใด
LexH

เป็นความคิดที่ดี แต่ฉันเห็นด้วยกับ @LexieHankins ฉันคิดว่าดีกว่าคือให้ "ไม่มีอะไรเลย" เป็นอินสแตนซ์เฉพาะของคลาสที่Nothingเก็บไว้ในฟิลด์สแตติกส่วนตัวและเพิ่มคอนสตรัคเตอร์ส่วนตัว ความจริงที่ว่าโมฆะยังคงเป็นไปได้นั้นน่ารำคาญ แต่จะได้รับการแก้ไขหวังว่าด้วย C # 8
Kirk Woll

ในทางวิชาการฉันเข้าใจถึงความแตกต่าง แต่มันจะเป็นกรณีพิเศษมากที่ความแตกต่างมีความสำคัญ (ลองนึกภาพฟังก์ชันของประเภท nullable ทั่วไปโดยที่ผลตอบแทนของ "null" ใช้เป็นเครื่องหมายพิเศษเช่นเป็นเครื่องหมายแสดงข้อผิดพลาดบางประเภท)
Eske Rahn

4

voidแม้ว่าจะเป็นประเภท แต่จะใช้ได้เฉพาะกับประเภทการส่งคืนของวิธีการ

ไม่มีทางเลี่ยงข้อ จำกัด voidนี้ไม่เป็น


1

สิ่งที่ฉันทำในตอนนี้คือสร้างชนิดปิดผนึกที่กำหนดเองด้วยตัวสร้างส่วนตัว สิ่งนี้ดีกว่าการทิ้งข้อยกเว้นใน c-tor เพราะคุณไม่ต้องใช้จนกว่ารันไทม์จะพบว่าสถานการณ์ไม่ถูกต้อง จะดีกว่าการส่งคืนอินสแตนซ์แบบคงที่อย่างละเอียดเนื่องจากคุณไม่ต้องจัดสรรเลยแม้แต่ครั้งเดียว ดีกว่าการคืนค่า null แบบคงที่อย่างละเอียดเนื่องจากมีรายละเอียดน้อยกว่าในด้านการโทร สิ่งเดียวที่ผู้โทรทำได้คือให้ null

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

public sealed class None {
    private None() { }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.