void dans les génériques C #?

94

J'ai une méthode générique qui prend une demande et fournit une réponse.

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

Mais je ne veux pas toujours une réponse à ma demande et je ne veux pas toujours alimenter les données de la demande pour obtenir une réponse. Je ne veux pas non plus avoir à copier et coller les méthodes dans leur intégralité pour apporter des modifications mineures. Ce que je veux, c'est pouvoir faire ceci:

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

Est-ce faisable d'une manière ou d'une autre? Il semble que l'utilisation spécifique de void ne fonctionne pas, mais j'espère trouver quelque chose d'analogue.

mise en scène
la source
1
Pourquoi ne pas simplement utiliser System.Object et effectuer une vérification nulle dans DoSomething (réponse Tres, requête Treq)?
James
Notez que vous devez utiliser la valeur de retour. Vous pouvez appeler des fonctions telles que des procédures. DoSomething(x);au lieu dey = DoSomething(x);
Olivier Jacot-Descombes
1
Je pense que vous vouliez dire: "Notez que vous n'avez pas besoin d'utiliser la valeur de retour." @ OlivierJacot-Descombes
zanedp

Réponses:

95

Vous ne pouvez pas utiliser void, mais vous pouvez utiliser object: c'est un petit inconvénient car vos futures voidfonctions doivent revenir null, mais si cela unifie votre code, cela devrait être un petit prix à payer.

Cette incapacité à utiliser voidcomme type de retour est au moins partiellement responsable d'une scission entre les Func<...>et les Action<...>familles de délégués génériques: s'il avait été possible de revenir void, tout Action<X,Y,Z>deviendrait simplement Func<X,Y,Z,void>. Malheureusement, ce n'est pas possible.

dasblinkenlight
la source
45
(Blague) Et il peut toujours revenir voidde ces méthodes prétendument nulles, avec return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));. Ce sera un vide fermé, cependant.
Jeppe Stig Nielsen
Comme C # prend en charge des fonctionnalités de programmation plus fonctionnelles, vous pouvez jeter un œil à Unit qui représente voiddans FP. Et il y a de bonnes raisons de l'utiliser. En F #, toujours .NET, nous avons unitintégré.
joe
88

Non, malheureusement non. Si voidc'était un type "réel" (comme uniten F # , par exemple), la vie serait beaucoup plus simple à bien des égards. En particulier, nous n'aurions pas besoin à la fois des familles Func<T>et Action<T>- il y aurait juste à la Func<void>place de Action, Func<T, void>au lieu de Action<T>etc.

Cela simplifierait également l'async - il n'y aurait aucun besoin du type non générique Task- nous l'aurions simplement fait Task<void>.

Malheureusement, ce n'est pas ainsi que fonctionnent les systèmes de type C # ou .NET ...

Jon Skeet
la source
4
Unfortunately, that's not the way the C# or .NET type systems work...Vous me donniez l'espoir que les choses pourraient éventuellement fonctionner comme ça. Votre dernier point signifie-t-il que nous ne pourrons probablement jamais faire fonctionner les choses de cette façon?
Dave Cousineau
2
@Sahuagin: Je suppose que non - ce serait un assez grand changement à ce stade.
Jon Skeet
1
@stannius: Non, c'est plus un inconvénient que quelque chose qui conduit à un code incorrect, je pense.
Jon Skeet
2
Y a-t-il un avantage à avoir un type de référence d'unité sur un type de valeur vide pour représenter "void"? Le type de valeur vide me semble mieux adapté, il n'a aucune valeur et ne prend pas de place. Je me demande pourquoi void n'a pas été implémenté comme ça. Le faire sauter ou ne pas le sortir de la pile ne ferait aucune différence (parler de code natif, en IL, cela pourrait être différent).
Ondrej Petrzilka
1
@Ondrej: J'ai déjà essayé d'utiliser une structure vide pour les choses avant, et elle finit par ne pas être vide dans le CLR ... Cela pourrait être une casse spéciale, bien sûr. Au-delà de cela, je ne sais pas ce que je suggérerais; Je n'y ai pas beaucoup réfléchi.
Jon Skeet
27

Voici ce que vous pouvez faire. Comme @JohnSkeet l'a dit, il n'y a pas de type d'unité en C #, alors faites-le vous-même!

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

Maintenant, vous pouvez toujours utiliser à la Func<..., ThankYou>place deAction<...>

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

Ou utilisez quelque chose déjà créé par l'équipe Rx: http://msdn.microsoft.com/en-us/library/system.reactive.unit%28v=VS.103%29.aspx

Trident D'Gao
la source
System.Reactive.Unit est une bonne suggestion. À partir de novembre 2016, si vous souhaitez obtenir un élément aussi petit que possible du cadre réactif, car vous n'utilisez rien d'autre que la classe Unit, utilisez le gestionnaire de paquets Install-Package System.Reactive.Core
nuget
6
Renommez ThankYou en "KThx", et c'est un gagnant. ^ _ ^Kthx.Bye;
LexH
Juste pour vérifier que je ne manque pas quelque chose ... le getter Bye n'ajoute rien de significatif sur l'accès direct ici n'est-ce pas?
Andrew le
1
@Andrew, vous n'avez pas besoin de bye getter, à moins que vous ne vouliez suivre l'esprit du codage C #, qui dit que vous ne devriez pas exposer les champs nus
Trident D'Gao
16

Vous pouvez simplement utiliser Objectcomme d'autres l'ont suggéré. Ou Int32dont j'ai vu une certaine utilité. Utiliser Int32introduit un nombre "factice" (utilisation 0), mais au moins vous ne pouvez pas mettre de gros objet exotique dans une Int32référence (les structures sont scellées).

Vous pouvez également écrire votre propre type "void":

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

MyVoidles références sont autorisées (ce n'est pas une classe statique) mais ne peuvent l'être que null. Le constructeur d'instance est privé (et si quelqu'un essaie d'appeler ce constructeur privé par réflexion, une exception lui sera renvoyée).


Depuis l'introduction de tuples de valeur (2017, .NET 4.7), il est peut-être naturel d'utiliser la structure structValueTuple (le 0-tuple, la variante non générique) au lieu d'un tel MyVoid. Son instance a un ToString()qui renvoie "()", donc elle ressemble à un zéro-tuple. À partir de la version actuelle de C #, vous ne pouvez pas utiliser les jetons ()dans le code pour obtenir une instance. Vous pouvez utiliser default(ValueTuple)ou juste default(lorsque le type peut être déduit du contexte) à la place.

Jeppe Stig Nielsen
la source
2
Un autre nom pour cela serait le modèle d'objet nul (modèle de conception).
Aelphaeis
@Aelphaeis Ce concept est un peu différent d'un modèle d'objet nul. Ici, le but est simplement d'avoir un type que nous pouvons utiliser avec un générique. Le but avec un modèle d'objet nul est d'éviter d'écrire une méthode qui renvoie null pour indiquer un cas spécial et de renvoyer à la place un objet réel avec un comportement par défaut approprié.
Andrew Palmer
8

J'aime l'idée d'Aleksey Bykov ci-dessus, mais elle pourrait être un peu simplifiée

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

Comme je ne vois aucune raison apparente pour laquelle Nothing.AtAll ne pourrait pas simplement donner null

La même idée (ou celle de Jeppe Stig Nielsen) est également idéale pour une utilisation avec des classes typées.

Par exemple, si le type n'est utilisé que pour décrire les arguments d'une procédure / fonction passée comme argument à une méthode, et qu'il ne prend lui-même aucun argument.

(Vous aurez toujours besoin de créer un emballage factice ou d'autoriser un "Nothing" facultatif. Mais à mon humble avis, l'utilisation de la classe semble bien avec myClass <Nothing>)

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

ou

void myProcWithNoArguments(Nothing Dummy=null){
    ...
}
Eske Rahn
la source
1
La valeur nulla la connotation d'être manquant ou absent object. Avoir une seule valeur pour un Nothingsignifie qu'il ne ressemble à rien d'autre.
LexH
C'est une bonne idée, mais je suis d'accord avec @LexieHankins. Je pense que mieux serait que "rien du tout" soit une instance unique de la classe Nothingstockée dans un champ statique privé et en ajoutant un constructeur privé. Le fait que null soit toujours possible est ennuyeux, mais cela sera résolu, espérons-le avec C # 8.
Kirk Woll
D'un point de vue académique, je comprends la distinction, mais ce serait un cas très spécial où la distinction importe. (Imaginez une fonction de type générique nullable, où le retour de "null" est utilisé comme marqueur spécial, par exemple comme une sorte de marqueur d'erreur)
Eske Rahn
4

void, bien qu'un type, n'est valide que comme type de retour d'une méthode.

Il n'y a aucun moyen de contourner cette limitation de void.

Oded
la source
1

Ce que je fais actuellement, c'est créer des types scellés personnalisés avec un constructeur privé. C'est mieux que de lancer des exceptions dans le c-tor parce que vous n'avez pas besoin d'obtenir jusqu'à l'exécution pour comprendre que la situation est incorrecte. C'est subtilement mieux que de renvoyer une instance statique car vous n'avez pas à allouer une seule fois. C'est subtilement mieux que de renvoyer un null statique car il est moins détaillé du côté de l'appel. La seule chose que l'appelant peut faire est de donner null.

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

public sealed class None {
    private None() { }
}
Gru
la source