[读行者][学习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()方法实现的,通过此方法将子节点的上级节点返回,这样可以继续调用父级节点的成员而不必中断语句。

 

posted @ 2014-09-19 16:40  Caption  阅读(439)  评论(0编辑  收藏  举报