创建任何集合副本的通用方法(具有匹配类型)

时间:2017-10-11 13:01:00

标签: c# collections interface copy generic-programming

我正在寻找一种方法来创建一个旧的集合,它包含相同的元素。

对于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>的新实现出现时,我都需要更新该方法......

3 个答案:

答案 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派生的其他集合,它就不会工作,因为它不知道它们,所以这个解决方案不是。非常可取。