void in C # generics?


94

Ho un metodo generico che accetta una richiesta e fornisce una risposta.

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

Ma non desidero sempre una risposta per la mia richiesta e non voglio sempre alimentare i dati della richiesta per ottenere una risposta. Inoltre, non voglio dover copiare e incollare i metodi nella loro interezza per apportare modifiche minori. Quello che voglio, è essere in grado di fare questo:

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

È fattibile in qualche modo? Sembra che l'uso specifico di void non funzioni, ma spero di trovare qualcosa di analogo.


1
Perché non usare semplicemente System.Object e fare un controllo nullo in DoSomething (risposta Tres, richiesta Treq)?
James

Tieni presente che devi utilizzare il valore restituito. Puoi chiamare funzioni come procedure. DoSomething(x);invece diy = DoSomething(x);
Olivier Jacot-Descombes

1
Penso che volessi dire: "Tieni presente che non è necessario utilizzare il valore restituito". @ OlivierJacot-Descombes
zanedp

Risposte:


95

Non puoi usare void, ma puoi usare object: è un piccolo inconveniente perché le tue aspiranti voidfunzioni devono tornare null, ma se unifica il tuo codice, dovrebbe essere un piccolo prezzo da pagare.

Questa incapacità di utilizzare voidcome tipo di ritorno è almeno in parte responsabile di una divisione tra le famiglie Func<...>e Action<...>dei delegati generici: se fosse stato possibile tornare void, tutto Action<X,Y,Z>sarebbe diventato semplice Func<X,Y,Z,void>. Sfortunatamente, questo non è possibile.


45
(Scherzo) E lui può ancora tornare voidda quei metodi inutili, con return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Sarà un vuoto inscatolato, però.
Jeppe Stig Nielsen

Poiché C # supporta funzionalità di programmazione più funzionali, puoi dare un'occhiata a Unit che rappresenta voidin FP. E ci sono buone ragioni per usarlo. In F #, ancora .NET, abbiamo unitbuilt-in.
joe

88

No, sfortunatamente no. Se voidfosse un tipo "reale" (come unitin F # , per esempio) la vita sarebbe molto più semplice in molti modi. In particolare, non avremmo bisogno di entrambe le famiglie Func<T>e Action<T>- ci sarebbero solo Func<void>invece di Action, Func<T, void>invece di Action<T>ecc.

Renderebbe anche l'asincronia più semplice - non ci sarebbe affatto bisogno del tipo non generico Task- avremmo semplicemente Task<void>.

Sfortunatamente, non è così che funzionano i sistemi di tipo C # o .NET ...


4
Unfortunately, that's not the way the C# or .NET type systems work...Mi stavi facendo sperare che forse alla fine le cose avrebbero potuto funzionare così. Il tuo ultimo punto significa che è improbabile che le cose funzionino in questo modo?
Dave Cousineau

2
@Sahuagin: Sospetto di no - sarebbe un cambiamento piuttosto grande a questo punto.
Jon Skeet

1
@stannius: No, è più un inconveniente che qualcosa che porta a un codice errato, credo.
Jon Skeet

2
C'è un vantaggio nell'avere un tipo di riferimento di unità rispetto al tipo di valore vuoto per rappresentare "void"? Il tipo di valore vuoto mi sembra più adatto, non ha alcun valore e non occupa spazio. Mi chiedo perché void non sia stato implementato in questo modo. Saltare o non estrarlo dallo stack non farebbe differenza (parlando di codice nativo, in IL potrebbe essere diverso).
Ondrej Petrzilka

1
@Ondrej: ho già provato a usare una struttura vuota per le cose, e finisce per non essere vuota nel CLR ... Potrebbe essere un caso speciale, ovviamente. Oltre a ciò, non so quale suggerirei; Non ci ho pensato molto.
Jon Skeet

27

Ecco cosa puoi fare. Come ha detto @JohnSkeet non esiste un tipo di unità in C #, quindi crealo tu stesso!

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

Ora puoi sempre usare al Func<..., ThankYou>posto diAction<...>

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

Oppure utilizza qualcosa già creato dal team Rx: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx


System.Reactive.Unit è un buon suggerimento. A partire da novembre 2016, se sei interessato a ottenere il più piccolo pezzo possibile del framework Reactive, perché non stai utilizzando nient'altro che la classe Unit, usa il gestore di pacchetti Install-Package System.Reactive.Core
nuget

6
Rinomina grazie a "KThx" ed è un vincitore. ^ _ ^Kthx.Bye;
LexH

Solo per controllare che non mi manchi qualcosa .. il Bye getter non aggiunge nulla di significativo sull'accesso diretto qui, vero?
Andrew

1
@Andrew non hai bisogno di ciao getter, a meno che tu non voglia seguire lo spirito della codifica C #, che dice che non dovresti esporre campi nudi
Trident D'Gao

16

Potresti semplicemente usare Objectcome altri hanno suggerito. O Int32che ho visto un certo utilizzo. L'uso Int32introduce un numero "fittizio" (usa 0), ma almeno non puoi mettere nessun oggetto grande ed esotico in un Int32riferimento (le strutture sono sigillate).

Puoi anche scrivere il tuo tipo "void":

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

MyVoidi riferimenti sono consentiti (non è una classe statica) ma possono essere solo null. Il costruttore dell'istanza è privato (e se qualcuno cerca di chiamare questo costruttore privato tramite la riflessione, verrà lanciata un'eccezione).


Poiché sono state introdotte le tuple di valore (2017, .NET 4.7), è forse naturale utilizzare la strutturaValueTuple (la tupla 0, la variante non generica) invece di tale MyVoid. La sua istanza ha un ToString()che restituisce "()", quindi sembra una tupla zero. A partire dalla versione corrente di C #, non è possibile utilizzare i token ()nel codice per ottenere un'istanza. Puoi usare default(ValueTuple)o solo default(quando il tipo può essere dedotto dal contesto) invece.


2
Un altro nome per questo sarebbe il modello di oggetto nullo (modello di progettazione).
Aelphaeis

@Aelphaeis Questo concetto è leggermente diverso da un modello di oggetto nullo. Qui, il punto è solo avere un tipo che possiamo usare con un generico. L'obiettivo con un modello di oggetto null è evitare di scrivere un metodo che restituisce null per indicare un caso speciale e restituire invece un oggetto effettivo con un comportamento predefinito appropriato.
Andrew Palmer

8

Mi piace l'idea di Aleksey Bykov sopra, ma potrebbe essere semplificata un po '

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

Non vedo alcun motivo apparente per cui Nothing.AtAll non poteva semplicemente dare null

La stessa idea (o quella di Jeppe Stig Nielsen) è ottima anche per l'utilizzo con classi dattiloscritte.

Ad esempio, se il tipo viene utilizzato solo per descrivere gli argomenti di una procedura / funzione passata come argomento a un metodo, e esso stesso non accetta alcun argomento.

(Dovrai comunque creare un wrapper fittizio o consentire un "Niente" opzionale. Ma IMHO l'utilizzo della classe sembra carino con myClass <Niente>)

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

o

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

1
Il valore nullha la connotazione di essere un mancante o assente object. Avere un solo valore per un Nothingmezzo non assomiglia mai a nient'altro.
LexH

È una buona idea, ma sono d'accordo con @LexieHankins. Penso che sarebbe meglio che "niente di niente" fosse un'istanza unica della classe Nothingmemorizzata in un campo statico privato e l'aggiunta di un costruttore privato. Il fatto che null sia ancora possibile è fastidioso, ma verrà risolto, si spera con C # 8.
Kirk Woll

Accademicamente capisco la distinzione, ma sarebbe un caso molto speciale in cui la distinzione è importante. (Immagina una funzione di tipo nullable generico, dove il ritorno di "null" è usato come un indicatore speciale, ad esempio come una sorta di indicatore di errore)
Eske Rahn

4

void, sebbene sia un tipo, è valido solo come tipo restituito di un metodo.

Non c'è modo di aggirare questa limitazione di void.


1

Quello che faccio attualmente è creare tipi sigillati personalizzati con un costruttore privato. Questo è meglio che lanciare eccezioni nel c-tor perché non devi andare fino al runtime per capire che la situazione non è corretta. È sottilmente meglio che restituire un'istanza statica perché non è necessario allocare nemmeno una volta. È sottilmente meglio che restituire null statico perché è meno dettagliato sul lato della chiamata. L'unica cosa che il chiamante può fare è dare null.

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

public sealed class None {
    private None() { }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.