Список <> членов коллекции меняется, когда они не должны - PullRequest
1 голос
/ 11 апреля 2011

У меня есть список, который я заполнил в основном методе консольного проекта. Я передаю эту совокупность методу, который предназначен для того, чтобы взять двух членов совокупности, разложить и рекомбинировать их таким образом, чтобы создать два новых уникальных члена, которые позже будут добавлены в совокупность.

Однако, когда я манипулирую двумя исходными членами, чтобы создать два новых уникальных члена, два исходных члена меняются в исходной совокупности (следовательно, изменяется первоначальная совокупность). Это означает, что когда я иду, чтобы добавить новых членов, я получаю дублирование записей в списке.

Я не делаю ничего слишком сложного, я думаю, что я делаю что-то глупое.

Есть ли у кого-нибудь понимание того, почему это происходит?

Вот метод, который вызывается для выбора первых двух членов населения:

public static List<Chromosome<Gene>> runEpoch(Random rand, List<Chromosome<Gene>> population, SelectionMethod selectionMethod)
    {
        int populationSize = population.Count;
        int selectionCount = (int)Math.Truncate((population.Count * 0.75));

        if (selectionMethod == SelectionMethod.Tournament)
        {
            for (int i = 0; i < selectionCount; i++)
            {
                Chromosome<Gene> parent = selection.runTournament(rand, population);
                Chromosome<Gene> parentTwo = selection.runTournament(rand, population);

                //Checks for the presence of incestuous mating. In some cases incestuous mating causes a stack overflow to occur that the program can not recover from 
                if (parent != parentTwo)
                {
                    //where runGeneOperators calls the crossOver method directly
                    offSpring = runGeneOperators(rand, parent, parentTwo);
                }
                else
                {
                    i--;
                }
            }
        }
        else
        {
            //NSGAII
        }
        //fixPopulation is meant to sort and remove any excess members
        return fixPopulation(rand, population, selectionMethod, populationSize); ;
    }

А вот код, который создает два новых уникальных члена:

public List<Chromosome<Gene>> crossOver(Random rand, Chromosome<Gene> parentOne, Chromosome<Gene> parentTwo)
    {
        List<Chromosome<Gene>> offSpring = new List<Chromosome<Gene>>();

        int crossPtOne = rand.Next(0, parentOne.Length);
        int crossPtTwo = rand.Next(0, parentTwo.Length);

        if ((crossPtOne == 0) && (crossPtTwo == 0))
        {
            offSpring.Add(parentOne);
            offSpring.Add(parentTwo);

            return offSpring;
        }
        else
        {
            GeneNode<Gene> fragOne = parentOne.Children[crossPtOne];
            GeneNode<Gene> fragTwo = parentTwo.Children[crossPtTwo];

            crossOverPoint = crossPtOne;
            GeneNode<Gene> genotype = performCrossOver(parentOne.Genotype, fragTwo);
            success = false;

            parentOne.repair(genotype);

            offSpring.Add(parentOne);

            crossOverPoint = crossPtTwo;

            GeneNode<Gene> genotype2 = performCrossOver(parentTwo.Genotype, fragOne);
            success = false;

            parentTwo.repair(genotype2);

            offSpring.Add(parentTwo);
        }

        return offSpring;
    }

    private GeneNode<Gene> performCrossOver(GeneNode<Gene> tree, GeneNode<Gene> frag)
    {
        if (tree != null)
        {
            if (crossOverPoint > 0)
            {
                if (!success && tree.Left != null)
                {
                    crossOverPoint--;
                    tree.Children[0] = performCrossOver(tree.Left, frag);
                }
            }

            if (crossOverPoint > 0)
            {
                if (!success && tree.Right != null)
                {
                    crossOverPoint--;
                    tree.Children[1] = performCrossOver(tree.Right, frag);
                }
            }
        }

        if (!success)
        {
            if (crossOverPoint == 0)
            {
                success = true;
                return frag;
            }
        }

        return tree;
    }

Ответы [ 3 ]

5 голосов
/ 11 апреля 2011

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

0 голосов
/ 11 апреля 2011

Пункт 1 - Храните ссылки в вашем списке

С точки зрения API, объект никогда не должен давать ссылку на свой внутренний список. Это связано с проблемой, которую вы только что обнаружили, из-за которой другой потребительский код может изменить этот список так, как он считает нужным. Каждая ссылка в этом списке, как обмениваемая, включая объект-владелец, обновляет свой список. Обычно это не то, что было задумано.

Кроме того, потребитель не несет ответственности за создание собственной копии. Ответственность за сохранение приватности его версии лежит на владельце списка.

Создать новый список достаточно просто

private List<Gene> initialGeneList;
public List<Gene> InitialGenes 
{ 
    get { return new List<Gene>(initialGeneList); } // return a new list
}

Этот совет также включает передачу вашего собственного списка другому классу / методу.

Пункт 2 - рассмотрите возможность использования неизменного объекта

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

Типы реальных значений в .net, такие как целые числа и DateTimes, имеют семантику копирования при присваивании. например При назначении копия создается.

Ссылочные типы также могут вести себя как типы значений, и строки являются хорошим примером. Многие люди делают следующую ошибку, потому что они не понимают семантику типа значения System.String.

string s = "Hello World";
s.Substring(0,5);     // s unchanged. still 'Hello World'
s = s.Substring(0,5); // s changed. now 'Hello'

Строковый метод Substring() не изменяет исходный объект, а вместо этого возвращает новый, содержащий результат операции.

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

При реализации неизменяемых объектов одна оговорка, вероятно, необходима для реализации равенства значений для сравнения двух объектов (путем переопределения Equals). Объекты являются ссылочными типами, и по умолчанию два объекта равны, если их ссылки равны.

В этом фрагменте значения объектов равны, но это разные объекты.

string s1 = "Hello";
string s2 = string.Concat("He","l", "lo");
bool equal = s1.Equals(s2);
bool referenceEqual = object.ReferenceEquals(s1, s2);

// s1 == s2 => True, Reference Equal => False
Console.Write("s1 == s2 => {0}, Reference Equal => {1}", equal, referenceEqual);
0 голосов
/ 11 апреля 2011

Чтобы манипулировать своей коллекцией и объектами внутри нее, не изменяя ее, вам необходимо выполнить глубокое клонирование коллекции и содержащей ее объектов, чтобы вы могли изменить список A без изменения клон B. Если вы просто Если вы хотите изменить какой-либо объект внутри списка, вам нужно сделать глубокий клон объекта внутри списка, а затем изменить глубокий клон. Это гарантирует, что ваш оригинал не будет изменен.

Интерфейс IClonable должен быть реализован на ваших объектах, чтобы сделать их клонируемыми.

РЕДАКТИРОВАТЬ: Согласно комментариям Хенка, вы должны просто реализовать свой собственный метод копирования без реализации интерфейса IClonable, согласно этой статье .

Клонирование объектов происходит в двух формах: глубокий и неглубокий .

Shallow : эта форма клонирования создает новый объект, а затем копирует нестатические поля текущего объекта в новый объект. Если поле является типом значения, выполняется побитовая копия поля. Если поле является ссылочным типом, ссылка копируется, а ссылочный объект - нет; следовательно, исходный объект и его клон ссылаются на один и тот же объект.

Deep : Это создает новый объект и клонирует все элементы данных и поля клонируемого объекта. Этот объект ссылается на X2, где оригинал будет ссылаться на X.

Здесь сообщение от SO, которое может помочь вам в клонировании.

...