39

我有一种看起来像这样的方法:

void throwException(string msg)
{
    throw new MyException(msg);
}

现在如果我写

int foo(int x, y)
{
    if (y == 0)
        throwException("Doh!");
    else
        return x/y;
}

编译器会抱怨 foo “并非所有路径都返回一个值”。

有没有我可以添加到 throwException 的属性来避免这种情况?就像是:

[NeverReturns]
void throwException(string msg)
{
    throw new MyException(msg);
}

恐怕自定义属性不行,因为为了我的目的,我需要编译器的合作。

4

10 回答 10

33

为什么不把它改成

int foo(int x, y)
{
    if (y == 0)
        throwException("Doh!");
    return x/y;
}

这给出了相同的运行时结果,编译器不会抱怨。

于 2010-01-04T12:18:49.583 回答
23

不,我建议您更改第一个函数的签名以返回异常而不是抛出异常,并将 throw 语句保留在第二个函数中。这将使编译器感到高兴,并且闻起来也不那么难闻。

于 2010-01-04T12:18:31.330 回答
12

伯恩霍夫的回答是正确的。但是,如果您在实例化异常时尝试封装大量逻辑,那么您需要做的就是更改您的代码:

void throwException(string msg) {
    throw new MyException(msg);
}

对此:

Exception makeException(string msg) {
    return new MyException(msg);
}

然后您的调用代码将如下所示:

int foo(int x, y) {
    if (y == 0) {
        throw makeException("Doh!");
    }
    return x / y;
}

在所有其他条件相同的情况下,更喜欢功能代码而不是过程代码。重用和单元测试更容易。

编辑:

根据 Fred 的示例代码,这就是我要做的。它不是代码合约,但它仍然有效。

private int getVarID(string s_varID) {
    int varID;
    if(s_varID == "ILT") {
        return 123;
    } else if(s_varID == "TL") {
        return 456;
    } else if(s_varID == "FT") {
        return 789;
    } else if(int.TryParse(s_varID, out varID)) {
        return varID;
    } else {
        throw makeParseError("varID must be an integer or 'ILT', 'TL' or 'FT'.");
    }
}
于 2010-01-04T12:50:35.170 回答
10

中的[DoesNotReturn]属性System.Diagnostics.CodeAnalysis应该是您想要的。

参考:https ://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.doesnotreturnattribute?view=netcore-3.1

于 2020-01-03T17:16:19.647 回答
6

您可以通过使用泛型声明函数返回“任何东西”来指示“从不返回”:

T ThrowException<T>(string msg)
{
    throw new MyException(msg);
}

所以现在你可以写:

int foo(int x, int y)
{
    if (y == 0)
        return ThrowException<int>("Doh!");
    else
        return x/y;
}

这个成语用在HaskellF#等语言中,基于爆炸原理,也称为“ex falso quodlibet”。推理是这样的:如果一个函数永远不会返回,那么我们可以对它的返回值做出任何我们想要的神奇假设,因为这样的值永远不会存在。在这里,调用者 (foo) 假设 ThrowException 将返回一个 int。

一些小缺点:

  • 的实现ThrowException可以通过返回来规避这一点default(T)
  • 调用时必须指定返回类型ThrowException(Haskell 和 F# 可以推断)。
  • 这个成语在C#中非常少见,所以很多人都认不出来。您可能需要添加评论说明您在做什么。

正如其他答案所说,您最好返回异常而不是抛出异常。

于 2015-02-23T19:03:09.653 回答
4

不要将异常创建交给另一个函数(即直接抛出它),编译器不会抱怨。除非该函数实际上为异常过程增加了价值,否则将异常抛出交给“帮助”类型的函数是浪费时间。

于 2010-01-04T12:27:14.670 回答
2

你的功能

void throwException(string msg)
{
    throw new MyException(msg);
}

向代码添加零值,因此您的问题没有实际意义。另一方面,如果您想在整个课程中使用相同的消息引发错误并最大程度地减少代码重复,那么您应该这样做。

通常的做法是MyException针对这种特殊情况进行扩展并抛出:

public class HomerSimpsonException : MyException
{
   public HomerSimpsonException() : base ("DOH!!!"){
   }
}
int foo(int x, y)
{
    if (y == 0)
        throw new HomerSimpsonException();
    else
        return x/y;
}

即使这样,根据 Microsoft 扩展异常的规则,这还不够完整,您应该实现至少 4 个构造函数 - http://msdn.microsoft.com/en-us/library/ms182151%28VS.80%29.aspx,即:

  public NewException(){}
  public NewException(string){}
  public NewException(string, Exception){}
  protected or private NewException(SerializationInfo, StreamingContext){}
于 2010-01-04T12:36:32.083 回答
1

Bernhof 已经为您提供了一种避免编译器抱怨的方法。但是,还要注意您的堆栈跟踪将关闭(并且某些记录器库不会处理 util-classed-i-throw-exceptions-for-your-app 方法),这会使调试您的应用程序变得更加困难。

于 2010-01-04T12:34:59.587 回答
1

删除“else”关键字,无论如何它都是多余的,它会起作用;)

于 2010-01-04T12:36:12.630 回答
1

好吧,这是一个“相当”省力的实现。

工人阶级:

/// <summary>
/// Representation of an unreachable type, exposing a method to represent unreachable code.
/// </summary>
public static class Unreachable {

    /// <summary>
    /// Representation of unreachable code with return semantics.
    /// </summary>
    public static dynamic Code() {
        throw new NotImplementedException(@"Unreachable code was reached.");
    }
}

例子:

public object[] UnreachableCodeTest() {
    return Unreachable.Code();
}

反编译:

Offset  OpCode  Operand
0   ldsfld  System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
5   brtrue.s    -> (10) ldsfld System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
7   ldc.i4.0    
8   ldtoken System.Object[]
13  call    System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
18  ldtoken TestApp.Program
23  call    System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)
28  call    System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder::Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags,System.Type,System.Type)
33  call    System.Runtime.CompilerServices.CallSite`1<!0> System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>>::Create(System.Runtime.CompilerServices.CallSiteBinder)
38  stsfld  System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
43  ldsfld  System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
48  ldfld   !0 System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>>::Target
53  ldsfld  System.Runtime.CompilerServices.CallSite`1<System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>> TestApp.Program/<UnreachableCodeTest>o__SiteContainere6::<>p__Sitee7
58  call    System.Object TestApp.Unreachable::Code()
63  callvirt    !2 System.Func`3<System.Runtime.CompilerServices.CallSite,System.Object,System.Object[]>::Invoke(!0,!1)
68  ret 

通过实现带有 Fody 的东西、搜索此调用签名并将其替换为单个原始 ret(如果它允许您)或其他一些简单的可接受的低成本替代方案来进行优化。

(编辑)

当你修改我时,我将被迫解释为什么这是有效和有用的。

大多数情况下,这会进入一个类似于方法末尾的块,该方法具有逻辑上无法到达的路径;

#pragma warning disable 162
// ReSharper disable CSharpWarnings::CS0162
// ReSharper disable HeuristicUnreachableCode
return Unreachable.Code();
// ReSharper restore HeuristicUnreachableCode
// ReSharper restore CSharpWarnings::CS0162
#pragma warning restore 162

如果您以允许访问代码的方式修改本节上方的代码,您的测试将失败。如果你有下面的代码,那你就错了,编译器会让你知道。一旦超出初始成熟度,您将一起删除此代码块。这样做的主要目的是捕捉代码在应该无法访问的情况下。

在其他代码不应到达但编译器无法在逻辑上排除的情况下(例如,案例中的默认语句),您通常会抛出错误。如果要针对这种情况进行优化,则需要这种实现。

select( thisIsAlways123Never0OrGreaterThan3 ) {
    default: return Unreachable.Code();
    case 1: DoSomething(); break;
    case 2: DoSomethingElse(); break;
    case 3: return GetSomething();
}

default:要进行优化,以便使用这种最少编写的代码为路径发出最少的指令,您需要一个名为 Fody 的朋友。

理想的场景是类似于 C++ 开发人员在 GCC 和 LLVM__builtin_unreachable()或 MSVC__assume(0)__declspec(noreturn)空方法中所期望的结果。

于 2015-06-29T19:26:13.257 回答