【问题标题】:Why would making List<T>.AddRange method a generic one be bad for performance?为什么将 List<T>.AddRange 方法设为通用方法对性能不利?
【发布时间】:2018-08-08 10:30:44
【问题描述】:

我正在阅读 C# 深度(第 3 版)和第 13 章,在讨论在 c# 4 中包含协变和逆变类型参数的部分中,提出了以下声明:

List.AddRange 的参数是 IEnumerable 类型的,因此在这种情况下,您将每个列表都视为一个 IEnumerable ——这在以前是不可能的。 AddRange 可以编写为具有自己类型参数的泛型方法,但事实并非如此——这样做会使一些优化变得困难或不可能。

谁能为这种说法提供一些理由?不清楚为什么它对我来说是正确的。

【问题讨论】:

  • 把这个 sn-p 断章取意会让人很难理解它在说什么。特别是因为AddRange 有一个通用参数。但它说的是,让AddRange 接受一个不同的 类型,例如可以转换为T 的Y,会使优化变得困难。
  • @Mahmoud 允许实现或继承自 T 的类型
  • 我也不明白你的问题。由于List&lt;T&gt; 已经 泛型,T 作为泛型参数,AddRange 也具有完全相同的参数。因此不能将另一种类型的实例添加到列表中。那么你的问题是什么?
  • 无论如何:为什么这个问题的投票率如此之高,尽管似乎没有人理解它?
  • 我的猜测是 Jon 暗示了类似 void AddRange&lt;TDerived&gt;(IEnumerable&lt;TDerived&gt; collection) where TDerived : T 的签名,但这意味着您需要迭代新项目而不是可能使用 Array.Copy 作为 it does now?跨度>

标签: c# .net performance generics covariance


【解决方案1】:

我猜它不是写成void AddRange&lt;T&gt;(IEnumerable&lt;T&gt; items) 是因为当IEnumerable&lt;T&gt;ICollection&lt;T&gt; 时它所做的优化。当IEnumerable&lt;T&gt;ICollection&lt;T&gt;时,AddRange内部调用ICollection&lt;T&gt;.CopyTo,其第一个参数为T[]。 (注意List&lt;T&gt; 的底层存储机制是T[])。

基类型的数组与派生类型的数组不同,因此您不能这样做,例如:

object[] objs = new object[4];
var collection = (new string[4]) as ICollection<string>;
collection.CopyTo(objs,0); //Cannot convert object[] to string[]

这是“不可能”的优化。

您可以在此处查看源代码: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,79de3e39e69a4811

似乎AddRange 应该检查T[]List&lt;T&gt;,并在这些情况下执行Array.Copy,但我猜是-100。你可能会对Array.ToArray() 没有做的事情感到有些惊讶。

【讨论】:

  • 是的,我想这基本上就是我想要的。完全有可能这是一个不正确的断言,但我认为这是我当时的推理路线。
【解决方案2】:

我认为原因是IEnumerable&lt;T&gt; 可能只包含类型/实现接口T 的对象,并且需要在AddRange&lt;T2&gt; 中的每个元素上检查此条件

让我们尝试一些会因泛型AddRange 而崩溃的代码

public class MyClass1 : IFoo, IBar
{
  /* some code */
}

public class MyClass2 : IFoo
{
  /* some code */
}

var fooList = new List<IFoo>
{
  new MyClass1(),
  new MyClass2()
}
var barList = new List<IBar>();
barList.AddRange<IFoo>(fooList);

现在,问题是,它应该如何反应?您可以将MyClass1 的对象添加到barList,因为它实现了IBar,但是当您尝试添加MyClass2 时会发生什么?我们需要在添加之前检查列表中的每个元素,以防止抛出异常。

【讨论】:

  • 但是添加类型约束可以解决这个问题。
  • 但是一旦你添加了类型约束,就没有实际意义了,因为如果你创建AddRange&lt;T2&gt; where T2:T,你可以只拥有AddRange&lt;T&gt;,这是一回事。你可以有AddRange&lt;T2&gt; where T2:IConvertible,然后在里面调用ToType(T)。那么,有道理吗?显然,对这种通用 AddRange 的任何需求首先都是糟糕的代码设计。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-19
  • 1970-01-01
相关资源
最近更新 更多