.NET Expression 从零开始四:结构化异常处理(SEH)

本文介绍在.NET Expression如何进行“结构化异常(SEH)”的处理,涵括对异常处理、抛出、清理等代码块的应用 与C#语法层面的try-catch-finally在表达式中的书写方式,那么如何捕获一个异常同时可以获取这个异常的错误信息呢?即需求“$exception”指针 对于调试而言这类的诉求是很必要的 毕竟你可以通过它捕获到的错误信息与堆栈跟踪 可以大致上确定到具体在哪一个位置发生了错误 

        ParameterExpression e = Expression.Parameter(typeof(DivideByZeroException));
            Action f = Expression.Lambda<Action>(
                Expression.TryCatch(
                    Expression.Block(
                        Expression.Divide(
                            Expression.Constant(1),
                            Expression.Constant(0)
                        ),
                        Expression.Constant(null)
                    ),
                    Expression.Catch(
                        e,
                        Expression.Block(
                            Expression.Call(
                                typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Property(e, "Message")
                            ),
                            Expression.Constant(null)
                        )
                    )
                )
            ).Compile();
            f();
上述代码在.DebugView中的代码结构形式如下
.Lambda #Lambda1<System.Action>() {
    .Try {
        .Block() {
            1 / 0;
            null
        }
    } .Catch (System.DivideByZeroException $var1) {
        .Block() {
            .Call System.Console.WriteLine($var1.Message);
            null
        }
    }
}
在C#中直接编写“1 / 0”代码表达式是无法成立的 但你可以通过定义一个变量为0 然后在用1除以这个变量 但它会抛出一个“DivideByZeroException”试图除以零

的异常 但在.NET Expression And MSIL中,允许直接除以零的

note: 值得注意的地方在于对于.NET Expression中进行异常处理 那么对于try、catch、finally代码块返回类型必须相同 这里的返回类型与函数返回值概念是不

同的 但它在某类情况下的确可以被认为是函数返回值 具体根据TRY类表达式形态而定

            ParameterExpression e = Expression.Parameter(typeof(DivideByZeroException));
            Expression.Lambda<Action>(
                Expression.TryCatch(
                    Expression.Block(
                        Expression.Divide(
                            Expression.Constant(1),
                            Expression.Constant(0)
                        )
                    ),
                    Expression.Catch(
                        e,
                        Expression.Block(
                            Expression.Call(
                                typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Property(e, "Message")
                            )
                        )
                    )
                )
            );
上述代码是一个很形象的例子,它无法被组合成有效的.NET Expression,如果你在编写TRY类表达式的过程中 不指定或者指定不相同的返回类型时则会引发

一个严重的参数错误, 即“System.ArgumentException:“catch 体与 try 体的类型必须相同。”

那么如何在代码中主动抛出异常?在很多情况下为了保证函数的可靠性 我们采取一些“if-when-throw”或者软件契约的防御性编程代码 那么对于编写动态表达

式而言也是相同的 它是用于提高接口的可见性、可维护性而提出的 事实上.NET Expression远远没有C#/VB.NET/C++/JAVA代码那么易于调试

            ParameterExpression str = Expression.Parameter(typeof(string));
            Expression.Lambda<Action<string>>(
                Expression.IfThen(
                    Expression.OrElse(
                        Expression.Equal(str, Expression.Constant(null)),
                        Expression.LessThanOrEqual(Expression.Property(str, "Length"), Expression.Constant(0))
                    ),
                    Expression.Throw(Expression.Constant(new ArgumentException()))
                ),
            str).Compile()(string.Empty);

上面的代码则是一个相对想象的例子,典型的“if-when-throw”即“如果-那么-抛出”模式防御性编程 它要求输入到函数的参数不可以是空同时长度大于零 否则它会抛出一个参数错误的异常到函数的调用方 对于在.NET Expression中抛出异常与C#中是相同的 严格要求被抛出的异常类型必须是System::Exception的派生类型

那么如何使用try-finally代码块?即使用保护代码块与清理代码块,实际上与try-catch是相同的 但有一些不同之处在于try可以拥有多个catch但只允许单个finally 不过try-finally这类代码块很容易遇见代码陷阱 即无法触发finally清理代码块 但一般在安全的代码(无异常抛出)中用于业务代码与垃圾清理代码的分离 有一个很形象的例子则是C#中的using语句代码块 它实际上是一个标准的try-finally代码块 using代码块内执行try内的内容 结束后执行finally中使用“IDisposable”中的Dispose函数进行垃圾清理

            Expression.Lambda<Action>(
                Expression.TryFinally(
                    Expression.Block(
                        Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("try"))
                    ),
                    Expression.Block(
                        Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("finally"))
                    )
                )
            ).Compile()();
但显然在某些情况下上述的try类处理无法满足要求 假设我们希望发生了异常又期望它能够得到正确处理的 同时还需要回收这些作业的废弃资源那么应该如何去做?而这类情况的恰恰需要的场景会常常遭遇 此示例不包含$exception指针的显示声明 即无法在catch块中获取到异常信息实例

            Expression.Lambda<Action>(
                Expression.TryCatchFinally(
                    Expression.Block(
                        Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("try")),
                        Expression.Throw(Expression.Constant(new InvalidOperationException()))
                    ),
                    Expression.Block(
                        Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("finally"))
                    ),
                    Expression.Catch(typeof(Exception),
                        Expression.Block(
                            Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }), Expression.Constant("catch"))
                        )
                    )
                )
            ).Compile()();
上述代码会经理三个不同的代码块 它在安全代码块中出现了一个“无效操作”的异常被catch捕获处理后 然后转移到finally代码块进行垃圾清理 这个例子恰恰适合代码看上去很容易理解 并且它不会太难~


猜你喜欢

转载自blog.csdn.net/liulilittle/article/details/73187881