C#函数是否有标准的“永不返回”属性?

时间:2010-01-04 12:13:58

标签: c# exception attributes return-type

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

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);
}

我担心自定义属性不起作用,因为为了我的目的,我需要编译器的合作。

10 个答案:

答案 0 :(得分:31)

为什么不将其改为

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

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

答案 1 :(得分:22)

没有。我建议你更改你的第一个函数的签名以返回异常而不是抛出它,并将throw语句保留在你的第二个函数中。这将使编译器保持愉快,并且闻起来也不那么糟糕。

答案 2 :(得分:8)

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

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'.");
    }
}

答案 3 :(得分:5)

您可以使用泛型声明函数返回“任何内容”来指示“永不返回”:

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#等语言,基于principle of explosion,也称为“ex falso quodlibet”。原因是:如果一个函数永远不会返回,那么我们可以对它的返回值做出我们想要的任何神奇假设,因为这样的值永远不会存在。这里,调用者(foo)假定ThrowException将返回一个int。

一些小缺点:

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

正如其他答案所说的那样,你可能最好不要抛弃异常而不是扔掉它。

答案 4 :(得分:3)

不要将异常创建移交给另一个函数(即直接抛出),编译器不会抱怨。将异常抛出的“帮助”类型函数移交给浪费时间是浪费时间,除非该函数实际上是将值添加到异常处理中。

答案 5 :(得分:3)

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

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

答案 6 :(得分: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){}

答案 7 :(得分:1)

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

答案 8 :(得分:1)

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

答案 9 :(得分: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 

通过实施Wody 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和GCC中所希望的结果。空方法上的LLVM __builtin_unreachable()或MSVC __assume(0)__declspec(noreturn)