为什么将 List<T>.AddRange 方法设为通用方法会影响性能?
Why would making List<T>.AddRange method a generic one be bad for performance?
我正在阅读 C# 深度(第 3 版),在第 13 章中,在讨论在 C# 4 中包含协变和逆变类型参数的部分中,做出了以下声明:
The parameter for List.AddRange is of type IEnumerable<T>, so in this case you’re treating each list as an IEnumerable <IShape>—something that wouldn’t have been possible before. AddRange could have been written as a generic method with its own type parameter, but it wasn’t—doing this would’ve made some optimizations hard or impossible.
有人可以为这种说法提供一些理由吗?不清楚为什么对我来说是这样。
我认为原因是,IEnumerable<T>
可能只包含 type/implement 接口 T
的对象,并且需要检查 T
中的每个元素的这种情况=13=]
让我们尝试一些会因通用 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
时会发生什么?我们需要在添加列表之前检查列表中的每个元素,以防止抛出异常。
我猜它没有写成 void AddRange<T>(IEnumerable<T> items)
是因为它在 IEnumerable<T>
是 ICollection<T>
时进行了优化。当IEnumerable<T>
为ICollection<T>
时,AddRange内部调用ICollection<T>.CopyTo
,第一个参数为T[]
。 (请注意,List<T>
的底层存储机制是 T[]
)。
基本类型的数组与派生类型的数组不同,因此您不能这样做,例如:
object[] objs = new object[4];
var collection = (new string[4]) as ICollection<string>;
collection.CopyTo(objs,0); //Cannot convert object[] to string[]
这是 "impossible" 的优化。
您可以在此处查看源代码:
https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,79de3e39e69a4811
似乎 AddRange
应该检查 T[]
和 List<T>
,并在这些情况下执行 Array.Copy
,但我猜是 -100。您可能会对 Array.ToArray()
没有做的事情感到有些惊讶。
我正在阅读 C# 深度(第 3 版),在第 13 章中,在讨论在 C# 4 中包含协变和逆变类型参数的部分中,做出了以下声明:
The parameter for List.AddRange is of type IEnumerable<T>, so in this case you’re treating each list as an IEnumerable <IShape>—something that wouldn’t have been possible before. AddRange could have been written as a generic method with its own type parameter, but it wasn’t—doing this would’ve made some optimizations hard or impossible.
有人可以为这种说法提供一些理由吗?不清楚为什么对我来说是这样。
我认为原因是,IEnumerable<T>
可能只包含 type/implement 接口 T
的对象,并且需要检查 T
中的每个元素的这种情况=13=]
让我们尝试一些会因通用 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
时会发生什么?我们需要在添加列表之前检查列表中的每个元素,以防止抛出异常。
我猜它没有写成 void AddRange<T>(IEnumerable<T> items)
是因为它在 IEnumerable<T>
是 ICollection<T>
时进行了优化。当IEnumerable<T>
为ICollection<T>
时,AddRange内部调用ICollection<T>.CopyTo
,第一个参数为T[]
。 (请注意,List<T>
的底层存储机制是 T[]
)。
基本类型的数组与派生类型的数组不同,因此您不能这样做,例如:
object[] objs = new object[4];
var collection = (new string[4]) as ICollection<string>;
collection.CopyTo(objs,0); //Cannot convert object[] to string[]
这是 "impossible" 的优化。
您可以在此处查看源代码: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,79de3e39e69a4811
似乎 AddRange
应该检查 T[]
和 List<T>
,并在这些情况下执行 Array.Copy
,但我猜是 -100。您可能会对 Array.ToArray()
没有做的事情感到有些惊讶。