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

5

6

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?

Fantasia answered 7/2, 2013 at 19:27 Comment(1)
You have separate independent lists that contain same elements. No wonder that if you alter an element it changes (no matter what list you use to access this element).Veronikaveronike
M
12

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.

Meditate answered 7/2, 2013 at 19:33 Comment(2)
Clone() is the typical way how to do/implement this task. See ICloneable here msdn.microsoft.com/en-us/library/…Lectra
Yes, I know that. The problem with clone is it adds complexity to the explanation which is not required. If I introduced clone, I would have to talk about shallow copies and deep copies and thats not something I wanted to go into with this answer.Meditate
P
4

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.

Pantomime answered 7/2, 2013 at 19:30 Comment(0)
I
1
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.

Intrinsic answered 7/2, 2013 at 19:32 Comment(1)
Assuming those objects all implement IClonable (which not a lot of objects will) and have sensible implementations of clone that go to the proper depth.Suggest
L
1

You need to clone the objects within the lists. Otherwise you create new lists and they all point to the same objects.

var listToClone = new List<Item>();

var clonedList = listToClone.Select(item => (Item)item.Clone()).ToList();
Lectra answered 7/2, 2013 at 19:32 Comment(0)
S
1

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.

Suggest answered 7/2, 2013 at 19:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.