代码改变世界

【More Effective C#】LINQ表达式与方法调用的映射

2010-10-15 08:05  空逸云  阅读(969)  评论(2编辑  收藏  举报

LINQ构建在两个概念之上,一种查询语言和一系列将查询语言转换成方法调用的实现.在编译时,编译器将LINQ表达式(LINQ to object)转换成方法调用.

.Net基础类库提供了两种扩展方法.System.Linq.Enumerable使用了IEnumerable<T>上扩展来实现,System.Linq.Queryable则提供了类似的一系列IQueryable<T>上的扩展.两者的转换略为不同.前者在编译时转换成相应的扩展方法调用.而后者则能将LINQ表达式转换成SQL查询,并有SQL数据库引擎执行.

LINQ表达式到方法调用的转换时一个复杂的迭代过程.编译器在转换时也有一个特定的顺序.

例如

 int[] someNumbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

            var answer = from n in someNumbers

                         where n < 5

                         select n;

最后将转换成

int[] someNumbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

            var answer =someNumbers.Where(n=>n<5);

可以看到.在上面的转换后.Select被优化去掉了.这就是一个退化选择.不止是Where和Select.相应的LINQ表达式都会被转换成相应的扩展方法..

编译器的转换

C#编译器将把查询和lambda表达式(Linq to object)转换成静态委托,实力委托和闭包.

int[] someNumbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

            var answer = from n in someNumbers

                         select n;

把上边的代码编译.使用Refletor打开程序集.查看生成的相关代码.

 int[] someNumbers = new int[] { 0123456789 };

    if (CS$<>9__CachedAnonymousMethodDelegate1 == null)

    {

        CS$<>9__CachedAnonymousMethodDelegate1 = new Func<intint>(null, (IntPtr<Main>b__0);

    }

    IEnumerable<intanswer = Enumerable.Select<intint>(someNumbersCS$<>9__CachedAnonymousMethodDelegate1);

可以看到.编译器自动生成了一个委托.下面.使用更为直观的代码描述.生成的代码大致如下:

 private static int HiddenFuc(int n)

        {

            return n * n;

        }

        private static Func<intint> HiddenDelegateDefinition

 int[] someNumbers = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

            if (HiddenDelegateDefinition == null)

            {

                HiddenDelegateDefinition = new Func<intint>(HiddenFuc);

            }

            IEnumerable<int> anser = someNumbers.Select<intint>(HiddenDelegateDefinition);

可以看到.编译器生成了一个静态方法实现Select表达式.并且通过委托实现查询.

实际上.上述的lambda表达式的主体部分并没有访问任何实例变量或是局部变量.什么是实例变量.什么又是局部变量.?

访问实例变量的Lambe表达式

public class ModFilter

    {

        private readonly int modulus;

        public ModFilter(int mod)

        {

            modulus = mod;

        }

        public IEnumerable<int> FindValues(IEnumerable<int> sequence)

        {

            return from n in sequence

                   where n % modulus == 0   //访¤问实例变量

                   select n * n;

        }

    }

编译器将会为你生成一个实例方法,生成的代码大致如下

 public class ModFilter

    {

        private readonly int modulus;

        public ModFilter(int mod)

        {

            modulus = mod;

        }

        private bool WhereClause(int n)

        {

            return (n % modulus) == 0;

        }

        private static int SelectClause(int n)

        {

            return n * n;

        }

        private static Func<intint> SelectDelegate;

        public IEnumerable<int> FindValues(IEnumerable<int> sequence)

        {

            if (SelectDelegate == null)

            {

                SelectDelegate = new Func<intint>(SelectClause);

            }

            return sequence.Where<int>(new Func<intbool>(this.WhereClause)).Select<intint>(SelectClause);

        }

}

Lambda表达式访问实例变量

  若是Lambda表达式中访问了外部方法的实例变量.则编译器将自动生成一个私有的嵌套类型.

public class ModFilterCloser

    {

        private readonly int modulus;

        public ModFilterCloser(int mod)

        {

            modulus = mod;

        }

        public IEnumerable<int> FindValues(IEnumerable<int> sequence)

        {

            int numValues = 0;

            return from n in sequence

                   where n % modulus == 0

                   select n * n / ++numValues; //调用了方法外的局部变量numValues

        }

    }

生成的代码大致如下.

 public class ModFilterCloser

    {

        private sealed class Closure

        {

            public ModFilterCloser outer;

            public int numValues;

            public int SelectClause(int n)

            {

                return (n * n) / ++this.numValues;

            }

        }

        private readonly int modulus;

        public ModFilterCloser(int mod)

        {

            modulus = mod;

        }

        private bool WhereClause(int n)

        {

            return (n % modulus) == 0;

        }

        public IEnumerable<int> FindValues(IEnumerable<int> sequence)

        {

            Closure c = new Closure();

            c.outer = this;

            c.numValues = 0;

            return sequence.Where<int>(new Func<int,bool>(this.WhereClause)).Select<int,int>(c.SelectClause));

        }

    }

LINQ to Sql 的实现

最后.要注意到.以上的转换实现都是在Linq to object中实现.IEnumberale<T>.LINQ to SQL 的转换.编译后可以看到.并没发生任何变化.那是因为.只有在遍历迭代时,延迟执行.LINQ to SQL Provider才将LINQ表达式转换成SQL查询.