Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a new instance of IList<T> from an existing one and modifying it [duplicate]

Given the the code below:

public class Item
{
    private int _id;
    private int _order;
    private string _name;

    public int Id
    {
        get { return _id; }
        set { _id = value; }
    }

    public int Order
    {
        get { return _order; }
        set { _order = value; }
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public static IList<Item> InitList1()
    {
        var list = new List<Item>
        {
            new Item { Id = 1, Order = 1, Name = "Alpha" },
            new Item { Id = 2, Order = 2, Name = "Bravo" },
            new Item { Id = 3, Order = 3, Name = "Charlie" },
            new Item { Id = 4, Order = 4, Name = "Delta" }
        };

        return list;
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Initialize the lists
        IList<Item> list1 = Item.InitList1();
        IList<Item> list2 = list1.ToList();
        IList<Item> list3 = new List<Item>(list1);

        // Modify list2
        foreach (Item item in list2)
            item.Order++;

        // Modify list3
        foreach (Item item in list3)
            item.Order++;

        // Output the lists
        Console.WriteLine(string.Format("\nList1\n====================="));
        foreach (Item item in list1)
            Console.WriteLine(string.Format("Item - id: {0} order: {1} name: {2}", item.Id, item.Order, item.Name));

        Console.WriteLine(string.Format("\nList2\n====================="));
        foreach (Item item in list2)
            Console.WriteLine(string.Format("Item - id: {0} order: {1} name: {2}", item.Id, item.Order, item.Name));

        Console.WriteLine(string.Format("\nList3\n====================="));
        foreach (Item item in list3)
            Console.WriteLine(string.Format("Item - id: {0} order: {1} name: {2}", item.Id, item.Order, item.Name));

        Console.Write("\nAny key to exit...");
        Console.ReadKey();
    }
}

The output will be:

List1
=====================
Item - id: 1 order: 3 name: Alpha
Item - id: 2 order: 4 name: Bravo
Item - id: 3 order: 5 name: Charlie
Item - id: 4 order: 6 name: Delta


List2
=====================
Item - id: 1 order: 3 name: Alpha
Item - id: 2 order: 4 name: Bravo
Item - id: 3 order: 5 name: Charlie
Item - id: 4 order: 6 name: Delta

List3
=====================
Item - id: 1 order: 3 name: Alpha
Item - id: 2 order: 4 name: Bravo
Item - id: 3 order: 5 name: Charlie
Item - id: 4 order: 6 name: Delta

Any key to exit...

Can someone please explain to me:

  1. Why after creating the new lists (list2 and list3) that the actions on those lists affects list1 (and subsequently the two other lists)? and

  2. How I can create a new instance of list1 and modify it without affecting list1?

like image 588
oonyalo Avatar asked Feb 07 '13 19:02

oonyalo


4 Answers

You've effectively got a "shallow copy". That yes, the lists are copied, but they still point to the original items.

Think of it like this. A list doesn't actually contain the items it contains, instead it has a reference to it. So, when you copy your list the new list just contains a reference to the original item. What you need is something like this

IList newlist = new List<Item>();
foreach(item anItem in myList)
{
     newList.Add(item.ReturnCopy());
}

where return copy looks something like this:

public Item ReturnCopy()
{
    Item newItem = new Item();

    newItem._id = _id;
    newItem._order = _order;
    newItem._name = _name;
    return newItem

}

That will copy all the data from the item, but leave the original intact. There are loads of patterns and interfaces that can offer better implementations, but I just wanted to give you a flavour of how it works.

like image 71
Immortal Blue Avatar answered Sep 25 '22 23:09

Immortal Blue


You have 3 lists, but they contain the same 4 elements (ie. references to the same 3 objects in memory). So when you modify order in an item in List1, it also takes effect in the item in List2 - because it is the same object.

Neither ToList nor the List constructor makes a deep copy.

If you want to copy the objects too, you need to copy them also, to create new references to add to the new lists. In .NET, you would typically implement ICloneable<T> in order to provide a Clone method. If you don't feel you need that, you can just create new Items and copy their properties.

like image 42
driis Avatar answered Sep 23 '22 23:09

driis


static class Extension
{
    public static IList<T> Clone<T>(this IList<T> list) where T: ICloneable
    {
        return list.Select(i => (T)i.Clone()).ToList();
    }
}

Now you can use IList<T>.Clone() to return objects.

like image 39
Aniket Inge Avatar answered Sep 23 '22 23:09

Aniket Inge


You have 3 distinct lists, and so editing those lists (i.e. adding a new item to the list, removing an item, setting a new item at a given position) is a change that won't affect the other variables.

However, the item in each of the lists only contains a reference to an actual Item instance, and all three lists have the same three references. When you change the item that is referenced by that list you are making a change that is "visible" from the other lists.

In order to not see this behavior you need to not only create a new list, but ensure that the items in the new list(s) are brand new references to new objects that happen to contain the same values. In the general case, this isn't a trivial task, nor is it often desirable.

like image 25
Servy Avatar answered Sep 24 '22 23:09

Servy