有没有办法将“抛出新的异常()”挤压到一个对象中?

Posted

技术标签:

【中文标题】有没有办法将“抛出新的异常()”挤压到一个对象中?【英文标题】:Is there a way to squeeze "throw new Exception()" into an object? 【发布时间】:2019-01-17 19:31:35 【问题描述】:

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

【问题讨论】:

恐怕在 c# 中没有办法做到这一点,但我很高兴被证明是错误的 @MatějŠtágl wellll .... 抛出表达式 @PanagiotisKanavos 如果 throw 表达式是答案(它们可能是),那么当 OP 想要它们抛出时我完全误解了;我以为他们希望他们推迟投掷 @MarcGravell:我将他的问题理解为 “我的(非惰性)辅助方法是否有规范替换?哦,顺便说一句,Haskell 还允许对异常进行惰性评估,我们也可以这样做吗?”. @MarcGravell 我仍在尝试将问题转换为熟悉的 F# 和 C# 术语。另外,代码没有显示任何延迟评估的尝试。它必须返回一个函数而不是实际值 【参考方案1】:

C# 7.0 支持throw expressions:

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

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

【讨论】:

【参考方案2】:

我怀疑您正在寻找在 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#

【讨论】:

【参考方案3】:

给出的答案是正确的,但我将添加一个答案(对我自己的问题)以指出在 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
    

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

【讨论】:

以上是关于有没有办法将“抛出新的异常()”挤压到一个对象中?的主要内容,如果未能解决你的问题,请参考以下文章

4.异常捕获后再次抛出

重载与重写问题

在PHP Try Catch块中抛出异常

异常处理

cinema4D - 使用带挤压样条的Boole对象

Java 重写必须满足的条件