C#泛型中的void是什么意思?

126

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

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 response,Treq request)中进行空值检查呢? - James
请注意,您不需要使用返回值。您可以像调用过程一样调用函数。例如:DoSomething(x); 而不是 y = DoSomething(x); - Olivier Jacot-Descombes
4
我认为您想说的是,“请注意,您不需要使用返回值。” @OlivierJacot-Descombes - zanedp
7个回答

127

您不能使用void,但可以使用object:这会带来一些不便,因为您原本的void函数现在需要返回null,但如果能统一您的代码,这应该是一个小代价。

无法使用void作为返回类型,至少部分地导致了泛型委托Func<...>Action<...>之间的分裂:如果可以返回void,所有的 Action<X,Y,Z>都可以变成简单的 Func<X,Y,Z,void>。不幸的是,这是不可能的。


57
他仍然可以从那些原本应该返回void的方法中返回void,使用return System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(void));。不过这将会是一个装箱后的void。 - Jeppe Stig Nielsen
3
随着 C# 支持更多函数式编程特性,你可以看一下 Unit,它代表了函数式编程中的 void。使用它是有充分理由的。在同样基于 .NET 的 F# 中,我们内建了 unit - joe
1
@joe 在这种情况下,我的做法是在需要的地方添加 public readonly struct Nothing {} - Dai

105

很遗憾,不是这样的。如果void是一个“真正”的类型(例如F#中的unit),在许多方面生活会变得简单得多。特别是,我们就不需要同时使用Func<T>Action<T>系列了——只需要Func<void>代替ActionFunc<T, void>代替Action<T>等。

这也会使异步操作更加简单——根本不需要非泛型的Task类型——我们只需要Task<void>

不幸的是,C#或.NET类型系统并不是这样工作的...


4
很遗憾,C#或.NET类型系统不是这样工作的。你让我满怀希望,可能最终事情会变成那样。你的最后一点是否意味着我们不太可能让事情变得那样? - Dave Cousineau
3
我怀疑不会 - 在这个时候进行这样的变化会是一个相当大的改变。 - Jon Skeet
1
@stannius:不,我认为这更多是一种不便,而不是导致代码错误的东西。 - Jon Skeet
2
使用单元引用类型来表示“void”是否比空值类型更有优势?对我来说,空值类型似乎更合适,它不包含任何值且不占用空间。我想知道为什么没有像这样实现void。从堆栈中弹出或不弹出它并没有什么区别(在本机代码中是这样,在IL中可能会有所不同)。 - Ondrej Petrzilka
2
@Ondrej:我以前尝试过使用空结构体,但在CLR中它最终并不是空的...当然可以特殊处理。除此之外,我不知道我会建议哪个;我没有仔细考虑过。 - Jon Skeet
显示剩余5条评论

29

以下是您可以做的事情。如@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


1
System.Reactive.Unit是一个不错的建议。截至2016年11月,如果您只使用Unit类而没有使用其他Reactive框架的内容,那么使用NuGet包管理器Install-Package System.Reactive.Core可以获得尽可能小的Reactive框架部分。 - DannyMeister
14
将"ThankYou"改名为"KThx",这会是一个赢家。 ^_^ Kthx.Bye; - LexH
只是为了确认我没有漏掉什么...Bye getter在这里与直接访问相比并没有添加任何重要的内容,对吗? - Andrew
1
@Andrew,您不需要拜拜getter(bye getter),除非您想遵循C#编码的精神,即不应该暴露裸字段。 - Trident D'Gao
您还可以从返回谢谢的方法中返回 null!,以节省一个字节的内存,如果您需要它。 - dimitar.bogdanov
显示剩余2条评论

19
您可以像其他人建议的那样简单地使用Object,或者像我看到有些人使用的Int32。使用Int32会引入一个“虚拟”数字(使用0),但至少您不能将任何大型和奇异的对象放入Int32引用(结构是密封的)。
您还可以编写自己的“void”类型:
public sealed class MyVoid
{
  MyVoid()
  {
    throw new InvalidOperationException("Don't instantiate MyVoid.");
  }
}

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


自从值元组被引入(2017年,.NET 4.7),使用结构体ValueTuple(0元组,非泛型变量)代替这样的MyVoid可能更自然。它的实例有一个返回"()"ToString(),所以看起来像一个零元组。在当前版本的C#中,您不能在代码中使用()令牌来获取实例。您可以使用default(ValueTuple)default(当类型可以从上下文中推断出时)。


2
另一个名称是空对象模式(设计模式)。 - Aelphaeis
3
@Aelphaeis 这个概念与空对象模式有些不同。这里的重点只是拥有某种类型能够用于泛型。空对象模式的目标是避免编写返回 null 表示特殊情况的方法,而是返回一个具有适当默认行为的实际对象。 - Andrew Palmer

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){
    ...
}

2
null 具有缺失或不存在 对象 的含义。仅有一个值的 Nothing 意味着它永远不会像其他任何东西一样。 - LexH
这是一个好主意,但我同意@LexieHankins的观点。我认为更好的做法是将“什么都没有”作为类Nothing的唯一实例存储在私有静态字段中,并添加一个私有构造函数。虽然null仍然可能存在,但这个问题将会得到解决,希望在C# 8中能够解决。 - Kirk Woll
从学术上讲,我确实理解这种区别,但只有在非常特殊的情况下才会有所区别。(比如一个通用可空类型的函数,其中返回值“null”被用作特殊标记,例如某种错误标记) - Eske Rahn

3

void作为一种类型,只能作为方法的返回类型。

无论如何都不能逾越这个void的限制。


3

我目前所做的是创建带有私有构造函数的自定义封闭类型。这比在构造函数中抛出异常更好,因为您不必等到运行时才能发现情况不正确。与返回静态实例相比,它略微更好,因为您甚至不必分配一次。它略微优于返回静态 null,因为在调用方面上不那么冗长。调用者唯一能做的就是提供 null。

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接