【问题标题】:Is AsList() better than ToList() with IDbConnection.Query() which returns IEnumerable?使用返回 IEnumerable 的 IDbConnection.Query(),AsList() 是否优于 ToList()?
【发布时间】:2018-05-27 10:08:30
【问题描述】:

我从 Marc Gravell (@MarcGravell) 那里读到了这个答案:https://stackoverflow.com/a/47790712/5779732

最后一行说:

作为对代码的小优化:更喜欢 AsList() 而不是 ToList() 以避免创建副本。

该语句是关于 QueryMultiple() 的,它返回 GridReader

据我了解,System.Linq提供了一个扩展方法IEnumerable.ToList()。以下来自Microsoft 关于ToList()

ToList(IEnumerable) 方法强制立即进行查询评估并返回一个包含查询结果的列表。您可以将此方法附加到查询中,以获取查询结果的缓存副本。

IDbConnection.Query() 将始终返回 IEnumerablenull。在调用代码时可以很容易地进行空检查。那么AsList 有什么不同呢?

如果我的理解是正确的,AsList 将始终在内部调用ToList,这将创建一个副本。

考虑到这一点,AsList() 是否比 ToList() 更好,IDbConnection.Query() 返回 IEnumerable?如是;为什么?

AsList() 在内部做了什么使其在这种情况下成为更好的选择?

【问题讨论】:

  • 查看the source,你会看到cmets表明如果数据已经在List数据结构中,那么将返回那个列表(即实例),而不是创建一个新的(即复制)List.
  • @KennethK.:同意。但是IDbConnection.Query() 总是会返回IEnumerablenull。在调用代码中可以轻松完成空值检查。那么AsList 有什么不同呢?

标签: c# performance linq dapper database-performance


【解决方案1】:

AsList 是一个自定义的 Dapper 扩展方法。它所做的只是检查您传递给它的IEnumerable<T> 是否真的是List<T>。如果是 - 它会将其返回,只需转换为 List<T>。如果不是 - 它会调用常规的ToList。关键是 - ToList() 总是创建一个副本,即使你传递给它的已经是一个列表。 AsList() 方法避免了这种复制,因此在不需要这种复制时很有用。

在这个特定的场景中,你有以下代码:

multipleresult.Read<MerchantProduct>()

其中multipleresultGridReaderRead 具有 buffered 参数,默认情况下为 true。当它为真时 - Read 将真正返回 List&lt;T&gt;,因此通过调用 ToList 您将无需太多理由再次复制该列表。

IDbConnection.Query() 也是如此 - 也有 buffered 参数,默认为 true,因此它也会默认返回 List&lt;T&gt;

如果您更喜欢使用ToList() - 您可以将buffered: false 传递给Query()Read() 以避免创建额外的副本。

【讨论】:

  • 扩展可能会伤害 imo。如果有人更改了内部实现,例如从列表更改为数组,AsList 会突然返回一个新列表,而不是返回相同的实例。这可能会默默地破坏代码。
  • @TimSchmelter 这在理论上是有效的,但我看不出在这种情况下它如何破坏代码(对于简洁的查询)。在这种情况下,您并不关心它是否会返回相同的列表或“新”列表 - 它们对您来说都是“新的”。
  • @TimSchmelter 如果我更改 Dapper 的内部实现,我一定会使用 [Obsolete("...", false)] 注释 AsList() 并提供一条解释原因和原因的消息;我怀疑它会改变,但是
  • @MarcGravell 为什么要引入缓冲参数,甚至默认设置?为了防止不知道 IEnumerable 工作原理的用户意外多次执行查询?
  • @Evk 部分原因是,虽然实际上更常见的情况是读取一次但延迟它 - 此时连接可能会关闭或另一个读取器可能处于活动状态;当您确实想要原始流式访问时,还可以提供一个统一的 API (buffered: false)
【解决方案2】:

这个扩展是一个自定义的 dapper 扩展,它在调用 ToList 之前会进行额外的检查。 Source:

public static List<T> AsList<T>(this IEnumerable<T> source) 
    => (source == null || source is List<T>) ? (List<T>)source : source.ToList();
  • ToList 总是创建一个新的 List&lt;T&gt; 实例并用给定的项目填充它
  • AsList 检查序列是否已经是 List&lt;T&gt;,然后将其强制转换

当然,这种方法可能更有效,因为铸造一些东西比创造和填充新东西要少得多。所以完全不同。

这是一种基于观点的观点,但我觉得这很危险。有人可能会忽略AsList 并阅读ToList 或者只是不知道其中的区别。以后有人修改代码很危险。

例如,采用IEnumerable&lt;T&gt; 的方法使用AsList

public static List<T> GetResult<T>(IEnumerable<T> seq)
{
    if(some condition here)
    {
        seq = seq.Where(some predicate here);
    }
    return seq.AsList()
}

现在代码用一个列表调用这个方法:

IEnumerable<string> sequence = (gets a list from somewhere)
List<string> userList = GetResult(sequence);

后来有人决定在这里使用数组更合适:

IEnumerable<string> sequence = (gets an array from somewhere)
List<string> userList = GetResult(sequence);

直到现在这才真正受到伤害。现在一个新的 List 被初始化并填充,因为源不是一个列表并且不能被强制转换。所以它只是效率较低。但是,如果逻辑也依赖于列表是相同的引用,那么这将不再有效。

if(userList == seq)
{
    // do something
}

一旦数组被使用,这总是false。于是代码被默默破解了。

长话短说:我不喜欢AsList 方法。您可以随时自行检查类型。

【讨论】:

  • 这里的关键点是AsList() 旨在用于与Dapper 以避免次要分配;它并不适用于所有用途,但坦率地说,它在许多用途中都可以正常工作
  • @MarcGravell:嗯,如果你添加using Dapper;,你也可以在外面使用这个扩展,这是IEnumerable&lt;T&gt;的公共扩展方法。我试图独立地回答dapper。人们可能会发现它非常有用,因此他们在自己的扩展库中采用它。我只是想提一下缺点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-02-15
  • 2018-07-02
  • 2011-01-28
  • 2011-04-20
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
相关资源
最近更新 更多