【问题标题】:Why use .AsEnumerable() rather than casting to IEnumerable<T>?为什么使用 .AsEnumerable() 而不是转换为 IEnumerable<T>?
【发布时间】:2010-01-06 15:10:57
【问题描述】:

IEnumerable&lt;T&gt; 的扩展方法之一是.AsEnumerable()。此方法将调用它的可枚举对象转换为IEnumerable&lt;T&gt; 的实例。但是,由于对象必须实现IEnumerable&lt;T&gt; 才能应用于此扩展方法,因此转换为IEnumerable&lt;T&gt; 是一个简单的转换为IEnumerable&lt;T&gt; 的问题。我的问题是为什么这种方法存在?

例子:

List<string> strings = new List<string>() { "test", "test2", "test3" };
IEnumerable<string> stringsEnum1 = strings.AsEnumerable();
IEnumerable<string> stringsEnum2 = (IEnumerable<string>)strings;

在上面的例子中,stringsEnum1stringsEnum2 是等价的。扩展方法的意义何在?

编辑:作为推论,为什么在转换为 IQueryable&lt;T&gt; 时有一个 .AsQueryable() 方法是等效的?

【问题讨论】:

    标签: c# linq ienumerable


    【解决方案1】:

    可读性是这里的主要问题。考虑一下

    Table.AsEnumerable().Where(somePredicate)
    

    更具可读性
    ((IEnumerable<TableObject>)Table).Where(somePredicate).
    

    或者想象一下想要在 SQL Server 上执行部分查询,而其余部分在内存中:

    Table.Where(somePredicate)
         .Select(someProjection)
         .AsEnumerable()
         .SomethingElse()
    

    ((IEnumerable<SomeProjectionType>)Table.Where(somePredicate)
                                           .Select(someProjection))
                                           .SomethingElse()
    

    现在,至于为什么这种方法完全有用,想想 LINQ to SQL DataContext 中的 Table 示例。由于TableIQueryable,它实现了IEnumerable。当您在此类Table 上调用Where 方法并枚举结果时,将执行最终导致在SQL Server 上执行SQL 语句的代码。 AsEnumerable 所做的是说,不,我不想使用 LINQ to SQL 提供程序来执行 Where,我想使用 Where 的 LINQ to Objects 实现。

    如此枚举

    Table.Where(somePredicate)
    

    导致查询在 SQL Server 上执行,同时枚举

    Table.AsEnumerable().Where(somePredicate)
    

    Table 表示的表带入内存并在内存中执行Where 功能(而不是在SQL Server 上!)

    这是AsEnumerable 的要点:允许您隐藏IEnumerable 方法的特定实现,而使用标准实现。

    【讨论】:

    • 这是真的 - 但不会投射到 IEnumerable&lt;T&gt; 提供相同的结果?
    • 我发现Table.AsEnumerable().Where(somePredicate)((IEnumerable&lt;TableObject&gt;)Table).Where(somePredicate) 更具可读性。
    • 我明白了——它们在技术上是等价的,但由于匿名类型,AsEnumerable 功能更强大:您不能强制转换为匿名类型的 IEnumerable,但 AsEnumerable 可以。
    • 这是一个非常好的观点。我确实认为这是一个附带好处;我认为重点是可读性。
    • @Erik:当然,您可以创建自己的扩展方法来转换为IEnumerable&lt;anon&gt;,但在这种情况下,您实际上是在重新实现AsEnumerable
    【解决方案2】:

    我想到了除了可读性之外的一个原因,尽管与查询实现有关:对通过另一个 Linq 提供程序返回的匿名类型使用 Linq to Objects。您不能强制转换为匿名类型(或匿名类型的集合),但您可以使用 .AsEnumerable() 为您执行强制转换。

    例子:

    // Get an IQueryable of anonymous types.
    var query = from p in db.PeopleTable /* Assume Linq to SQL */
                select new { Name = p.Name, Age = p.Age };
    
    // Execute the query and pull the results into an IEnumerable of anonymous types
    var @enum = query.AsEnumerable();
    
    // Use Linq to Objects methods to further refine.
    var refined = from p in @enum
                  select new
                  {
                      Name = GetPrettyName(p.Name),
                      DOB = CalculateDOB(p.Age, DateTime.Now)
                  };
    

    很明显,这里的原因是我们想使用 Linq to SQL 之类的东西将一些记录拉入匿名类型,然后使用 Linq to Objects 执行一些自定义逻辑(通过 Linq to SQL 无法实现)客户端。

    无法投射到IEnumerable&lt;_anon&gt;,因此.AsEnumerable() 是唯一的选择。

    感谢所有回答并帮助我拼凑起来的人。 =)

    【讨论】:

    【解决方案3】:

    当我在阅读本书时C# 6.0 in a Nutshell。下面是书中AsEnumerable的一个例子。


    目的是将IQueryable&lt;T&gt;序列强制转换为IEnumerable&lt;T&gt;,强制后续查询运算符绑定到可枚举运算符而不是可查询运算符。这会导致查询的其余部分在本地执行。

    为了说明,假设我们在 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-to-db 提供程序会抛出异常,抱怨无法将查询转换为 SQL。我们可以通过分两步查询来解决这个问题:首先通过 LINQ to SQL 查询检索所有关于流感的文章,然后在本地过滤少于 100 个单词的摘要:

    Regex wordCounter = new Regex (@"\b(\w|[-'])+\b");
    
    IEnumerable<MedicalArticle> sqlQuery = dataContext.MedicalArticles
        .Where (article => article.Topic == "influenza");
    
    IEnumerable<MedicalArticle> localQuery = sqlQuery
        .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 的优点是它不会强制立即执行查询,也不会创建任何存储结构。

    【讨论】:

      【解决方案4】:

      这只是转换为 IEnumerable 的最好和最短的方法。如果您在 Reflector 中查看它,您会发现它除了将对象作为 IEnumerable 返回之外什么都不做。

      来自 MSDN:

      AsEnumerable(Of TSource)(IEnumerable(Of TSource)) 方法除了 更改源的编译时类型 从实现的类型 IEnumerable(Of T) 到 IEnumerable(Of T) 自己。

      【讨论】:

      • 我不确定这是不是真的。我相信这与查询实现有关。
      • @runrunraygun:它的存在是因为它可以更容易地从 IQueryable 切换到 IEnumerable,例如,但除了将其转换为 IEnumerable 之外,它通常不会做任何特别的事情。如果您不相信我,请检查 Reflector ;-)
      • @runrunraygun:我提供了您提供的链接中的引用来证明这一点;-)
      • 实际上在 linq-to-sql 中使用的效果不止于此——它将执行查询。
      【解决方案5】:

      匿名类型是提供这些扩展方法的主要原因。 (不能在泛型参数中使用匿名类型) 但是方法调用可以使用类型推断,允许您省略在泛型参数中指定类型。

      【讨论】:

        【解决方案6】:

        如果对象上有与 Linq 扩展方法同名的方法,它会隐藏扩展方法。使用 AsEnumerable 可以让您获得扩展。

        这似乎是 SP1 中的新功能。

        昨天我有一行代码从数据表中提取成员标识符:-

        var lMmIds = new List<int>(
            lDmMember.DataTable.Select(R => R.MmId)
        );
        

        在我安装 SP1 之前效果很好。现在它不会工作,除非它读取

        var lMmIds = new List<int>(
            lDmMember.DataTable.AsEnumerable().Select(R => (int)((dsMtables.tbMMemberRow)R).MmId)
        );
        

        编辑:我找到了真正的原因

        这样您就可以在同一个 linq 语句中同时使用远程方法(例如 SQL 语句中的 WHERE)和本地方法。如果不使用 AsEnumerable(即只是强制转换),它将使查询生成器尝试为包含本地方法的远程执行创建表达式树。将 AsEnumerable 放入查询将导致该查询的其余部分在远程查询的结果上在本地执行。

        来自https://msdn.microsoft.com/en-us/library/bb335435(v=vs.110).aspx

        表示数据库表的 Table 类型可以具有 Where 方法,该方法将谓词参数作为表达式树并将树转换为 SQL 以进行远程执行。如果不需要远程执行,例如因为谓词调用本地方法,则可以使用 AsEnumerable 方法隐藏自定义方法,而是使标准查询运算符可用。

        【讨论】:

          【解决方案7】:

          如你所说,如果一个类型已经实现了IEnumerable&lt;T&gt;,那么在转换为接口或调用AsEnumerable 方法之间实际上并没有任何功能上的区别。

          我的猜测,也只是猜测,是调用 AsEnumerable 提高了可读性并保留了其他 LINQ 扩展方法的流畅签名:

          var query = ((IEnumerable<YourType>)yourCollection).Select(x => x.YourProperty);
          
          // vs
          
          var query = yourCollection.AsEnumerable().Select(x => x.YourProperty);
          

          它还允许未实现 IEnumerable&lt;T&gt; - for example, DataTable 的类型拥有自己的 AsEnumerable 扩展版本。这允许您在针对这些类型的查询中继续使用相同的模式 - 即使您调用的是不同的 AsEnumerable 方法 - 而无需担心该类型是否真正实现 @ 987654331@.

          【讨论】:

          • 但是那些不实现 IEnumerable 的类型无权访问 AsEnumerable 扩展方法 - 所以这些类型不包括在这个问题所涉及的类型集中。不过好点。
          • @Erik:我的意思是,例如,即使DataTable 没有实现IEnumerable&lt;T&gt;,在查询DataTable 时也可以使用相同的模式var query = yourDataTable.AsEnumerable().Select(x =&gt; x["YourColumn"])msdn.microsoft.com/en-us/library/…
          猜你喜欢
          • 1970-01-01
          • 2011-05-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-21
          • 1970-01-01
          • 2012-02-24
          相关资源
          最近更新 更多