[读行者][学习LinqExpression和Reflection(Emit)]阅读TypeBuilderSample之ExampleFromTheArticle
前言
关于”读行者“
俗语有云:"读万卷书,行万里路“。多读一些优秀代码,不仅可以锻炼我们读代码的能力(便于维护或相互交流),还可以吸取很多我们成长所需的知识点。多读,才能开阔我们的眼界,才能在我们小有所成沾沾自喜时提醒我们:前面的路还很长。
阅读是一种探索式的学习,你可以针对demo中的知识点有选择的研究;如果我们阅读的是一个project,还可以断点调试,更改前后条件,验证自己对API的猜想和加深理解。这些都比直接查阅API文档和看书效果好很多。另外,如果觉得对自己有帮助和启发的代码,还可以存下来,作为自己的积累,供今后查阅和调用。
如果有时间,我将不定期分享一些读书笔记,放在此类别下。如果有兴趣,不妨一起”读“吧。
Demo 地址: Fluent Method and Type Builder(自己下载)
研究目的: 进阶学习Linq.Expression和System.Reflection和System.Reflection.Emit。 同时这个工具在动态创建方法和类型时(如AOP),也十分有用。
点1:为什么方法中:
string fileName = null; var method = new FluentMethodBuilder(typeof(void)). AddParameter(() => fileName).
的表达式“() => fileName”, 在AddParameter中访问时,表达式的类型是MemberExpression并且 Expression.Member.MemberType=MemberType.Field? 'fileName'明明是一个局部变量呀?
答案:见StackOverFlow
class C { void M() { int x = 1; Func<int, int> f = y=>x+y; } }
实际上被编译成了:
class C
{
private class DisplayClass
{
public int x;
public int AnonymousMethod(int y)
{
return this.x + y;
}
}
void M()
{
C.DisplayClass d = new C.DisplayClass();
d.x = 1;
Func<int, int> f = d.AnonymousMethod;
}
}
点2: MemberExpression的基本属性:MSDN
Member: 获取表达式指向的属性或字段 [和System.Reflection第一次交互,返回类型为System.Reflection.MemberInfo]
Expression: 获取包含表达式指向的属性或字段的object
Type: 表达式里的委托的类型
NodeType: 对表达式的类型的细分,在二元操作表达式时需要用到以细分为加,减等。
断点调试中发现:
Expression: ()=>xxx.fileName NodeType是 Lambda
Expression.Body xxx.fileName NodeType是MemberAccess
Body.Expression xxx NodeType是Constant
((ConstantExpression)Body.Expression).Value可以访问xxx对象,通过watch窗口,可以查看这个匿名类的属性,证明1中的结论)
点3: ParameterExpression
IsByRef: 是否是引用类型
Name: 参数名称
产生: Expression.Parameter(fieldType, fieldName)或 Expression.Variable(fieldType, fieldName)
额外学习点:
a.生成ref参数类型: fieldType = fieldType.MakeByRefType();
b.装箱类型: fieldType = typeof(Box<>).MakeGenericType(fieldType); (联想typeof(Nullable<>).MakeGenericType)。(如果是要从泛型类型”Box<x>“中获取Box<>类型呢?其实代码中有)
点4: Using的实现
var result = Expression.Block ( new ParameterExpression[]{otherVariable}, Expression.Assign(otherVariable, _usingValue), Expression.Assign(_usingVariable, otherVariable), Expression.TryFinally ( base._CompileToExpression(), Expression.IfThen ( Expression.NotEqual(otherVariable, Expression.Constant(null)), Expression.Block ( Expression.Assign(_usingVariable, Expression.Constant(null, _usingVariable.Type)), Expression.Call(otherVariable, _disposeMethod) ) ) ) );
可以看见Using语句的实现,在LinqExpression中本质上使用的是TryFinally(expBody,expFinally)。
额外知识点:
a. Expression可以把语句组装起来一起执行;
b. 赋值操作和If语句的实现: Expression.Assign和Expression.IfThen。 Demo中_usingValue的类型为MemberAccess, 即xxx.StreamReader;
c. 和System.Reflection的第二次交互: Expression.Call(varableExp, methodInfo)。
点5: 循环语句的实现
var innerBody = base._CompileToExpression(); var result = Expression.Block ( Expression.Label(ContinueTarget), innerBody, Expression.Goto(ContinueTarget), Expression.Label(BreakTarget) ); return result;
可以发现,循环语句本年是通过GoTo实现的,在循环体的入口和出口创建两个Label, 执行完后让程序跳转到循环体的入口;如果执行过程中要中断程序,刚跳转至循环语句的出口。
那么,在innerBody中,必然有跳转到Break处的语句,这可以从Break()方法看到:
_IBlockBuilder instance = this; while(instance != null) { _ILoop loop = instance as _ILoop; if (loop != null) { var gotoExpression = Expression.Goto(loop.BreakTarget); var statement = new _Expression(gotoExpression); _statements.Add(statement); return _this; } instance = instance.Parent as _IBlockBuilder; }
其流程也需要注意一下: 从当前的Block级语句中逐级往上遍历,利用Goto BreakTarget中断离当前块最近的一级Loop循环。
点6: Expression Visitor
在FluentMethodBuilder的372行有这样一句:
body = _visitor.Visit(body);
大致看下其中的代码,尤其是VisitMember方法,好像就是在根据节点在上下文中找对应的表达式。不要它行不行?肯定不行,否则作者不会写它。
那么它到底是干啥的呢?
先把它注了看看:再F5,会报一个错误,差不多是说streamreader的参数path不能为null。好像是赋的值没有传过去,但具体这又是为什么呢?
取消先前的注释,打个断点在"visit"语句之前和之后,查看一下body的DebugView,对比一下可以发现(以第5行代码为例):
//visit之前 $var1 = .New System.IO.StreamReader(.Constant<TypeBuildingSample.Program+<>c__DisplayClass0>(TypeBuildingSample.Program+<>c__DisplayClass0).fileName) //visit之后 $var1 = .New System.IO.StreamReader($fileName);
这就是为什么不被visit,执行method时会报参数为null的错误的元凶了。因为之前使用的仍然是匿名类的fileName字段,而非我们传进去的参数!
再想想之前的代码:Using(() => streamReader, () => new StreamReader(fileName)),把局部变量传给了StreamReader作参数,不visit当然会出现那样的语句。
那么不使用局部变量呢?也不行,因为我们在一个方法的外部无法访问它自身的局部变量,所以作者先以上级方法的局部变量代替之,然后再把用Visitor把它恢复为“真正的局部变量”,不用再依赖上级方法中的局部变量而存在了。
所以ExpressionVisitor到底是干啥的,从这里可以看出:它可以遍历和修改Linq Expression中的子元素(语法树的子节点)。
点7: 连缀写法的实现及整体实现
首先是设计模式,整体使用组合模式, 产生树状体系。子节点实现了ICompile接口,每生成一个新的节点,都会添加到父级节点的statements中,最终遍历至根结点,生成总的 statements。如果我们正向划分也会发现:一个类可以划分为属性,方法,字段,事件的代码等,方法的代码又可以划分为循环块,条件块,using 块等,这些块又可以相互嵌套。。。
连缀写法的实现是通过EndXXX()方法实现的,通过此方法将子节点的上级节点返回,这样可以继续调用父级节点的成员而不必中断语句。