2017-10-11 67 views
0

我正在寻找一种方法来从包含相同元素的旧集合创建新集合。通用方法来创建任何集合的副本(与匹配类型)

对于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

顺便说一句,你不能创建通用的扩展方法 – MetaColon

+0

@MetaColon是的,我可以。当我用例如'...'替换最后一行'else if ...'时,'CreateCopy'方法编译时没有错误。 'else return null;' – Kjara

+0

我不知道你是如何设法达到这个目的的。 https://stackoverflow.com/a/9238156/7700150 – MetaColon

回答

0

如果它实现IEnumerable<T>您可以使用标识投影:

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

当然,这只是一个浅拷贝,如果T是引用类型,你只会拷贝参考,因此两个枚举将指向相同的对象。

另外,你放弃了orignal enumerable的特化,但这是无法避免的,除非你实际为所有预期集合编写了一个重载。

+0

您是否知道,如果'myEnumerable'实现了'ICollection ',那么cast'(ICollection )myEnumerable.Select(item => item)'是安全的吗? – Kjara

+0

@Kjara是的,这应该工作 – MetaColon

+1

@Kjara不,这是行不通的,就像你通过运行代码看到异常被抛出一样。 – Servy

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>,则不是,而不是通用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>等)。

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具有很大的优势(至少在很多情况下)是懒惰的(以及枚举),这就是为什么我会使用它。


这是相当有趣的比较,如果级联:

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派生的,因为它不会知道它们,所以这个解决方案不是非常明智的。

相关问题