我正在寻找一种方法来创建一个旧的集合,它包含相同的元素。
对于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>
的新实现出现时,我都需要更新该方法......
答案 0 :(得分:1)
没有反思就可以做到这一点:
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>
,那么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>
等)。
答案 1 :(得分:0)
如果它实现IEnumerable<T>
,您可以使用身份投影:
var copy = myEnumerable.Select(item => item);
当然,这只是一个浅表副本,如果T
是引用类型,你只会复制引用,因此两个枚举都将指向相同的对象。
此外,您放弃了可枚举的orignal的特化,但除非您实际为所有预期的集合编写重载,否则无法避免。
答案 2 :(得分:0)
实际上有三种方法可以做到这一点:
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具有巨大的(至少在很多情况下)优势,它是懒惰的(以及枚举),这就是我使用它的原因。 / p>
与cascades进行比较非常有趣:
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派生的其他集合,它就不会工作,因为它不知道它们,所以这个解决方案不是。非常可取。