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

18

我从Marc Gravell (@MarcGravell)的回答中读到了以下内容: https://dev59.com/DlYN5IYBdhLWcg3wO14v#47790712

最后一行说:

作为代码的小优化:在避免创建副本的情况下,优先使用AsList()而不是ToList()。

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

据我理解,System.Linq提供了一个扩展方法IEnumerable.ToList()。以下是Microsoft关于ToList()的说明。

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

IDbConnection.Query()将始终返回IEnumerablenull。在调用代码中可以轻松进行空值检查。那么AsList有什么区别呢?

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

考虑到这一点,AsList()是否比ToList()更适用于返回IEnumerableIDbConnection.Query()?如果是; 为什么?

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


2
看一下源代码,你会看到注释表明,如果数据已经在一个List数据结构中,那么该列表(即实例)将被返回,而不是创建一个新的(即副本)List - Kenneth K.
2
@KennethK.:同意。但是IDbConnection.Query()将始终返回IEnumerablenull。在调用代码中可以轻松进行空值检查。那么AsList有什么区别呢? - Amit Joshi
2个回答

25

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

在这种特定的情况下,你有以下代码:

multipleresult.Read<MerchantProduct>()

在此情况下,multipleresultGridReaderRead 有一个名为 buffered 的参数,默认值为 true。当它为 true 时,Read 将真正返回 List<T>,因此通过调用 ToList,你将再次复制该列表而没有太多理由。

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

如果你喜欢使用 ToList() - 你可以传递 buffered: falseQuery()Read() 来避免创建这个额外的副本。


2
扩展功能可能会对IMO(个人意见)造成影响。例如,如果有人将内部实现从列表更改为数组,则AsList突然返回新列表而不是返回相同的实例。这可能会悄悄地破坏代码。 - Tim Schmelter
3
@TimSchmelter 这在理论上是一个有效的观点,但我不明白它如何会在这种情况下(对于Dapper查询)破坏代码。在这种情况下,您实际上并不关心它是否会返回相同的列表或“新”列表 - 对于您来说,它们都是“新”的。 - Evk
2
@TimSchmelter 如果我更改Dapper的内部实现,我会确保像AsList()这样的注释使用[Obsolete("...", false)],并解释是什么以及为什么;但我怀疑它不会改变。 - Marc Gravell
2
@MarcGravell 为什么要引入那个缓冲参数,并且还默认设置呢?是为了防止不了解 IEnumerable 工作原理的用户意外多次执行查询吗? - Evk
2
@Evk 部分是这样,虽然更常见的情况实际上是读取一次但推迟它 - 此时连接可以关闭或另一个读者可以活动; 还有部分原因是在您确实想要原始流访问(buffered: false)时提供统一的API。 - Marc Gravell

6

这个扩展是一个自定义的 dapper 扩展,在调用 ToList 前进行了额外的检查。 源代码:

public static List<T> AsList<T>(this IEnumerable<T> source) 
    => (source == null || source is List<T>) ? (List<T>)source : source.ToList();
  • ToList总是创建一个新的List<T>实例,并用给定的项填充它。
  • AsList检查序列是否已经是一个List<T>,如果是,则只需将其强制转换即可。

当然,这种方法可能更有效率,因为强制转换比创建和填充新内容要少得多。所以它完全不同。

这有点基于个人观点,但我认为这很危险。有些人可能会忽略AsList并阅读ToList,或者根本不知道它们之间的区别。如果有人以后更改代码,这就很危险。

例如,一个使用AsList的接受IEnumerable<T>的方法:

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);

直到现在这并不会有什么影响。现在,由于源不是列表且无法进行强制转换,因此将初始化并填充新的列表。所以只是效率降低了。但是,如果逻辑还依赖于列表是同一引用,则这将不再起作用。

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

一旦数组被使用,这个值总是false。所以代码默默地崩溃了。

长话短说:我不喜欢AsList方法。你总可以自己检查类型。


3
这里的关键点是 AsList() 旨在与 Dapper 一起使用以避免小的分配;虽然它并非用于所有目的,但老实说在许多情况下它表现良好。 - Marc Gravell
1
@MarcGravell:嗯,如果你添加了using Dapper;,你也可以在外部使用这个扩展,它是IEnumerable<T>的公共扩展方法。我试图独立回答Dapper。人们可能会发现它非常有用,因此他们会将其采用到自己的扩展库中。我只是想提一下缺点。 - Tim Schmelter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接