为什么使用.AsEnumerable()而不是将其强制转换为IEnumerable?
为什么使用.AsEnumerable()而不是将其强制转换为IEnumerable?
在IEnumerable
上的一个扩展方法是.AsEnumerable()
。该方法将调用它的可枚举对象转换为IEnumerable
的实例。然而,由于对象必须实现IEnumerable
才能应用于这个扩展方法,所以转换为IEnumerable
只需要简单地进行类型转换。我的问题是为什么这个方法存在呢?\n示例:\n
Liststrings = new List () { "test", "test2", "test3" }; IEnumerable stringsEnum1 = strings.AsEnumerable(); IEnumerable stringsEnum2 = (IEnumerable )strings;
\n在上面的示例中,stringsEnum1
和stringsEnum2
是等效的。那么这个扩展方法的意义是什么呢?\n作为一个推论,为什么存在.AsQueryable()
方法,而进行类型转换为IQueryable
是等效的?
为什么使用.AsEnumerable()而不是强制转换为IEnumerable
在阅读《C# 6.0 in a Nutshell》一书时,我看到了书中关于.AsEnumerable的一个例子。
这个例子的目的是将一个IQueryable
为了说明这一点,假设我们在SQL Server中有一个MedicalArticles表,并且想要使用LINQ to SQL或EF来检索所有关于流感的文章,其摘要包含少于100个单词的内容。对于后一个谓词,我们需要一个正则表达式:
Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
var query = dataContext.MedicalArticles
.Where (article => article.Topic == "influenza" &&
wordCounter.Matches (article.Abstract).Count < 100);
问题是SQL Server不支持正则表达式,因此LINQ到数据库提供程序会抛出一个异常,指出无法将查询转换为SQL。我们可以通过两个步骤来解决这个问题:首先通过一个LINQ to SQL查询检索所有关于流感的文章,然后在本地过滤掉摘要少于100个单词的文章:
Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
IEnumerable
.Where (article => article.Topic == "influenza");
IEnumerable
.Where (article => wordCounter.Matches (article.Abstract).Count < 100);
通过使用AsEnumerable,我们可以在单个查询中完成相同的操作:
var query = dataContext.MedicalArticles
.Where (article => article.Topic == "influenza")
.AsEnumerable()
.Where (article => wordCounter.Matches (article.Abstract).Count < 100);
调用AsEnumerable的另一种方法是调用ToArray或ToList。AsEnumerable的优点在于它不会强制立即执行查询,也不会创建任何存储结构。
为什么要使用.AsEnumerable()而不是将其转换为IEnumerable
在使用Linq查询时,有时我们会遇到无法直接将查询结果转换为匿名类型(或匿名类型的集合)的情况。这时,我们可以使用.AsEnumerable()方法来完成转换。
例如,假设我们使用Linq to SQL查询数据库中的PeopleTable表,并将查询结果转换为一个匿名类型(包含姓名和年龄)的集合。然后,我们希望在客户端使用Linq to Objects对这个集合进行进一步的处理,例如通过某些自定义逻辑对姓名进行美化,计算年龄对应的出生日期等。
但是,我们无法直接将查询结果转换为IEnumerable<匿名类型>,因为匿名类型是由编译器在编译时生成的,无法进行显式的类型转换。这时,我们可以使用.AsEnumerable()方法将查询结果转换为IEnumerable
以下是示例代码:
// 获取一个返回匿名类型的IQueryable var query = from p in db.PeopleTable /* 假设是Linq to SQL */ select new { Name = p.Name, Age = p.Age }; // 将查询结果转换为IEnumerable<匿名类型> var results = query.AsEnumerable(); // 使用Linq to Objects方法进行进一步处理 var refined = from p in results select new { Name = GetPrettyName(p.Name), DOB = CalculateDOB(p.Age, DateTime.Now) };
显然,这里的原因是我们希望使用类似Linq to SQL这样的方法从数据库中获取一些记录,并将其转换为匿名类型的集合,然后在客户端使用Linq to Objects进行一些无法通过Linq to SQL实现的自定义逻辑处理。
由于无法直接将查询结果转换为IEnumerable<匿名类型>,所以使用.AsEnumerable()是唯一的解决方法。
需要注意的是,调用.AsEnumerable()并不会立即执行查询,它只是将查询结果转换为IEnumerable
感谢所有为我提供帮助的回答者,帮助我理清了这个问题。
为什么要使用.AsEnumerable()而不是将其转换为IEnumerable
在这里,可读性是主要问题。考虑以下代码:
Table.AsEnumerable().Where(somePredicate)
这比以下代码更可读:
((IEnumerable
或者想象一下,想要在SQL Server上执行查询的一部分,而在内存中执行剩余部分:
Table.Where(somePredicate)
.Select(someProjection)
.AsEnumerable()
.SomethingElse()
相比于以下代码:
((IEnumerable
.Select(someProjection))
.SomethingElse()
那么,为什么会有这样的方法呢?以LINQ to SQL DataContext中的Table为例。由于Table是IQueryable,它实现了IEnumerable。当在这样的Table上调用Where方法并枚举结果时,会执行代码,最终导致在SQL Server上执行SQL语句。AsEnumerable的作用是告诉LINQ不要使用LINQ to SQL提供程序执行Where,而是使用LINQ to Objects实现的Where。
因此,对Table.Where(somePredicate)进行枚举会在SQL Server上执行查询,而对Table.AsEnumerable().Where(somePredicate)进行枚举会将由Table表示的表加载到内存中,并在内存中执行Where功能(而不是在SQL Server上执行)。
这就是AsEnumerable的作用:允许您隐藏特定的IEnumerable方法实现,而使用标准实现。
这是正确的 - 但是将其强制转换为IEnumerable
我发现Table.AsEnumerable().Where(somePredicate)比((IEnumerable
我明白了 - 它们在技术上是等价的,但是AsEnumerable由于匿名类型的存在而更加强大:无法将其转换为IEnumerable
这是一个很好的观点。我认为这是一个附加的好处,我认为主要问题是可读性。
也许,但是使用AsEnumerable是执行我示例中所示操作的唯一方法。如果不能使用AsEnumerable,每次想要执行此操作时都必须创建一个新类型,并且不能使用匿名类型。
您当然可以创建自己的扩展方法来执行到IEnumerable
同样,我想举一个例子加以说明:Convert.ToString()和.ToString()都存在并且可以互换使用,用途相同,但.ToString()更可读。
它们不是等价的。强制转换只是将当前结果集强制转换为IEnumerable
那是不正确的。AsEnumerable的文档说明只改变对象的类型,这当然会改变可用于对对象运行的扩展方法集。
请参阅我在此主题的其他位置的答案,以获取相关详细信息。然而,由于我现在经常使用这种技术,我相信我的评论是正确的。
感谢您的回复。您在答案中看到的结果正是我对强制转换的期望。事实上,我进行了尝试,并且它与AsEnumerable的工作方式相同。
将其转换为IEnumerable没有任何效果,因为IQueryable已经实现了IEnumerable。如果原始查询正在本地运行,将其转换为IEnumerable将正常工作。当查询针对远程源运行时,情况会变得复杂,所以MySQLSource.MyTable.Where(SQlPredicate).Where(LocalPredicate)将无法工作,而MySQLSource.MyTable.Where(SQlPredicate).IEnumerable