14

I'm trying to write some code that populates a List (actually, it's a series of Lists, but we can pretend it's just one List). The idea is to add an IPackage to the List for the total quantity of IPackage on order. See the following code:

        ParseExcel pe = new ParseExcel();
        Pinnacle p = pe.ParsePinnacleExcel();
        Rack r = new Rack(20,2,4.5,96,42,6,25*12);
        foreach (PinnacleStock ps in p.StockList.Where(x => 
                 x.ColorCode == "10" && 
                 x.PackageLength == 30.64))
        {
            for (int i = 1; i <= ps.OnOrder; i++)
            {
                r.TryAddPackage((IPackage)ps);
            }
        }

Everything seems to be working well, insofar as the IPackage is repeatedly added to the list. However, it seems that the same instance of the object is being added, i.e. the object is not being copied each time it's added to the list.

What do I need to do to ensure that a copy of the object is inserted into the list, and not just an additional reference?

Ben McCormack
  • 32,086
  • 48
  • 148
  • 223
  • How should the object be copied? – Anon. Jan 21 '10 at 03:31
  • Duplicate http://stackoverflow.com/questions/78536/cloning-objects-in-c/78612#78612 This has a full discussion on the pros and cons of ICloneable and other implmentations – johnc Jan 21 '10 at 03:36
  • If you're getting the same IPackage, then you have many of the same PinnacleStock in Pinnacle.StockList. If this is the case, then follow the answer about ICloneable. If that is not what you expect, then the issue is with the ParseExcel.ParsePinnacleExcel() method, specifically that it is putting the same objects in the StockList over and over again. – Jay Jan 21 '10 at 03:36
  • I really appreciate how multiple users fed off of each others' answers to produce a solid solution to this problem. Well done! FWIW, I wouldn't choose to close this because of the difference in terminology between `Copy` and `Clone`. I used the wrong terminology and perhaps other who do the same will find this helpful. – Ben McCormack Jan 21 '10 at 03:55

4 Answers4

13

Then you need to implement ICloneable and replace

r.TryAddPackage((IPackage)ps);

with

r.TryAddPackage((IPackage)ps.Clone());

It's up to you to decide how Clone should populate the new instance of PinnacleStock that it returns.

At the most basic level, you could say

public PinnacleStock : ICloneable {
    public PinnacleStock Clone() {
        return (PinnacleStock)this.MemberwiseClone();
    }
    object ICloneable.Clone() {
        return Clone();
    }
    // details
}

This will just do a shallow copy of PinnacleStock. Only you know if this is the correct semantics for your domain.

jason
  • 236,483
  • 35
  • 423
  • 525
  • This worked great. As a nod to other suggestions, for a Shallow Clone, you can use `MemberwiseClone()`. However, since it's protected, you have to implement it within the actual class of the object to Clone. Here's how I did it: `public Object Clone(){ return (PinnacleStock)this.MemberwiseClone();}` – Ben McCormack Jan 21 '10 at 03:50
  • 1
    @Ben McCormack: Right. And you can go one step further and declare a method `Clone` that does return a `PinnacleStock` and have `ICloneable.Clone` implemented explicitly. See my edit. – jason Jan 21 '10 at 03:52
5

If you only need a shallow copy, then you can write a quick-fix clone method:

public class PinnacleStock : ICloneable
{
    public PinnacleStock Clone()
    {
        return (PinnacleStock)this.MemberwiseClone();
    }

    object ICloneable.Clone()
    {
        return Clone();
    }

    // Other methods
}

If you need a deep copy (i.e. if PinnacleStock has sub-objects that you want to be copied as well), then you will need to write one yourself.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • To note, `MemberwiseClone` is a `protected` method. It is not publicly accessible. Therefore the above will not compile unless you hide the base method `MemberwiseClone` and declare a `public` method `MemberwiseClone`. – jason Jan 21 '10 at 03:41
  • @Jason I noticed that it didn't compile. However, what do you mean by "hide the base method...and declare a public method"? – Ben McCormack Jan 21 '10 at 03:44
  • I think I found an answer: http://stackoverflow.com/questions/2023210/help-with-c-memberwiseclone – Ben McCormack Jan 21 '10 at 03:46
  • @Ben McCormack: Well, you really shouldn't do this, but you could say `public new PinnacleStock MemberwiseClone() { return (PinnacleStock)base.MemberwiseClone(); }`. If you think that `MemberwiseClone` will solve the problem that you need to solve then it is much better to say `public PinnacleStock Clone() { return (PinnacleStock)this.MemberwiseClone(); } object ICloneable.Clone() { return Clone(); }` I'll spell this out in my original answer. – jason Jan 21 '10 at 03:46
  • @Jason: You're right, I don't know how I managed to miss that. I rewrote it using a public method on the class. – Aaronaught Jan 21 '10 at 03:49
  • @Aaronaught: Ah, it's easy to miss. But one more correction; the return type of `ICloneable.Clone` is `object` so the above is not an implementation of `Clone`. The best way is to add an explicit interface implementation of `ICloneable.Clone` and then you're good to go. – jason Jan 21 '10 at 03:51
  • Incidentally, I've always hated the `ICloneable` interface. It doesn't *do* anything, and it's not generic. For some reason it just annoys me implementing a core interface when the framework itself never looks at it. I added it here because I was writing a `Clone` method anyway... – Aaronaught Jan 21 '10 at 03:52
  • @Jason: Ha ha, and that's another reason I hate it. ;) – Aaronaught Jan 21 '10 at 03:54
  • @Aaronaught: Oh I completely agree. I hate that it could be a shallow copy, it could be a deep copy, or it could be something in between. I also hate that it's not strongly typed. – jason Jan 21 '10 at 03:54
3

As others have said, you would need to make that copy, in some PinnacleStock-specific way:

foreach (PinnacleStock ps in p.StockList.Where(x => x.ColorCode == "10" && 
                                                    x.PackageLength == 30.64))
{
  for (int i = 1; i <= ps.OnOrder; i++)
  {
    PinnacleStock clone = ps.CopySomehow();  // your problem
    r.TryAddPackage((IPackage)clone);
  }
}

However, you might want to question whether this is the right solution. Do you really need a separate instance of the PinnacleStock? Does adding a PinnacleStock to a Rack really create a new, independent instance? Are you planning to modify or track each of these individual copies separately? Is it correct that now you will have PinnacleStock instances that don't appear in your StockList? Not knowing your domain or the semantics of PinnacleStock, it's hard to be sure, but you might want to consider, say, creating a PinnacleStockRackEntry object to represent an instance of a PinnacleStock -- depends on the intended semantics of course!

itowlson
  • 73,686
  • 17
  • 161
  • 157
2

You have to supply the logic to copy the object yourself. .Net does not have deep-copy built-in anywhere (with the notable potential exception of serialization). The closest it comes is the MemberwiseClone() method, but even that would copy references for any members of your type that are themselves reference type.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794