有没有办法将“ throw new Exception()”挤进一个对象?

时间:2018-08-10 08:25:41

标签: c# exception functional-programming

Haskell和Perl 6等其他一些高级语言提供了语法糖,即使在语法要求对象的地方,该糖也允许引发异常。当使用该值时,它的行为就好像它变成了引发的异常(在以下非常人为的示例中将立即发生):

enum BuildMode { Debug, MemoryProfiling, Release };

bool IsDebugMode(BuildMode mode)
{
    return mode == BuildMode.Debug ? true
        : mode == BuildMode.MemoryProfiling ? true
        : mode == BuildMode.Release ? false
        : ThrowException<bool>("Unhandled mode: " + mode);
}

以上帮助器允许从允许值而不是语句的位置引发异常。我可以按如下方式编写此函数,尽管它没有Haskell或Perl 6代码那么酷,因为它没有惰性的评估:

T ThrowException<T>(string message)
{
#line hidden
    throw new Exception(message);
#line default
}

是否有任何规范的方法可以做到这一点,或者有充分的理由不这样做?

编辑:

在发布此代码之前,我实际上并没有尝试在C#7中使用throw new Exception()作为值。或多或少,这就是答案。如果将来有人搜索与Perl 6的Failure类或Haskell的error类等效的C#,我将不做介绍。

3 个答案:

答案 0 :(得分:6)

C#7.0支持throw expressions

return mode == BuildMode.Debug ? true
    : mode == BuildMode.MemoryProfiling ? true
    : mode == BuildMode.Release ? false
    : throw new Exception("Unhandled mode: " + mode);

没有偷懒的评估方法,但是您不再需要辅助方法。

答案 1 :(得分:3)

我怀疑您正在寻找C#7中添加的throw expressions

return mode == BuildMode.Debug ? true
    : mode == BuildMode.MemoryProfiling ? true
    : mode == BuildMode.Release ? false
    : throw new Exception(...);

最常见的用法之一是用于空参数验证

var nameValue = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "New name must not be null");

惰性评估

对于惰性计算,您必须返回 function 或Lazy:

Lazy<bool> IsDebugMode(BuildMode mode)
{
    bool isDebug() 
    {
        return mode == BuildMode.Debug ? true
            : mode == BuildMode.MemoryProfiling ? true
            : mode == BuildMode.Release ? false
            : throw new Exception(...);
   }

    return new Lazy<bool>(isDebug);
}

并将其用作:

var isDbg=IsDebugMode(someMode);
.....
.....
//Will throw here!
if (isDbg.Value)
{
    ...
}

F#提供了lazy computations,它也以更方便的语法返回了Lazy:

let isDebugMode mode = 
    match mode with
    | BuildMode.Debug -> true
    | BuildMode.Release -> false
    | _ -> failwith "Ouch!"

let isDbg = lazy (isDebugMode someMode)
...
//Can throw here
if (isDbg.Force() then 
   ...

使用Func进行相同的惰性评估:

Func<bool> IsDebugMode(BuildMode mode)
{
    bool isDebug() 
    {
        return mode == BuildMode.Debug ? true
            : mode == BuildMode.MemoryProfiling ? true
            : mode == BuildMode.Release ? false
            : throw new Exception(...);
   }

    return isDebug;
}

用作功能:

var isDbg=IsDebugMode(someMode);
...
//Can throw here
if(isDbg())
{
    ...
}

切换表达式

C#8将添加可能 如下的开关表达式:

return mode switch {
    BuildMode.Debug           => true,
    BuildMode.MemoryProfiling => true,
    BuildMode.Release         => false,
    _ => throw new Exception (...)
};

惰性函数可能如下所示:

Lazy<bool> IsDebugMode(BuildMode mode)
{
    bool isDebug() =>
        mode switch {
            BuildMode.Debug           => true,
            BuildMode.MemoryProfiling => true,
            BuildMode.Release         => false,
            _ => throw new Exception (...)
    };

   return new Lazy<bool>(isDebug);
}

看起来更像F#

答案 2 :(得分:0)

给出的答案是正确的,但是我将添加一个答案(针对我自己的问题),以指出一种在C#6及更低版本中模拟throw表达式的理想方法。具有相同的名称和相似的API对于前向兼容性很有用,因此这是我确定的帮助程序类:

public class ThrowExpression<T>
{
    public ThrowExpression(string message)
    {
#line hidden
        throw new Exception(message);
#line default
    }

    // never used, but makes the compiler happy:
    public static implicit operator T(ThrowExpression<T> obj)
    {
        return default(T);
    }
}

还可以创建一个惰性抛出表达式,该表达式仅在将其强制转换为目标值类型时才会抛出。对于是否以减少代码类型安全性的方式使用此类(从object到目标类型进行广播),请做出判断。

public class ThrowExpression<T>
{
    private string message;
    public ThrowExpression(string message)
    {
        this.message = message;
    }

    public static implicit operator T(ThrowExpression<T> obj)
    {
#line hidden
        throw new Exception(message);
#line default
    }
}

各种修饰都是可能的,例如接受不同的异常类型作为参数或通过附加的模板参数,但是我打算保持简单,直到需要这些增强。