65.9K
CodeProject 正在变化。 阅读更多。
Home

玩转泛型集合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (6投票s)

2013年11月7日

CPOL

3分钟阅读

viewsIcon

13206

玩转用于查询集合的各种接口。

引言

本文仅触及了在使用 ORM(如 Entity Framework)访问数据时,何时以及如何使用不同集合接口的表面。

背景 

在处理从数据库检索的数据列表时,很容易习惯仅依赖 List<T>。但了解数据可以从方法中“生成”以及从调用代码中“消耗”数据的不同方式很有趣。

让我们开始……

这背后的概念非常美妙……

以使用 Entity Framework 等 ORM 进行基本数据访问为例:

public IEnumerable<dCustomer> GetAllCustomers()
{
   IEnumerable<dCustomer> cResults = null;
   using (MyTest1DBConnection context = new MyTest1DBConnection())
   {
      cResults = (from q in context.dCustomers
                 select q);
   }

   // cResults is of type ObjectQuery!   
   return cResults;
}

这如何工作?

请记住,cResults 的类型是 ObjectQuery<dCustomers>,而 ObjectQuery 类同时实现了 IEnumerableIQueryable 接口。这意味着我们可以将方法签名更改为

public IQueryable<dCustomer> GetAllCustomers()  

这样,它仍然可以编译。

何时为返回类型使用 IEnumerable、IQuerable、List?

实现 IEnumerable 接口使我们的 ObjectQuery 对象能够以仅前向(forward-only)的方式读取结果,而 IQueryable 则允许我们执行类似 .Where(...) 的操作,并在查询实际执行之前“修改”它。

  • Enumerable<T> 属于 System.Collections 命名空间。它只有一个 GetEnumerator() 方法,该方法返回一个 Enumerator<T>,您可以在其上调用 MoveNext() 方法来遍历 T 的序列……
  • IQueryable<T> 属于 System.Linq 命名空间,并实现 IEnumerable<T>,但添加了查询提供程序和表达式引用。它允许我们执行 .Where() 等操作并修改我们的查询。
  • ICollection<T> 允许添加和删除条目,并在拥有实际数据集合时使用。
  • List<T>IEnumerable 的实际具体实现,但不是 IQueryable,并且可以通过调用 .ToList() 方法从实现这两个接口之一的类生成。
  • List<T> 从何处获得执行 .Where() 类型操作的能力,因为它不实现 IQueryable?因为它实现了 ICollection 接口,该接口具有这些方法。

请注意!

我们还没有执行查询,也无法访问结果。我们得到“某个东西”(ObjectQuery 类),它具有获取数据的方法,但还没有数据本身。

尝试访问数据将引发“ObjectContext 实例已被释放,无法再用于需要连接的操作。”错误。

要访问数据,我们必须在离开数据上下文范围之前获取数据。这可以通过多种方式完成:

1. 将 cResults 声明为 IEnumerable<dCustomer> 类型:
cResults = (from q in context.dCustomers                  
            select q); 

IEnumerator<dCustomer> enumerator = cResults.GetEnumerator();

while (enumerator.MoveNext())
{
    string strContent = Convert.ToString(enumerator.Current.Name);
    // Add it to a list, array or any structure implementing the IEnumerable interface.
}  

2. 将 cResults 声明为 ICollection<dCustomer>List<dCustomer> 类型

cResults = (from q in context.dCustomers
select q).ToList(); 

请记住,如果我们希望数据在方法外部可访问,我们的方法必须返回任何已填充数据的列表结构。这意味着列表、数组、hashset……或者更具体地说,任何使用“ICollection”接口的东西。我们可以将方法签名更改为

public ICollection<dCustomer> GetAllCustomers()

这样,它仍然可以工作。

现在是关于有趣的部分

我们现在有一个方法,它可以作为列表、数组、hashset……也就是说:多种形式……返回数据,而无需显式指定类型。这是因为我们的方法返回类型是 IEnumerableICollection 之一。

在接收端,我们可以将结果转换为我们想要的任何其他类型,只要它也实现 IEnumerable 或 ICollection。

如果我们从代码库的其他地方调用该方法

MyTest1Data.CustomerManager cmData = new MyTest1Data.CustomerManager();

List<dCustomer> lResults = cmData.GetAllCustomers().ToList();
//or
dCustomer[] lResults = cmData.GetAllCustomers().ToArray();

这意味着我们可以返回一个对象数组,然后在接收端调用 .ToList() 来得到一个列表。或者我们可以在方法内部返回一个 List,让接收端将其用作 array[]。所有这些都在接收端不必知道我们方法内部生成数据的具体情况。

这也意味着如果我们“发送”的数据是 List,并且在“接收”数据时将其作为 List,我们可能需要调用两次 .ToList()(一次在方法内部,然后再次在接收端),因为接收端只知道它是一个实现了 IEnumerable 接口的“某物”。

关注点

以下链接也可能有用:

历史

  • First version.
© . All rights reserved.