在C#泛型中无效?


94

我有一个接受请求并提供响应的通用方法。

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请求)中做空检查?
詹姆斯

请注意,您确实需要使用返回值。您可以调用诸如过程之类的函数。DoSomething(x);而不是y = DoSomething(x);
Olivier Jacot-Descombes 2012年

1
我想你的意思是说,“请注意,你就不会需要使用返回值。” @
OlivierJacot

Answers:


95

您不能使用void,但是可以使用object:这样做有一点不便,因为您想要的void功能需要返回null,但是如果它统一了您的代码,则应该付出很小的代价。

这种无法void用作返回类型的原因至少部分造成了泛型委托的Func<...>Action<...>族之间的分裂:如果有可能返回void,那么一切Action<X,Y,Z>将变得简单Func<X,Y,Z,void>。不幸的是,这是不可能的。


45
(笑话)他仍然void可以使用避免使用的方法返回return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));。但是,它将是一个有框的空白。
Jeppe Stig Nielsen 2014年

由于C#支持更多的功能编程功能,因此您可以查看FP 中代表的单元void。并且有充分的理由使用它。在F#(仍然是.NET)中,我们已unit内置。

88

不,不幸的是没有。如果void是“真实”类型(例如unit在F#中),生活在许多方面将变得简单得多。特别是,我们既不需要Func<T>和,也不需要Action<T>- ,而只需存在Func<void>代替ActionFunc<T, void>而不是Action<T>等。

这也将使异步变得更简单-完全不需要非泛型Task类型-我们只需拥有Task<void>

不幸的是,这不是C#或.NET类型系统的工作方式。


4
Unfortunately, that's not the way the C# or .NET type systems work...您让我充满希望,也许事情最终会像那样运作。您的最后一点是不是意味着我们不可能以这种方式运作?
戴夫·库西诺

2
@Sahuagin:我怀疑不是-这将是一个很大的变化。
乔恩·斯基特

1
@stannius:不,我认为这比带来不正确代码的麻烦更多。
乔恩·斯基特

2
单元引用类型比空值类型代表“ void”是否有优势?空值类型似乎更适合我,它不包含任何值且不占用空间。我想知道为什么没有这样实现void。从堆栈中弹出或不弹出都不会有任何区别(谈论本机代码,在IL中可能有所不同)。
Ondrej Petrzilka,

1
@Ondrej:我之前尝试过使用空结构,但是最终在CLR中它不是空的...当然,这可能是特殊情况。除此之外,我不知道我会建议哪个。我没有想太多。
乔恩·斯基特

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/zh-cn/library/system.reactive.unit%28v=VS.103%29.aspx


System.Reactive.Unit是一个很好的建议。从2016年11月开始,如果您有兴趣获得尽可能小的Reactive框架,因为您除了使用Unit类之外没有使用其他任何东西,请使用nuget软件包管理器Install-Package System.Reactive.Core
DannyMeister

6
将ThankYou重命名为“ KThx”,它是一个赢家。^ _ ^Kthx.Bye;
LexH

只是为了检查我没有丢失任何东西.. Bye getter不会在直接访问中添加任何重要内容,对吗?
安德鲁(Andrew)

1
@Andrew不需要再见,除非您想遵循C#编码的精神,即您不应该公开裸露的字段
Trident D'Gao

16

您可以简单地使用Object其他人的建议。还是Int32我看到的一些用途。using Int32引入了一个“虚拟”数字(use 0),但是至少您不能将任何大的奇异对象放入Int32引用中(结构已密封)。

您还可以编写自己的“ void”类型:

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

MyVoid允许引用(这不是静态类),但只能是null。实例构造函数是私有的(如果有人尝试通过反射来调用此私有构造函数,则会向他们抛出异常)。


由于引入了值元组(2017,.NET 4.7),使用structValueTuple(0-tuple,non-generic variant)而不是这样的struct是很自然的MyVoid。它的实例有一个ToString()return "()",因此它看起来像一个零元组。从当前的C#版本开始,您不能()在代码中使用标记来获取实例。您可以使用default(ValueTuple)或只是default(当可以从上下文中推断类型时)。


2
另一个名称是空对象模式(设计模式)。
Aelphaeis 2014年

@Aelphaeis这个概念与空对象模式有些不同。在这里,关键是要具有可与泛型一起使用的某种类型。使用空对象模式的目标是避免编写一种方法,该方法返回null以表示特殊情况,而返回具有适当默认行为的实际对象。
安德鲁·帕尔默

8

我喜欢上面的Aleksey Bykov的想法,但是可以简化一下

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

我认为没有明显的原因为何Nothing.AtAll不能只给出null

同样的想法(或Jeppe Stig Nielsen提出的想法)也非常适合用于类型化类。

例如,如果类型仅用于描述过程/函数的参数,而该过程/函数作为参数传递给某个方法,则它本身不接受任何参数。

(您仍然需要制作一个虚拟包装器或允许使用可选的“ Nothing”。但是恕我直言,使用myClass <Nothing>可以很好地使用类)

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

要么

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

1
该值null的含义是缺少或缺少object。仅仅具有一种价值Nothing意味着它永远不会像其他任何东西。
LexH

这是个好主意,但我同意@LexieHankins。我认为最好是“什么都不做”是Nothing存储在私有静态字段中并添加私有构造函数的类的唯一实例。那空仍然是可能的事实是恼人的,但将得到解决,希望用C#8
柯克沃尔

从学术上讲,我确实了解这种区别,但是在区别很重要的情况下,这将是一个非常特殊的情况。(想象一个通用可空类型的函数,其中“ null”的返回用作特殊标记,例如作为某种错误标记)
Eske Rahn

4

void,尽管是类型,但仅作为方法的返回类型有效。

无法绕开的限制void


1

我目前要做的是使用私有构造函数创建自定义密封类型。这比在c-tor中引发异常更好,因为您不必在运行时才弄清楚情况是不正确的。它比返回静态实例更好,因为您不必分配一次。它比返回静态null更好,因为它在调用方较不冗长。呼叫者唯一可以做的就是给null。

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

public sealed class None {
    private None() { }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.