2

其他一些高级语言,如 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
}

有什么规范的方法可以做到这一点,或者有什么好的理由不这样做?

编辑:

throw new Exception()实际上,直到发布此内容后,我才尝试在 C# 7 中使用as a value。这就是答案,或多或少。我会留下这个,以防将来人们搜索 C# 相当于 Perl 6 的Failure类或 Haskell 的error.

4

3 回答 3

6

C# 7.0 支持throw表达式

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

没有惰性求值,但您不再需要您的辅助方法。

于 2018-08-10T08:29:13.870 回答
3

我怀疑您正在寻找在 C# 7 中添加的throw 表达式。

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

懒惰的评价

对于惰性评估,您必须返回一个函数或一个 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# 提供惰性计算,它还返回一个具有更方便语法的惰性计算:

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#

于 2018-08-10T08:32:57.290 回答
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);
    }
}

也可以创建一个惰性 throw 表达式,该表达式仅在转换为目标值类型时才会抛出。使用您的判断是否以使代码不那么安全的方式使用此类(从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
    }
}

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

于 2018-08-13T04:12:41.657 回答