飘遥的Blog

C/C++/.NET
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Linq 学习(7) 延迟执行 & 查询非泛型集合

Posted on 2008-09-13 00:15  Zzx飘遥  阅读(845)  评论(1编辑  收藏  举报
本篇介绍Linq的延迟执行和查询非泛型集合。

延迟执行
从Linq的查询过程看,不断有新类型、集合的构建、释放,感觉效率不高,但Linq的延迟执行机制将大大改善常规操作加载整个数据源导致的效率低下的状况。
示例:
static void Main()
{
    
int[] collection = { 10, 11, 12, 13, 14 };

    Console.WriteLine(
"Begin query collection:" + DateTime.Now);

    
var result = from i in collection
                 select DoubleInt(i);

    Console.WriteLine(
"End query collection & Begin output:" + DateTime.Now);

    
foreach (var i in result)
    {
        Console.WriteLine(
"Result:" + i + " Time:" + DateTime.Now.ToString());
    }


    Console.WriteLine(
"End output:" + DateTime.Now);
}

static int DoubleInt(int i)
{
    Console.WriteLine(
"The parameter is:" + i);
    Thread.Sleep(
1000);
    
return i * 2;
}

// 输出为:
// Begin query collection:2008-9-12 22:41:50
// End query collection & Begin output:2008-9-12 22:41:50
// The parameter is:10
// Result:20 Time:2008-9-12 22:41:51
// The parameter is:11
// Result:22 Time:2008-9-12 22:41:52
// The parameter is:12
// Result:24 Time:2008-9-12 22:41:53
// The parameter is:13
// Result:26 Time:2008-9-12 22:41:54
// The parameter is:14
// Result:28 Time:2008-9-12 22:41:55
// End output:2008-9-12 22:41:55

从输出结果可以看出,查询时并没有立即计算结果,而是输出时才进行计算。
其实在构造查询语句时,只是传递的相应操作的委托,并没有真正执行查询操作,真正的执行是在 IEnumerator<T> 的 GetEnumerator() 方法中执行的,每取得一个元素就执行一次。延迟执行还有个好处是改变数据源时不需要构造新的查询语句即可执行获得想要的结果。
示例:
static void Main()
{
    
// 原始集合
    int[] collection = { 10, 11, 12, 13, 14 };

    
// 查询集合
    var result = from i in collection
                 select DoubleInt(i);

    
// 输出原集合
    foreach (var i in result)
    {
        Console.WriteLine(i);
    }

    Console.WriteLine(
"Change data source:");

    
// 改变集合
    for (var i = 0; i < collection.Length; i++)
    {
        collection[i]
+= 10;
    }

    
// 输出改变后的集合
    foreach (var i in result)
    {
        Console.WriteLine(i);
    }
}

static int DoubleInt(int i)
{
    
return i * 2;
}

// 结果:
// 20
// 22
// 24
// 26
// 28
// Change data source:
// 40
// 42
// 44
// 46
// 48

为了更清楚的演示延迟执行的执行时间,自己实现 IEnumerator<T> 接口,通过下面代码可以看出,查询操作是在 GetEnumerator() 方法中执行的。
static void Main()
{
    
int[] collection = { 10, 11, 12, 13, 14 };

    
var result = from i in GetEnumerator(collection)
                 select i;

    
foreach (var i in result)
    {
        Console.WriteLine(i);
    }
}

public static IEnumerable<int> GetEnumerator(int[] collection)
{
    
foreach (var i in collection)
    {
        Console.WriteLine(
"Compute:" + i);
        
yield return DoubleInt(i);
    }
}

static int DoubleInt(int i)
{
    Console.WriteLine(
"The parameter is:" + i);
    
return i * 2;
}

// 输出:
// Compute:10
// The parameter is:10
// 20
// Compute:11
// The parameter is:11
// 22
// Compute:12
// The parameter is:12
// 24
// Compute:13
// The parameter is:13
// 26
// Compute:14
// The parameter is:14
// 28

延迟执行的优点是不需要同时加载整个数据源,执行占用资源少、效率高,试想如果数据源非常大,全部加载效率会多低。
当然,某些时候,延迟执行也会导致数据不一致等等,想要关闭延迟执行,可以用ToList<T>()、ToArray<T>()等方法立即执行查询操作。
前面介绍的集合操作时会影响延迟操作,因此在执行集合操作时应特别注意。
下面的例子演示立即执行。
static void Main()
{
    
int[] collection = { 10, 11, 12, 13, 14 };

    Console.WriteLine(
"Begin query collection:" + DateTime.Now);

    
var result = from i in collection
                 select DoubleInt(i);

    Console.WriteLine(
"End query collection & Begin output:" + DateTime.Now);

    
foreach (var i in result.ToList())
    {
        Console.WriteLine(
"Result:" + i + " Time:" + DateTime.Now.ToString());
    }

    Console.WriteLine(
"End output & Begin get collection count:" + DateTime.Now);

    
// 集合操作:取元素的数量
    Console.WriteLine("元素数量为:" + result.Count());

    Console.WriteLine(
"End get collection count:" + DateTime.Now);
}

static int DoubleInt(int i)
{
    Console.WriteLine(
"The parameter is:" + i);
    Thread.Sleep(
1000);
    
return i * 2;
}

// 输出结果:
// Begin query collection:2008-9-12 23:36:34
// End query collection & Begin output:2008-9-12 23:36:34
// The parameter is:10
// The parameter is:11
// The parameter is:12
// The parameter is:13
// The parameter is:14
// Result:20 Time:2008-9-12 23:36:39
// Result:22 Time:2008-9-12 23:36:39
// Result:24 Time:2008-9-12 23:36:39
// Result:26 Time:2008-9-12 23:36:39
// Result:28 Time:2008-9-12 23:36:39
// End output & Begin get collection count:2008-9-12 23:36:39
// The parameter is:10
// The parameter is:11
// The parameter is:12
// The parameter is:13
// The parameter is:14
// 元素数量为:5
// End get collection count:2008-9-12 23:36:44

查询非泛型集合
在 .NET1.X 时代不支持泛型,构造集合时都要转换或装箱为Object类型,Linq 查询是以泛型为基础的,在老系统升级或整合时要进行非泛型集合操作。Enumerable的Cast<T>() 和OfType<T>()扩展方法使非泛型集合的 Linq 查询成为可能。
ArrayList list = new ArrayList();
list.Add(
1);
list.Add(
2);
list.Add(
3);
list.Add(
4);
list.Add(
5);

var result = from i in list.Cast<int>()
             select i;

foreach (var i in result)
{
    Console.WriteLine(i);
}
// result:
// 1
// 2
// 3
// 4
// 5

如果不想显式调用Cast<T>()方法,可以在元素前面指定类型。上面的例子变为:
ArrayList list = new ArrayList();
list.Add(
1);
list.Add(
2);
list.Add(
3);
list.Add(
4);
list.Add(
5);

var result = from int i in list
             select i;

foreach (var i in result)
{
    Console.WriteLine(i);
}

我们知道,非泛型不是类型安全的,可以在非泛型的ArrayList中同时插入整型、字符串,那么上面的方法进行类型转换会抛出异常,影响操作的继续进行,OfType<T>()可以一定程度上避免这个问题。如下面的示例会选择适当的类型而忽略不匹配类型。
ArrayList list = new ArrayList();
list.Add(
1);
list.Add(
2);
list.Add(
"abc");
list.Add(
3);
list.Add(
4);

var result = from int i in list.OfType<int>()
             select i;

foreach (var i in result)
{
    Console.WriteLine(i);
}
// result:
// 1
// 2
// 3
// 4

如果这个例子中的集合用上面的Cast<T>()或隐式转换将会抛出异常。