【问题标题】:generic method to create a copy of any collection (with matching type)创建任何集合的副本的通用方法(具有匹配类型)
【发布时间】:2017-10-11 13:01:00
【问题描述】:

我正在寻找一种从旧集合创建新集合的方法,其中包含相同的元素。

对于HashSet<T>,它的工作方式如下:

HashSet<T> oldSet = ... // consider it filled with elements
HashSet<T> newSet = new HashSet<T>(oldSet);

List&lt;T&gt; 类似:

List<T> oldList = ... // consider it filled with elements
List<T> newList = new List<T>(oldList);

据我所知,所有ICollection&lt;T&gt; 实现都有这种类型的复制构造函数。

有没有一种方法(我们暂时称它为CreateCopy)对所有ICollection&lt;T&gt; 执行此操作?所以我可以这样称呼它?

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&lt;T&gt; 的新实现时,我都需要更新该方法...

【问题讨论】:

  • 顺便说一句,你不能创建通用扩展方法
  • @MetaColon 是的,我可以。当我用例如替换最后一行else if ... 时,方法CreateCopy 编译没有错误。 else return null;
  • 我不知道你是怎么做到的。 stackoverflow.com/a/9238156/7700150
  • @MetaColon 然后用当前的 C# 版本试试。这个问题已经有 5 年历史了......
  • @MetaColon 当然你可以创建通用的扩展方法。例如,LINQ 是建立在通用扩展方法之上的。你的意思是你声明扩展方法的静态类不能是通用的。方法可以。

标签: c# collections interface copy generic-programming


【解决方案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&lt;T&gt; 实例(并且有这样的实现)。
  • 无反射。
  • 返回您传入的相同类型(如果您传入的是HashSet&lt;T&gt;,则返回HashSet&lt;T&gt;,而不是通用ICollection&lt;T&gt;)。

缺点:

  • 主要是你必须使用的语法来调用它:

    var set = new HashSet<string>();
    // have to specify type arguments because they cannot be inferred
    var copy = set.CreateCopy<HashSet<string>, string>();
    
  • 无法传递接口(@98​​7654327@ 本身) - 应该传递具体类(HashSet&lt;T&gt;List&lt;T&gt; 等)。

【讨论】:

    【解决方案2】:

    如果它实现了IEnumerable&lt;T&gt;,您可以使用身份投影:​​

    var copy = myEnumerable.Select(item => item);
    

    当然,这只是一个浅拷贝,如果T 是一个引用类型,你只会复制这个引用,因此两个枚举都指向同一个对象。

    此外,您也失去了原始可枚举的特化,但这是无法避免的,除非您实际上为所有预期的集合编写重载。

    【讨论】:

    • 你知道如果myEnumerable 实现ICollection&lt;T&gt;,演员(ICollection&lt;T&gt;)myEnumerable.Select(item =&gt; item) 是否安全?
    • @Kjara 不,这是行不通的,你可以通过运行代码看到异常被抛出。
    • @Kjara 不,ICollection&lt;T&gt;IEnumerable&lt;T&gt;,但它并没有解决问题;没有任何IEnumerable&lt;T&gt;ICollection&lt;T&gt;
    【解决方案3】:

    实际上有三种可能的方法来做到这一点:

    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&lt;T&gt; 派生自 IEnumerable&lt;T&gt;

    第一个解决方案使用反射创建副本,第二个使用 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 具有巨大的(至少在很多情况下)优势,即它是惰性的(以及枚举),这就是我使用它的原因。


    比较一下 if 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 派生的集合,它将无法工作,因为它不会知道它们,所以这个解决方案不是非常可取的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-05
      • 1970-01-01
      • 1970-01-01
      • 2018-11-20
      • 2014-11-03
      • 2013-07-15
      • 1970-01-01
      • 2023-03-17
      相关资源
      最近更新 更多