列表与LT;>收集成员不应该改变

时间:2011-04-10 20:34:35

标签: c# .net list data-structures

我有一个List,我已经在控制台项目的main方法中填充了。我将这一群体传递给一种方法,该方法旨在将两个群体成员分解并重新组合它们,以便创建两个新的独特成员,这些成员随后将被添加到群体中。

然而,当我操纵两个原始成员来创建两个新的唯一成员时,两个原始成员在初始填充中更改(因此改变了初始填充)。这意味着当我去添加新成员时,我得到重复列表中的条目。

我没有做任何过于复杂的事情我认为我只是在做一些愚蠢的事情。

有没有人知道为什么会发生这种情况?

以下是调用以选择初始化两个人口成员的方法:

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 个答案:

答案 0 :(得分:5)

在C#中,对象是引用类型,这意味着向集合添加内容只会添加引用。如果使用相同的引用(在您的情况下,“原始”对象)操作变量,则指向该对象的所有引用也将更改。您需要以某种方式复制对象以拥有不同的对象并对其进行操作。

答案 1 :(得分:0)

要在不改变它的情况下操纵你的集合和对象,你需要对集合进行深度克隆并且它包含对象,这样你就可以改变列表A而不改变克隆 B.如果你只是希望更改列表中的某个对象,您需要深入克隆列表中的对象,然后更改深度克隆。这样可以确保您的原件不会被更改。

应在对象上实现IClonable接口以使其可克隆。

编辑:根据this文章,您应该实施自己的复制方法,而无需实施IClonable界面。

克隆对象有两种形式,

Shallow :这种克隆形式创建一个新对象,然后将当前对象的非静态字段复制到新对象。如果字段是值类型,则执行字段的逐位复制。如果字段是引用类型,则复制引用但不引用引用的对象;因此,原始对象及其克隆引用相同的对象。

Deep :这将创建一个新对象并克隆可克隆对象的所有数据成员和字段。此对象引用X2,其中原始引用X.

Here来自SO的帖子可能会帮助您克隆。

答案 2 :(得分:0)

第1项 - 保持列表引用为私有

从API的角度来看,对象永远不应该提供对其内部列表的引用。这是由于您刚刚找到的问题,其中一些其他消费者代码可以继续并按其认为合适的方式修改该列表。该列表的每个引用(包括“拥有”对象)都会更新其列表。这通常不是预期的。

此外,消费者不负责制作自己的副本。责任在列表的所有者身上,以保持其版本的私密性。

创建新列表

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

此建议还包括将您自己的列表传递给另一个类/方法。

第2项 - 考虑使用不可变对象

对于引用vs值类型没有过多考虑,这意味着将对象视为值类型,并且只要有操作,就返回一个新对象而不是改变原始对象。

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