Создание нового экземпляра IList ‹T› на основе существующего и изменение его

Учитывая приведенный ниже код:

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();
    }
}

Результатом будет:

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...

Может кто-нибудь объяснить мне:

  1. Почему после создания новых списков (list2 и list3) действия в этих списках влияют на list1 (а затем и на два других списка)? и

  2. Как я могу создать новый экземпляр list1 и изменить его, не затрагивая list1?


person oonyalo    schedule 07.02.2013    source источник
comment
У вас есть отдельные независимые списки, содержащие одинаковые элементы. Неудивительно, что если вы измените элемент, он изменится (независимо от того, какой список вы используете для доступа к этому элементу).   -  person Andrew Savinykh    schedule 07.02.2013


Ответы (5)


По сути, у вас есть «поверхностная копия». Да, списки копируются, но они по-прежнему указывают на исходные элементы.

Подумайте об этом так. Список фактически не содержит элементы, которые он содержит, вместо этого он имеет ссылку на него. Итак, когда вы копируете свой список, новый список просто содержит ссылку на исходный элемент. Вам нужно что-то вроде этого

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

где возвратная копия выглядит примерно так:

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

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

}

Это скопирует все данные из элемента, но оставит исходный нетронутым. Существует множество шаблонов и интерфейсов, которые могут предложить лучшие реализации, но я просто хотел показать вам, как это работает.

person Immortal Blue    schedule 07.02.2013
comment
Clone () - это типичный способ выполнения / реализации этой задачи. См. ICloneable здесь msdn.microsoft.com/ en-us / library / - person Beachwalker; 10.02.2013
comment
Да, я знаю это. Проблема с клоном состоит в том, что он усложняет объяснение, которое не требуется. Если бы я представил клон, мне пришлось бы говорить о мелких и глубоких копиях, и это не то, что я хотел бы вдаваться в этот ответ. - person Immortal Blue; 10.02.2013

У вас есть 3 списка, но они содержат те же 4 элемента (т. Е. Ссылки на те же 3 объекта в памяти). Поэтому, когда вы изменяете порядок в элементе в List1, он также вступает в силу для элемента в List2, потому что это тот же объект.

Ни ToList, ни конструктор List не делают глубокой копии.

Если вы хотите также скопировать объекты, вам необходимо скопировать и их, чтобы создать новые ссылки для добавления в новые списки. В .NET вы обычно реализуете ICloneable<T>, чтобы предоставить метод Clone. Если вам это не нужно, вы можете просто создать новые Item и скопировать их свойства.

person driis    schedule 07.02.2013

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

Теперь вы можете использовать IList<T>.Clone() для возврата объектов.

person Aniket Inge    schedule 07.02.2013
comment
Предполагая, что все эти объекты реализуют IClonable (что есть не у многих объектов) и имеют разумные реализации клонирования, которые идут на должную глубину. - person Servy; 07.02.2013

У вас есть 3 отдельных списка, поэтому редактирование этих списков (т.е. добавление нового элемента в список, удаление элемента, установка нового элемента в заданной позиции) - это изменение, которое не повлияет на другие переменные.

Однако элемент в каждом из списков содержит только ссылку на фактический экземпляр Item, и все три списка имеют одни и те же три ссылки. Когда вы меняете элемент, на который ссылается этот список, вы вносите изменение, которое «видно» из других списков.

Чтобы не видеть такого поведения, вам необходимо не только создать новый список, но и убедиться, что элементы в новом (-ых) списке (-ах) являются совершенно новыми ссылками на новые объекты, которые содержат те же значения. В общем случае это нетривиальная задача, да и часто она нежелательна.

person Servy    schedule 07.02.2013

Вам нужно клонировать объекты в списках. В противном случае вы создаете новые списки, и все они указывают на одни и те же объекты.

var listToClone = new List<Item>();

var clonedList = listToClone.Select(item => (Item)item.Clone()).ToList();
person Beachwalker    schedule 07.02.2013