通用方法来创建任何集合的副本(与匹配类型)
我正在寻找一种方法来从包含相同元素的旧集合创建新集合。通用方法来创建任何集合的副本(与匹配类型)
对于HashSet<T>
它的工作原理是这样的:
HashSet<T> oldSet = ... // consider it filled with elements
HashSet<T> newSet = new HashSet<T>(oldSet);
对于List<T>
它类似于:
List<T> oldList = ... // consider it filled with elements
List<T> newList = new List<T>(oldList);
据我所知,所有ICollection<T>
实现有这种类型的拷贝构造函数。
有没有一种方法(现在我们称之为CreateCopy
),它对所有ICollection<T>
都这样做?为什么我可以这样称呼它?
ICollection<T> oldColl = ... // can be List, HashSet, ...
ICollection<T> newColl = oldColl.CreateCopy(); // will have the same type as oldColl
如果不是,我该如何编写自己的方法来实现这个目标?这使我心中唯一的想法是这样的:
public static ICollection<T> CreateCopy<T>(this ICollection<T> c)
{
if (c is List<T>) return new List<T>(c);
else if (c is HashSet<T>) return new HashSet<T>(c);
else if ...
}
但当然,这是一个可怕的解决方案 - 每当一个新的执行ICollection<T>
恶有恶报,我需要更新方法...
如果它实现IEnumerable<T>
您可以使用标识投影:
var copy = myEnumerable.Select(item => item);
当然,这只是一个浅拷贝,如果T
是引用类型,你只会拷贝参考,因此两个枚举将指向相同的对象。
另外,你放弃了orignal enumerable的特化,但这是无法避免的,除非你实际为所有预期集合编写了一个重载。
一个选择这样做,而不反思的是:
public static class Extensions {
public static TCollection CreateCopy<TCollection, TItem>(this TCollection c) where TCollection : ICollection<TItem>, new() {
var copy = new TCollection();
foreach (var item in c) {
copy.Add(item);
}
return copy;
}
}
这有以下好处:
- 类型安全。无法传递没有无参数构造函数的
ICollection<T>
实例(并且有这样的实现)。 - 没有反思。
- 返回与您传入的相同类型(如果您通过
HashSet<T>
,则不是,而不是通用ICollection<T>
)。
缺点:
-
主要一个是你必须使用调用它的语法:
var set = new HashSet<string>(); // have to specify type arguments because they cannot be inferred var copy = set.CreateCopy<HashSet<string>, string>();
无法通过接口(
ICollection<T>
本身) - 应该通过具体类(HashSet<T>
,List<T>
等)。
实际上有三种可能的方式来做到这一点:
public static ICollection<T> CreateCopyReflection<T> (this ICollection<T> c)
{
var n = (ICollection<T>) Activator.CreateInstance (c.GetType());
foreach (var item in c)
n.Add (item);
return n;
}
public static IEnumerable<T> CreateCopyLinq<T> (this IEnumerable<T> c) => c.Select (arg => arg);
public static IEnumerable<T> CreateCopyEnumeration<T> (this IEnumerable<T> c)
{
foreach (var item in c)
yield return item;
}
请注意,我们可以在这里使用IEnumerables无需担心,因为ICollection<T>
从IEnumerable<T>
派生。
第一个解决方案使用反射创建副本,第二个使用Linq,第三个使用枚举。
var myList = Enumerable.Range (0, 100000000).ToList();
var trueCopy = new List<int> (myList);
var time = Environment.TickCount;
var copyOne = myList.CreateCopyReflection();
Console.WriteLine($"Refelection copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyTwo = myList.CreateCopyLinq();
Console.WriteLine ($"Linq copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyThree = myList.CreateCopyEnumeration();
Console.WriteLine ($"Enumeration copy: {Environment.TickCount - time}");
time = Environment.TickCount;
导致:现在,我们可以用下面的代码资料这个
Reflection copy: 1375
Linq copy: 0
Enumeration copy: 0
然而,我们必须记住,C#是懒惰这里,这意味着它实际上并没有计算的值,因此枚举IEnumerables时,我们只得到类似的结果:
var myList = Enumerable.Range (0, 100000000).ToList();
var trueCopy = new List<int> (myList);
var time = Environment.TickCount;
var copyOne = myList.CreateCopyReflection().ToList();
Console.WriteLine($"Reflection copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyTwo = myList.CreateCopyLinq().ToList();
Console.WriteLine ($"Linq copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyThree = myList.CreateCopyEnumeration().ToList();
Console.WriteLine ($"Enumeration copy: {Environment.TickCount - time}");
time = Environment.TickCount;
导致:
Reflection copy: 1500
Linq copy: 1625
Enumeration copy: 3140
所以我们可以看到枚举是最慢的,然后是linq然后反射。然而,反射和linq非常接近,并且linq具有很大的优势(至少在很多情况下)是懒惰的(以及枚举),这就是为什么我会使用它。
这是相当有趣的比较,如果级联:
private static void Main()
{
var myList = Enumerable.Range (0, 100000000).ToList();
var trueCopy = new List<int> (myList);
var time = Environment.TickCount;
var copyOne = myList.CreateCopyReflection().ToList();
Console.WriteLine($"Reflection copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyTwo = myList.CreateCopyLinq().ToList();
Console.WriteLine ($"Linq copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyThree = myList.CreateCopyEnumeration().ToList();
Console.WriteLine ($"Enumeration copy: {Environment.TickCount - time}");
time = Environment.TickCount;
var copyFour = myList.CreateCopyCascade();
Console.WriteLine($"Cascade copy: {Environment.TickCount - time}");
time = Environment.TickCount;
Console.ReadLine();
}
public static ICollection<T> CreateCopyReflection<T> (this ICollection<T> c)
{
var n = (ICollection<T>) Activator.CreateInstance (c.GetType());
foreach (var item in c)
n.Add (item);
return n;
}
public static IEnumerable<T> CreateCopyLinq<T> (this IEnumerable<T> c) => c.Select (arg => arg);
public static IEnumerable<T> CreateCopyEnumeration<T> (this IEnumerable<T> c)
{
foreach (var item in c)
yield return item;
}
public static ICollection<T> CreateCopyCascade<T> (this ICollection<T> c)
{
if (c.GetType() == typeof(List<T>))
return new List<T> (c);
if (c.GetType() == typeof(HashSet<T>))
return new HashSet<T> (c);
//...
return null;
}
导致:
Reflection copy: 1594
Linq copy: 1750
Enumeration copy: 3141
Cascade copy: 172
所以我们可以看到,级联方式速度更快 - 然而,它赢得了”如果其他集合是从ICollection派生的,因为它不会知道它们,所以这个解决方案不是非常明智的。
顺便说一句,你不能创建通用的扩展方法 – MetaColon
@MetaColon是的,我可以。当我用例如'...'替换最后一行'else if ...'时,'CreateCopy'方法编译时没有错误。 'else return null;' – Kjara
我不知道你是如何设法达到这个目的的。 https://stackoverflow.com/a/9238156/7700150 – MetaColon