AsList()比使用返回IEnumerable的IDbConnection.Query()的ToList()更好吗?

Ami*_*shi 10 c# linq performance database-performance dapper

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

最后一行说:

作为代码的次要优化:首选AsList()到ToList()以避免创建副本.

该陈述是关于QueryMultiple()哪些回报GridReader.

在我的理解中,System.Linq提供了一种扩展方法IEnumerable.ToList().以下是从微软ToList().

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

IDbConnection.Query()将永远返回IEnumerablenull.在调用代码时可以轻松完成空检查.那么有什么区别AsList呢?

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

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

AsList()在这种情况下,内部是什么使它成为更好的选择?

Evk*_*Evk 11

AsList是一种自定义Dapper扩展方法.它所做的只是检查IEnumerable<T>你是否真的传递给它List<T>.如果是 - 它将它返回,只是转换为List<T>.如果不是 - 它会定期调用ToList.关键是 - ToList()总是创建一个副本,即使你传递给它的是一个列表.AsList()方法避免执行此复制,因此如果不需要此类复制,则非常有用.

在此特定方案中,您具有以下代码:

multipleresult.Read<MerchantProduct>()
Run Code Online (Sandbox Code Playgroud)

哪里multipleresultGridReader.Read具有buffered默认情况下为true的参数.当它真实 - Read将真正返回List<T>,所以通过调用ToList你将再次复制该列表没有太多理由.

同样如此IDbConnection.Query()- 是也有buffered参数,默认情况下为true,因此它也会默认返回List<T>.

如果你喜欢使用ToList()-你可以传递buffered: falseQuery()Read()以避免产生额外的副本.

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

Tim*_*ter 6

此扩展是一个自定义的简洁扩展,它在调用之前执行额外的检查ToList来源

public static List<T> AsList<T>(this IEnumerable<T> source) 
    => (source == null || source is List<T>) ? (List<T>)source : source.ToList();
Run Code Online (Sandbox Code Playgroud)
  • ToList总是创建一个新List<T>实例并用给定的项目填充它
  • AsList检查序列是否已经是 a List<T>,然后它将直接转换它

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

这是基于意见的,但我发现这很危险。有人可能会忽略AsList并阅读ToList或只是不知道其中的区别。如果以后有人更改代码,那就很危险了。

例如,采用以下方法的IEnumerable<T>方法AsList

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

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

IEnumerable<string> sequence = (gets a list from somewhere)
List<string> userList = GetResult(sequence);
Run Code Online (Sandbox Code Playgroud)

后来有人认为数组在这里更合适:

IEnumerable<string> sequence = (gets an array from somewhere)
List<string> userList = GetResult(sequence);
Run Code Online (Sandbox Code Playgroud)

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

if(userList == seq)
{
    // do something
}
Run Code Online (Sandbox Code Playgroud)

false一旦使用了数组,就会出现这种情况。于是密码就被悄无声息地破解了。

长话短说:我不喜欢这种AsList方法。您始终可以自己检查类型。

  • 这里的关键点是 `AsList()` 旨在*与 Dapper* 一起使用,以避免较小的分配;它并不适用于所有用途,尽管坦率地说它在许多用途中都工作得很好 (3认同)