本文介绍在.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代码块进行垃圾清理 这个例子恰恰适合代码看上去很容易理解 并且它不会太难~