Lambda Expressions

无论是expression lambda还是statement lambda,都适用以下的描述:

Lambda Expressions – This is a shorthand for specifying a method. The C# compiler will translate each into either an anonymous method or a true System.Linq.Expressions.Expression. You really need to understand these to use Linq well. There are three parts: A parameter list, an arrow, and a method body.

 

1.匿名方法的例子:(匿名方法可以作为delegate直接传给接受Action<>,Func<>这些concrete delegate的地方,也就是编译器可以自动转换,但如果接受方需要的是Delegate这个抽象的类对象,比如Dispatch.Invoke方法,就要类型转换,这里有讨论这个问题的。)

new Action(() =>{…})

lambda表达式变成了 .method public hidebysig instance void <LoadData>b__8() cil managed

    <>c__DisplayClassa CS$<>8__localsb; (这里Reflector翻译的C#不准,看IL指令集,实际是newobj指令,把当前context创建出来)
      CS$<>9__CachedAnonymousMethodDelegate9 = new Action(CS$<>8__localsb, (IntPtr) this.<LoadData>b__8);
(调用了Lambda表达式形成的匿名方法)

 

2.转成Expressions的例子,生成了暴多的IL,应用了很多Expressons命名空间的类和方法:

h => h.CompanyCode == "1"  会变成 Expression<Func<T, bool>> expr

: ldtoken [CdcSoftware.Erp.RuntimeServices]CdcSoftware.Erp.RuntimeServices.Entities.Finance.SalesOrderHeader
    L_0012: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0017: ldstr "h"
    L_001c: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string)
    L_0021: stloc.3
    L_0022: ldloc.3
    L_0023: ldtoken instance string [CdcSoftware.Erp.RuntimeServices]CdcSoftware.Erp.RuntimeServices.Entities.Finance.SalesOrderHeader::get_CompanyCode()
    L_0028: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
    L_002d: castclass [mscorlib]System.Reflection.MethodInfo
    L_0032: call class [System.Core]System.Linq.Expressions.MemberExpression [System.Core]System.Linq.Expressions.Expression::Property(class [System.Core]System.Linq.Expressions.Expression, class [mscorlib]System.Reflection.MethodInfo)
    L_0037: ldstr "1"
    L_003c: ldtoken string
    L_0041: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    L_0046: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object, class [mscorlib]System.Type)
    L_004b: ldc.i4.0
    L_004c: ldtoken bool [mscorlib]System.String::op_Equality(string, string)
    L_0051: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
    L_0056: castclass [mscorlib]System.Reflection.MethodInfo
    L_005b: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Equal(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression, bool, class [mscorlib]System.Reflection.MethodInfo) //这时栈内是第一个参数,lambda的body Expression
    L_0060: ldc.i4.1
    L_0061: newarr [System.Core]System.Linq.Expressions.ParameterExpression
    L_0066: stloc.s CS$0$0002
    L_0068: ldloc.s CS$0$0002
    L_006a: ldc.i4.0
    L_006b: ldloc.3
    L_006c: stelem.ref
    L_006d: ldloc.s CS$0$0002 //把ParameterExpression[0]加载到栈里(第二个参数,lambda的左边的参数集合)
    L_006f: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`2<class [CdcSoftware.Erp.RuntimeServices]CdcSoftware.Erp.RuntimeServices.Entities.Finance.SalesOrderHeader, bool>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[])

题外话:关于IL的参数顺序,生成的IL都是从左到右压到栈里的,但为什么网上都说c#是stdcall呢(也是从右到左的)。这里指出:“当我们使用 call 等指令调用一个方法时,CLR 为目标方法在调用堆栈上新分配一个堆栈帧,并将方法参数从当前方法的计算堆栈弹出压入目标方法的参数表中,接着执行流程跳转到目标方法。”注意弹出压入一词,在老方法里压栈的顺序是param1,param2,则老栈内是param2在param1上面 弹出压入新参数表栈后,由于是param2先弹压,就变成了param2在param1下面了。这就是从右到左的过程。

Struct 作为 Pointer Type的使用实例

来自MSDN。(更新:标题不准确,msdn指的pointer type是 “基础类型 *” 格式定义的变量类型,其中基础类型(referent type)可以是非托管类或者只包含非托管类型的自定义struct,因为pointer type不归GC管,所以不允许有reference type参与其中或者属于用户自定义struct。

参考http://books.google.com.hk/books?id=g6axWRRpJZwC&pg=PA712&lpg=PA712&dq=Any+user-defined+struct+type+that+contains+fields+of+unmanaged+types&source=bl&ots=Hy5jfiHo5K&sig=wCPiDy2c0pP4XfSkJcoMbKyHuWs&hl=zh-TW&sa=X&ei=rbwnU7ubCI-tiQezm4HwDw&ved=0CE0Q6AEwAw#v=onepage&q=Any%20user-defined%20struct%20type%20that%20contains%20fields%20of%20unmanaged%20types&f=false

下面的代码里的Node Struct就是满足条件的自定义struct,但是,另一方面,可以说是不相关的一点,我发现即使struct里含有string类型的成员和其他自定义struct, 直接试用时用reflector看到的仍然是根据地址来用的(也就是用ldloca之类的IL),也就是说它也是像pointer一样被使用的。没有用“基础类型 *“的格式就会被直接当作pointer type来用。从这个角度来说,标题又是对的。但是,这种情况下如果struct里含有string的reference,那个string能被GC吗?待验证。更新:string是immutable的特殊类,用weakReference.Alive来判断的话,GC.Collect()不会影响到它。用自己的类做实验,结果是可以被GC掉的。而这种含有managed成员的struct,就无法再用“基础类型 *“的格式来取得它的地址,它已经是作为managed Type的struct,:

Error 2 Cannot take the address of, get the size of, or declare a pointer to a managed type XXXXXX

所以,这种情况下的struct其实也不算可以作为point type。

这可真是c#和c++的一个大不同,这也解决了为什么用reflector看下列的代码(pointer和address操作必须用unsafe)

public   struct Node
   {
       public int Data;
   }
   class TestPointer
   {
       public unsafe Node* getNode(int i)
       {
           Node n = new Node();
           n.Data = i;
           Node* ptr = &n;
           return ptr;
       }

 

会生成c#版本的

[StructLayout(LayoutKind.Sequential)]
public struct Node
{
    public int Data;
}

public unsafe Node* getNode(int i)
{
    Node n;
    Node* ptr;
    Node* CS$1$0000;
    n = new Node();
    &n.Data = i;
    ptr = (IntPtr) &n;
    CS$1$0000 = ptr;
Label_0019:
    return CS$1$0000;
}

在n.Data = i;这句上,生成出的C#代码实际上是加了&(取地址)符号的。而如果你想在源代码里使用&n.Data=i,却不可以编译通过。所以,编译器在你访问struct的成员的时候会帮你标记出这是个指针访问(为什么不是变成n->Data呢,据说是reflector的C#语言反编译的语法bug?)看IL版本,实际还是指针访问

.method public hidebysig instance valuetype TestPointerForStruct.Node* getNode(int32 i) cil managed
{
    .maxstack 2
    .locals init (
        [0] valuetype TestPointerForStruct.Node n,
        [1] valuetype TestPointerForStruct.Node* ptr,
        [2] valuetype TestPointerForStruct.Node* CS$1$0000)
    L_0000: nop
    L_0001: ldloca.s n
    L_0003: initobj TestPointerForStruct.Node
    L_0009: ldloca.s n //把变量n的地址压栈
    L_000b: ldarg.1  //把第一个参数,也就是i,压栈
    L_000c: stfld int32 TestPointerForStruct.Node::Data  //成员赋值
    L_0011: ldloca.s n
    L_0013: conv.u
    L_0014: stloc.1
    L_0015: ldloc.1
    L_0016: stloc.2
    L_0017: br.s L_0019
    L_0019: ldloc.2
    L_001a: ret
}

 

对比类的赋值源代码:

  m = new Myclass();
   m.Name = “23”;

生成的IL是:
L_003a: newobj instance void TestPointerForStruct.Myclass::.ctor()
   L_003f: stloc.3
   L_0040: ldloc.3  //把第三个变量,这里也就是m,压栈
   L_0041: ldstr “23” //常量压栈
   L_0046: stfld string TestPointerForStruct.Myclass::Name 。//成员赋值

所以指针类型(值类型)和引用类型的成员访问(偏移访问)都是一样的IL,只是压栈的时候一个是用ldloca.s var,压的是var的地址,另一个直接压第x个变量ldloc.x

注:上面两段IL的主要区别就是ldloc和ldloca的区别,也就是一个a,address的区别。

又有机会复习一下基础知识了:值类型和引用类型

essential c# 4里的,c#和c++不一样,不强调值类型的默认modifier是不是public(应该默认都是private,和引用类型一样)。

强调的差异更多的是在构造函数的约束,boxing和unboxing的过程,以及不需要finalize,而这些差别的出发点,都是值类型本身存在的目地就是一个做为值传来传去的过程,这就是它的存在的价值和意义。所以书里也建议,包括网上也有不少地方说到mutable的valueType是不提倡的。

作为一个固定值传来传去,这就直接引出了它和引用类型的差异,或者说,这些差异都是围绕这个目的而产生的。

1.不能有无参数的构造函数。而无参数的构造函数如果存在,会给人一种必然会调用构造函数的假象,但实际如果是通过数组批量构造值类型,就是分配一片初始化为0的内存,没有调用无参数构造函数。而由于field在声明使赋值,如int a=3;会被编译放到构造函数里,没有构造函数的struct也就不能这么做。 而有参数的构造函数就必须责任尽到底,把它的field都初始化过。所以,总的来说,初始化是非常重要的一步,是struct严格要求的地方。

2.boxing的过程,就是先分配个指针(c++的概念,c#内就是引用索引),把栈里的东西拷贝到堆区,然后再指向那个地址。unboxing就相反,也有个拷贝的过程。由于栈区是局部临时性的,会在调用结束后回收,struct也就不需要override Finalize(),写了也不会被调用。

3.接口是引用类型,如果把值类型强制转换为接口,也会发生boxing和unboxing。

3.struct无论如何传递,如果非要改变,那么改变的是它自己,但如果是boxing后作为对象被改变了内部的field,那么受影响的,就是全部的指向它的指针(引用),因为这时它就是引用类型了。

4.一个很好的不区分引用和值类型而赋默认值的办法:http://stackoverflow.com/questions/325426/c-programmatic-equivalent-of-defaulttype