6

I have an enumeration, Fruit, and a class, FruitCollection, which derives Collection<Fruit>. I couldn't find a way to clone FruitCollection using .NET and I found this MSDN article which defined a DeepClone() function and used MemberwiseClone(). Now, since this is an enumeration, I don't think I need to "deep" clone it, so I thought MemberwiseClone() would be sufficient. However, when I try it in PowerShell, the cloned object seems to simply be a pointer to the original object and not a clone. What am I doing wrong?

Is there another way to simply clone a Collection? FruitCollection has no other custom members.

C# code:

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 Output:

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
Hossy
  • 139
  • 1
  • 2
  • 11
  • 2
    [What is the method MemberwiseClone() doing?](http://stackoverflow.com/questions/2289420/what-is-the-method-memberwiseclone-doing) – Tim Schmelter Sep 30 '14 at 17:29
  • @TimSchmelter: Are you saying that `Fruit` is a reference type? – Hossy Sep 30 '14 at 17:36
  • @TimSchmelter It's [`object.MemberwiseClone()`](http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone%28v=vs.110%29.aspx) (which is `protected`). – Matthew Watson Sep 30 '14 at 17:40
  • `object.MemberwiseClone()` doesn't clone the *contents* of a collection. It just copies the fields of the object- in this case, one of the fields will be an array (or other reference collection type), and only the reference is copied. – Matthew Watson Sep 30 '14 at 17:42
  • Thanks, @MatthewWatson. So, how do I clone the _contents_ of `FruitCollection`? – Hossy Sep 30 '14 at 17:43
  • `new FruitCollection(this)` doesn't do the trick? – SimpleVar Sep 30 '14 at 17:44
  • @Hossy That's going to depend on what the contents are. You'll need to have objects that know how to clone themselves. – Servy Sep 30 '14 at 17:45
  • @YoryeNathan: No. I get a compile error: 'TestLibrary.FruitCollection' does not contain a constructor that takes 1 arguments – Hossy Sep 30 '14 at 17:49
  • @Servy: The contents are `Fruit` (enumeration). – Hossy Sep 30 '14 at 17:49
  • Maybe I missed this in the question, but are you trying to clone the collection (new collection, but same items) or are you trying to get a new collection where the items inside the original collection were cloned? EDIT: Nevermind, I see Fruit was an enum. – TyCobb Sep 30 '14 at 17:52
  • `Collection` doesn't have a copy constructor of another `Collection`? or `IEnumerable`? strange. – SimpleVar Sep 30 '14 at 17:53
  • @YoryeNathan: Maybe I'm missing something obvious. `new Collection(fruitCollection)` is valid, but returns a `Collection` object and C# does not want to convert `Collection` to `FruitCollection` even though `FruitCollection` is derived from `Collection`. – Hossy Sep 30 '14 at 18:01
  • @TyCobb: I'm trying to create a new `FruitCollection` that contains the same contents as the original `FruitCollection`. – Hossy Sep 30 '14 at 18:03

3 Answers3

6

As others have pointed out in the comments, you can use the constructor that already exists on Collection and then in your Clone, create a new list for the new Collection to use so adding to basket1 doesn't affect basket2 and so forth.

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
  • 8,909
  • 1
  • 33
  • 53
2

It's because you are doing a Memberwise clone, which is a shallow copy of the non-static fields of the object. Since your object is a Collection (reference type), it is only copying a reference to the collection.

Try this instead:

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;
    }
}
Rufus L
  • 36,127
  • 5
  • 30
  • 43
1

Create a new FruitCollection and add all elements to it. You can also create a constructor which does this, see this http://msdn.microsoft.com/en-us/library/fkbw11z0%28v=vs.110%29.aspx.

fejesjoco
  • 11,763
  • 3
  • 35
  • 65
  • I tried `return new Collection(fruitCollection);`, but I get an error: Cannot implicitly convert type 'System.Collections.ObjectModel.Collection' to 'TestLibrary.FruitCollection'. An explicit conversion exists (are you missing a cast?). `return (FruitCollection)new Collection(fruitCollection);` also fails (at runtime) with the same error. – Hossy Sep 30 '14 at 17:59
  • Of course: all FruitCollection's are Collection, but not the other way around. You need to create a new FruitCollection. And define your own copy constructor in this class. – fejesjoco Sep 30 '14 at 18:07