如何克隆一个Collection<T>?

6
我有一个枚举类型 Fruit 和一个继承于 Collection<Fruit> 的类 FruitCollection。我在 .NET 中找不到克隆 FruitCollection 的方法,于是我在 这篇 MSDN 文章 中找到了定义 DeepClone() 函数并使用 MemberwiseClone() 的方法。由于这是一个枚举类型,我认为不需要进行“深度”克隆,所以我认为 MemberwiseClone() 是足够的。然而,在 PowerShell 中尝试时,克隆出来的对象似乎只是原始对象的指针而不是副本。我做错了什么?
是否还有其他简单克隆 Collection 的方法?FruitCollection 没有其他自定义成员。
C# 代码:
public enum Fruit
{
    Apple = 1,
    Orange = 2
}

public class FruitCollection : Collection<Fruit>
{
    public FruitCollection Clone()
    {
        return Clone(this);
    }

    public static FruitCollection Clone(FruitCollection fruitCollection)
    {
        return (FruitCollection)fruitCollection.MemberwiseClone();
    }

}

PowerShell输出:

PS> $basket1 = New-Object TestLibrary.FruitCollection
PS> $basket1.Add([TestLibrary.Fruit]::Apple)
PS> $basket2 = $basket1.Clone()
PS> $basket1.Add([TestLibrary.Fruit]::Orange)
PS> $basket2
Apple
Orange

2
"MemberwiseClone()方法是做什么的?" - Tim Schmelter
@TimSchmelter它是object.MemberwiseClone()(受保护的)。 - Matthew Watson
object.MemberwiseClone() 不会克隆集合的内容。它只会复制对象的字段 - 在这种情况下,其中一个字段将是一个数组(或其他引用集合类型),并且只有引用被复制。 - Matthew Watson
Collection<T> 没有另一个 Collection<T>IEnumerable<T> 的拷贝构造函数?这很奇怪。 - SimpleVar
@YoryeNathan:也许我错过了一些显而易见的东西。 new Collection<Fruit>(fruitCollection) 是合法的,但返回一个 Collection<Fruit> 对象,C# 不想将 Collection<Fruit> 转换为 FruitCollection,即使 FruitCollection 派生自 Collection<Fruit> - Hossy
显示剩余8条评论
3个回答

6

正如其他人在评论中指出的那样,你可以使用已经存在于集合上的构造函数,然后在你的Clone方法中,为新的集合创建一个新的列表,以便向basket1添加商品不会影响到basket2等等。

public class FruitCollection : Collection<Fruit>
{
    public FruitCollection(IList<Fruit> source) : base(source)
    {
    }

    public FruitCollection()
    {
    }

    public FruitCollection Clone()
    {
        return Clone(this);
    }

    public static FruitCollection Clone(FruitCollection fruitCollection)
    {
        // ToList() will give a new List. Otherwise Collection will use the same IList we passed.
        return new FruitCollection(fruitCollection.ToList());
    }

}

void Main()
{
    var basket1 = new FruitCollection();
    basket1.Add(Fruit.Apple);
    var basket2 = basket1.Clone();
    basket2.Add(Fruit.Orange);
    Console.WriteLine("{0}", basket1.Count);
    Console.WriteLine("{0}", basket2.Count);
}

谢谢,@TyCobb。我没有意识到FruitCollection不会自动派生Collection<T>(IList<T> list)构造函数。 - Hossy
@Hossy,是的,在继承时你必须手动定义你想要的构造函数。 - TyCobb
嗯...我总是会犯这个错误。我希望能够自动继承构造函数。 - SimpleVar

2

这是因为你正在进行成员逐一克隆,这是对象的非静态字段的浅层副本。由于你的对象是一个集合(引用类型),它只复制对该集合的引用。

请尝试使用以下方法代替:

public class FruitCollection : Collection<Fruit>
{
    public FruitCollection Clone()
    {
        return Clone(this);
    }

    public static FruitCollection Clone(FruitCollection fruitCollection)
    {
        var clonedFruitCollection = new FruitCollection();

        // Deep copy the collection instead of copying the reference with MemberwiseClone()
        foreach (var fruit in fruitCollection)
        {
            clonedFruitCollection.Add(fruit);
        }

        return clonedFruitCollection;
    }
}

那样确实可以工作,但是难道没有比遍历列表更好/更快的方法吗? - Hossy

1

我尝试了 return new Collection<Fruit>(fruitCollection);,但是出现了错误:无法隐式转换类型 'System.Collections.ObjectModel.Collection<TestLibrary.Fruit>' 到 'TestLibrary.FruitCollection'。存在一个显式的转换(是否缺少强制转换?)return (FruitCollection)new Collection<Fruit>(fruitCollection); 也在运行时出现相同的错误。 - Hossy
当然:所有的FruitCollection都是Collection<Fruit>,但反过来不一定成立。你需要创建一个新的FruitCollection,并在这个类中定义自己的复制构造函数。 - fejesjoco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接