可以使用运算符??并抛出新异常吗?

34

我有几个方法实现以下功能:

var result = command.ExecuteScalar() as Int32?;
if(result.HasValue)
{
   return result.Value;
}
else
{
   throw new Exception(); // just an example, in my code I throw my own exception
}

我希望我能像这样使用运算符 ??

return command.ExecuteScalar() as Int32? ?? throw new Exception();

但它会生成编译错误。

我是否可以重写我的代码,还是只有一种方法可以做到这一点?


我希望能够使用return this as T ?? that as T ?? other as T ?? throw new NotSupportedException();来返回结果。但现在我必须使用一个临时变量,检查是否为空,然后返回这个临时变量。这样做有点丑陋。 - Tergiver
在Connect() 2016上,有一个演示展示即将推出的C# 7的这个特性。 - Thomas
2
似乎你在C#7中得到了想要的东西 http://structuredsight.com/2016/09/01/c-7-additions-throw-expressions/ - Svek
5个回答

65

对于C# 7

在C# 7中,throw变成了一个表达式,所以可以直接使用问题描述中的代码。

对于C# 6及更早版本

在C# 6及更早版本中,你不能直接这样做 - ??的第二个操作数必须是一个表达式,而不是一个throw语句。

如果你真的只是想找到一个简洁的选项,有几个替代方案:

你可以写成:

public static T ThrowException<T>()
{
    throw new Exception(); // Could pass this in
}

然后:

return command.ExecuteScalar() as int? ?? ThrowException<int?>();

我真的不建议你这样做...这很糟糕,也不符合惯用语。

那么扩展方法怎么样:

public static T ThrowIfNull(this T value)
{
    if (value == null)
    {
        throw new Exception(); // Use a better exception of course
    }
    return value;
}

然后:

return (command.ExecuteScalar() as int?).ThrowIfNull();

另一种替代方案(再次是扩展方法):

public static T? CastOrThrow<T>(this object x) 
    where T : struct
{
    T? ret = x as T?;
    if (ret == null)
    {
        throw new Exception(); // Again, get a better exception
    }
    return ret;
}

Call with:

return command.ExecuteScalar().CastOrThrow<int>();

这有点丑陋,因为你不能指定 int? 作为类型参数...


我认为这是因为你没有以Tony的身份回答。无论如何,我已经为你反驳了。你在这里走上了正确的轨道,但我认为有一种更好、更普遍的技巧,我会将其作为我的回答添加进来(尽管可能会被踩)。 - philsquared
Jon,你能否使用泛型参数约束来创建两个CastOrThrow<T>方法,一个用于值类型/结构体,另一个用于引用类型?前者将使用T?,而后者将使用T - Adam Maras
@Adam:不幸的是,您不能有两种方法,其签名唯一的区别是输出类型和/或泛型约束。 - LukeH
1
扩展方法!简直太棒了 ThrowIfNull +1 - Alex Bagnolini
嗨@JonSkeet,您可能需要更新此答案,并提供一个提示,即在C#7中添加了执行OP想要的操作的功能:http://structuredsight.com/2016/09/01/c-7-additions-throw-expressions/ - Mafii
@Mafii:在这种情况下我会这么做,但我不打算去检查我所有7年前的答案... - Jon Skeet

9
正如所说的那样,你不能使用??运算符来实现这一点(好吧,不使用一些似乎与你使代码更清晰的目标不符的扭曲方法是行不通的)。
当我看到这种模式出现时,我立刻想到了Enforcements。最初来自C++世界,它们在C#中也可以很好地应用,尽管大多数情况下可能不太重要。
这个想法是你采取类似下面这样的东西:
if( condition )
{
  throw Exception;
}

并将其转换为:
Enforce<Exception>( condition );

你可以通过默认异常类型来进一步简化。

更进一步地,您可以编写一组Nunit风格的方法来检查不同的条件,例如:

Enforce<Exception>.NotNull( obj );
Enforce<Exception>.Equal( actual, expected );
Enforce<Exception>.NotEqual( actual, expected );

或者,更好的方法是提供一个期望的lambda函数:

Enforce<Exception>( actual, expectation );

非常棒的是,一旦完成了这个步骤,你可以返回实际的参数并强制执行内联。
return Enforce( command.ExecuteScalar() as Int32?, (o) => o.HasValue ).Value;

... 而这似乎是最接近你需要的。

我之前已经实现过这个功能。有一些小问题,比如如何通用地创建带参数的异常对象 - 选择有些(我当时选择了反射,但通过将工厂作为额外参数传递可能更好)。但总的来说,这非常简单并且可以真正简化很多代码。

我的待办事项清单上有一项是创建一个开源实现。


1
这里不使用扩展方法有什么原因吗?对我来说,“return (command.ExecuteScalar() as int?).Enforce(x => x.HasValue);”读起来更好一些...虽然在那个点上改变名称可能是值得的。我确实喜欢使用谓词的想法。 - Jon Skeet
主要是因为我最初在使用C#2时就已经这样做了;-) 我最初没有使用谓词的lambda表达式,原因也是如此,但是将其移动到那里是易如反掌的。我认为扩展方法可以很好地工作,但需要稍微调整一下。 - philsquared

4
如果您只想在返回值不是Int32时出现异常,请执行以下操作:
return (int)command.ExecuteScalar();

如果你想抛出自定义异常,我建议你这样做:
int? result = command.ExecuteScalar() as int?;
if (result == null) throw new YourCustomException();
return result.Value;

是的,会抛出异常。但也许无效转换异常不是应该抛出的合适异常;如果该命令没有返回值,则可能需要一个特定于应用程序的异常。 - Adam Maras
@Adam:我真的不认为这个应该被投反对票!问题中的示例代码只抛出了一个普通的“Exception”。与普通的“Exception”没有额外细节信息相比,“InvalidCastException”或“NullReferenceException”更加适当和有意义。 - LukeH
@Phil:奇怪,之前不允许我投票,现在可以了。我收到了一个关于投票过期的错误提示,说除非修改答案,否则无法更改。总之,我已经更改了投票。 - Adam Maras
你可以使用Execute-Around来重新映射异常(这将涉及到另一个方法,其目的是回调调用您的方法,包装在try-catch块中,抛出您选择的异常。但如果您这样做,也可以使用我的Enforcements建议:-) - philsquared
大家好。我编辑了我的帖子。正如Adam所提到的,我只是举了一个例子写了一个异常。我的原始代码会抛出我的自定义的FinanceResultNotFoundException :) - abatishchev
显示剩余5条评论

2
你不能在空值合并运算符的右侧抛出异常。原因是该运算符的右侧需要是一个表达式,而不是语句。
空值合并运算符的工作方式如下:如果运算符的左值为空,则返回它;否则,返回运算符右侧的内容。throw关键字不返回值,因此无法用于运算符的右侧。

1

你不能这样做的原因是:

return command.ExecuteScalar() as Int32? ?? throw new Exception();

因为抛出异常是一种语句,而不是表达式。
如果你只是想稍微简化一下代码,也许可以这样做:
var result = command.ExecuteScalar() as Int32?;
if(result.HasValue) return result;
throw new Exception();

不需要 else。


只有当包含此返回语句的函数返回一个对象时,此方法才有效。任何其他返回类型都会导致编译器错误,因为空合并运算符的左右表达式类型是不同的。 - Adam Maras
我认为,这就是为什么我用了“可能”的原因。我已经把我的回答中的那部分删除了。 - Josh Smeaton
1
我更喜欢反转对值的检查,如果它没有值,则抛出异常。听起来更合乎逻辑。 - Dykam

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