nichtig in C # Generika?

94

Ich habe eine generische Methode, die eine Anfrage entgegennimmt und eine Antwort liefert.

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

Ich möchte jedoch nicht immer eine Antwort auf meine Anfrage, und ich möchte nicht immer Anforderungsdaten eingeben, um eine Antwort zu erhalten. Ich möchte auch nicht, dass Methoden vollständig kopiert und eingefügt werden müssen, um geringfügige Änderungen vorzunehmen. Was ich will, ist, dies tun zu können:

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

Ist das irgendwie machbar? Es scheint, dass die spezifische Verwendung von void nicht funktioniert, aber ich hoffe, etwas Analoges zu finden.

Regie
quelle
1
Warum nicht einfach System.Object verwenden und in DoSomething (Tres-Antwort, Treq-Anfrage) eine Nullprüfung durchführen?
James
Beachten Sie, dass Sie den Rückgabewert verwenden müssen. Sie können Funktionen wie Prozeduren aufrufen. DoSomething(x);statty = DoSomething(x);
Olivier Jacot-Descombes
1
Ich denke, Sie wollten sagen: "Beachten Sie, dass Sie den Rückgabewert nicht verwenden müssen." @ OlivierJacot-Descombes
zanedp

Antworten:

95

Sie können nicht verwenden void, aber Sie können verwenden object: Es ist ein wenig unpraktisch, weil Ihre potenziellen voidFunktionen zurückgegeben werden müssen null, aber wenn es Ihren Code vereinheitlicht, sollte es ein kleiner Preis sein, den Sie zahlen müssen.

Diese Unfähigkeit, voidals Rückgabetyp zu verwenden, ist zumindest teilweise für eine Aufteilung zwischen den Func<...>und den Action<...>Familien der generischen Delegierten verantwortlich: Wäre eine Rückgabe möglich gewesen void, Action<X,Y,Z>würde alles einfach werden Func<X,Y,Z,void>. Das geht leider nicht.

dasblinkenlight
quelle
44
(Witz) Und er kann immer noch voidvon diesen Methoden zurückkehren, mit denen er nichtig wäre return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Es wird jedoch eine Leere sein.
Jeppe Stig Nielsen
Da C # mehr funktionale Programmierfunktionen unterstützt, können Sie sich Unit ansehen , die voidin FP dargestellt wird. Und es gibt gute Gründe, es zu benutzen. In F #, immer noch .NET, haben wir uniteingebaut.
Joe
87

Nein, leider nicht. Wennvoid ein "echter" Typ wäre (wie zum Beispiel unitin F # ), wäre das Leben in vielerlei Hinsicht viel einfacher. Insbesondere würden wir nicht sowohl die Func<T>als auch die Action<T>Familien brauchen - es würde nur Func<void>statt Action, Func<T, void>statt Action<T>usw. geben.

Es würde auch die Asynchronisation einfacher machen - der nicht generische TaskTyp wäre überhaupt nicht erforderlich - wir hätten es einfach getan Task<void>.

Leider funktionieren die Systeme vom Typ C # oder .NET nicht so ...

Jon Skeet
quelle
4
Unfortunately, that's not the way the C# or .NET type systems work...Sie haben mich hoffnungsvoll gemacht, dass die Dinge vielleicht irgendwann so funktionieren könnten. Bedeutet Ihr letzter Punkt, dass wir wahrscheinlich nie so arbeiten werden?
Dave Cousineau
2
@Sahuagin: Ich vermute nicht - es wäre zu diesem Zeitpunkt eine ziemlich große Veränderung.
Jon Skeet
1
@stannius: Nein, es ist eher eine Unannehmlichkeit als etwas, das zu falschem Code führt, denke ich.
Jon Skeet
2
Gibt es einen Vorteil, wenn der Einheitenreferenztyp gegenüber dem leeren Werttyp "void" darstellt? Der leere Werttyp scheint mir besser geeignet zu sein, er trägt keinen Wert und nimmt keinen Platz ein. Ich frage mich, warum void nicht so implementiert wurde. Das Poppen oder Nicht-Poppen vom Stapel würde keinen Unterschied machen (sprechender nativer Code, in IL könnte es anders sein).
Ondrej Petrzilka
1
@Ondrej: Ich habe schon früher versucht, eine leere Struktur für Dinge zu verwenden, und sie ist in der CLR nicht leer ... Es könnte natürlich auch ein Sonderfall sein. Darüber hinaus weiß ich nicht, was ich vorschlagen würde; Ich habe nicht viel darüber nachgedacht.
Jon Skeet
27

Hier ist was Sie tun können. Wie @JohnSkeet sagte, gibt es in C # keinen Einheitentyp, also machen Sie es selbst!

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

Jetzt können Sie immer Func<..., ThankYou>anstelle von verwendenAction<...>

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

Oder verwenden Sie etwas, das bereits vom Rx-Team erstellt wurde: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx

Dreizack D'Gao
quelle
System.Reactive.Unit ist ein guter Vorschlag. Wenn Sie ab November 2016 daran interessiert sind, einen möglichst kleinen Teil des Reactive-Frameworks zu erhalten, da Sie nichts anderes als die Unit-Klasse verwenden, verwenden Sie den Nuget-PaketmanagerInstall-Package System.Reactive.Core
DannyMeister,
6
Benennen Sie ThankYou in "KThx" um und es ist ein Gewinner. ^ _ ^Kthx.Bye;
LexH
Nur um zu überprüfen, ob mir etwas fehlt. Der Bye Getter fügt hier nichts Wesentliches über den direkten Zugriff hinzu, oder?
Andrew
1
@ Andrew Sie brauchen keinen Tschüss Getter, es sei denn, Sie möchten dem Geist der C # -Codierung folgen, die besagt, dass Sie keine nackten Felder aussetzen sollten
Trident D'Gao
16

Sie können einfach verwenden, Objectwie andere vorgeschlagen haben. Oder Int32was ich gesehen habe. Mit using wird Int32eine "Dummy" -Nummer eingeführt (Verwendung 0), aber zumindest können Sie kein großes und exotisches Objekt in eine Int32Referenz einfügen (Strukturen sind versiegelt).

Sie können auch Ihren eigenen "void" -Typ schreiben:

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

MyVoidReferenzen sind erlaubt (es ist keine statische Klasse), können aber nur sein null. Der Instanzkonstruktor ist privat (und wenn jemand versucht, diesen privaten Konstruktor durch Reflektion aufzurufen, wird eine Ausnahme ausgelöst).


Da Werttupel eingeführt wurden (2017, .NET 4.7), ist es möglicherweise natürlich, die StrukturValueTuple (das 0-Tupel, die nicht generische Variante) anstelle eines solchen zu verwenden MyVoid. Die Instanz hat eine ToString(), die zurückgibt "()", sodass sie wie ein Null-Tupel aussieht. Ab der aktuellen Version von C # können Sie die Token ()im Code nicht mehr verwenden , um eine Instanz abzurufen. Sie können stattdessen default(ValueTuple)oder nur default(wenn der Typ aus dem Kontext abgeleitet werden kann) verwenden.

Jeppe Stig Nielsen
quelle
2
Ein anderer Name hierfür wäre das Nullobjektmuster (Entwurfsmuster).
Aelphaeis
@Aelphaeis Dieses Konzept unterscheidet sich ein wenig von einem Nullobjektmuster. Hier geht es nur darum, einen Typ zu haben, den wir mit einem Generikum verwenden können. Das Ziel mit einem Nullobjektmuster besteht darin, das Schreiben einer Methode zu vermeiden, die Null zurückgibt, um einen Sonderfall anzuzeigen, und stattdessen ein tatsächliches Objekt mit entsprechendem Standardverhalten zurückzugeben.
Andrew Palmer
8

Ich mag die Idee von Aleksey Bykov oben, aber sie könnte etwas vereinfacht werden

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

Da sehe ich keinen offensichtlichen Grund warum Nothing.AtAll nicht einfach null geben konnte

Die gleiche Idee (oder die von Jeppe Stig Nielsen) eignet sich auch hervorragend für typisierte Klassen.

Beispiel, wenn der Typ nur verwendet wird, um die Argumente für eine Prozedur / Funktion zu beschreiben, die als Argument an eine Methode übergeben wurde, und selbst keine Argumente akzeptiert.

(Sie müssen immer noch entweder einen Dummy-Wrapper erstellen oder ein optionales "Nothing" zulassen. Aber meiner Meinung nach sieht die Klassenverwendung mit myClass <Nothing> gut aus.)

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

oder

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}
Eske Rahn
quelle
1
Der Wert nullhat die Konnotation, dass er fehlt oder fehlt object. Nur einen Wert für ein zu haben Nothingbedeutet, dass es nie wie etwas anderes aussieht.
LexH
Es ist eine gute Idee, aber ich stimme @LexieHankins zu. Ich denke, es wäre besser, wenn "überhaupt nichts" eine eindeutige Instanz der Klasse wäre, Nothingdie in einem privaten statischen Feld gespeichert ist und einen privaten Konstruktor hinzufügt. Die Tatsache, dass Null immer noch möglich ist, ist ärgerlich, aber das wird hoffentlich mit C # 8 gelöst.
Kirk Woll
Akademisch verstehe ich die Unterscheidung, aber es wäre ein ganz besonderer Fall, wenn die Unterscheidung wichtig ist. (Stellen Sie sich eine Funktion vom generischen nullbaren Typ vor, bei der die Rückgabe von "null" als spezieller Marker verwendet wird, z. B. als eine Art Fehlermarker)
Eske Rahn
4

voidObwohl ein Typ, ist er nur als Rückgabetyp einer Methode gültig.

An dieser Einschränkung von führt kein Weg vorbei void.

Oded
quelle
1

Was ich derzeit mache, ist das Erstellen von benutzerdefinierten versiegelten Typen mit einem privaten Konstruktor. Dies ist besser, als Ausnahmen in den C-Tor zu werfen, da Sie erst zur Laufzeit abrufen müssen, um herauszufinden, ob die Situation falsch ist. Es ist subtil besser als die Rückgabe einer statischen Instanz, da Sie nicht einmal zuordnen müssen. Es ist subtil besser als die Rückgabe der statischen Null, da es auf der Aufrufseite weniger ausführlich ist. Der Anrufer kann nur null angeben.

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

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