C#奇怪的对象行为

时间:2011-09-26 20:22:00

标签: c# .net class object reference

在处理自定义对象时,我注意到了C#中的一些东西,我发现它有点奇怪。我确信这只是我缺乏理解,所以也许有人可以启发我。

如果我创建一个自定义对象然后将该对象分配给另一个对象的属性,而第二个对象修改分配给它的对象,则这些更改将反映在执行分配的同一个类中,即使没有返回任何内容。

你想用英语吗?这是一个例子:

class MyProgram
{
    static void Main()
    {
        var myList = new List<string>();
        myList.Add("I was added from MyProgram.Main().");
        var myObject = new SomeObject();
        myObject.MyList = myList;
        myObject.DoSomething();

        foreach (string s in myList)
            Console.WriteLine(s); // This displays both strings.
    }
}

public class SomeObject
{
    public List<string> MyList { get; set; }

    public void DoSomething()
    {
        this.MyList.Add("I was added from SomeObject.DoSomething()");
    }
}

在上面的示例中,我会想到,因为SomeObject.DoSomething()返回void,所以此程序只显示"I was added from MyProgram.Main()."。但是,List<string>实际上包含该行和"I was added from SomeObject.DoSomething()"

这是另一个例子。在此示例中,字符串保持不变。有什么区别,我错过了什么?

class MyProgram
{
    static void Main()
    {
        var myString = "I was set in MyProgram.Main()";
        var myObject = new SomeObject();
        myObject.MyString = myString;
        myObject.DoSomething();

        Console.WriteLine(myString); // Displays original string.
    }
}

public class SomeObject
{
    public string MyString { get; set; }

    public void DoSomething()
    {
        this.MyString = "I was set in SomeObject.DoSomething().";
    }
}

此程序示例最终显示"I was set in MyProgram.Main()"。在看到第一个样本的结果后,我会假设第二个程序会用"I was set in SomeObject.DoSomething()."覆盖该字符串。我想我一定是误会了。

8 个答案:

答案 0 :(得分:15)

这不奇怪,也不奇怪。创建类时,可以创建引用类型。当您将引用传递给周围的对象时,对引用该对象的任何人都可以看到对它们引用的对象的修改。

var myList = new List<string>();
myList.Add("I was added from MyProgram.Main().");
var myObject = new SomeObject();
myObject.MyList = myList;
myObject.DoSomething();

因此,在此代码块中,您实例化List<string>的新实例,并将该实例的引用分配给变量myList。然后,您将"I was added from MyProgram.Main()."添加到myList引用的列表中。然后,您将同一列表的引荐分配给myObject.MyList(要明确,myListmyObject.MyList都指的是相同的List<string>!然后您调用myObject.DoSomething()"I was added from SomeObject.DoSomething()"添加到myObject.MyList。由于myListmyObject.MyList都指的是同一个List<string>,因此他们都会看到此修改。

让我们通过类比来做。我有一张纸上有电话号码。我复印那张纸给你。我们都有一张纸,上面有相同的电话号码。现在我打电话给那个号码并告诉线路另一端的人在他们的房子上竖起一条横幅,上面写着"I was added from MyProgram.Main()."你打电话给另一端的那个人竖起横幅他们的房子说"I was added from SomeObject.DoSomething()"。那么,住在有这个电话号码的房子的人现在将在他们家外面有两个横幅。一个说

I was added from MyProgram.Main().

和另一个说

I was added from SomeObject.DoSomething()

有意义吗?

现在,在你的第二个例子中,它有点棘手。

var myString = "I was set in MyProgram.Main()";
var myObject = new SomeObject();
myObject.MyString = myString;
myObject.DoSomething();

首先创建一个值为string的新"I was set in MyProgram.Main()",然后将该字符串的引用分配给myString。然后,将同一字符串的引用分配给myObject.MyString。同样,myStringmyObject.MyString都指的是string,其值为"I was set in MyProgram.Main()"。但是你调用myObject.DoSomething这个有趣的行

this.MyString = "I was set in SomeObject.DoSomething().";

好了,现在您已经创建了一个新的string,其值为"I was set in SomeObject.DoSomething().",并将string的引用分配给myObject.MyString。请注意,您从未更改myString所持有的引用。现在,myStringmyObject.MyString指的是不同的字符串!

让我们再次类推。我有一张纸上有一个网址。我复印那张纸给你。我们都有一张纸,上面有相同的网址。您划掉该网址并写下不同的地址。它不会影响我在纸上看到的内容!

最后,这个帖子中的很多人都在谈论string的不变性。这里发生的事情与string的不变性无关。

答案 1 :(得分:2)

绝对正确:

myObject.MyList = myList; 

此行将myList的引用分配给myObject的属性 要证明这一点,请在GetHashCode()myList上致电myObject.MyList

如果您愿意,我们正在讨论指向相同内存位置的不同指针。

答案 2 :(得分:1)

方法是否返回某些东西,与其中发生的事情无关 你似乎对作业的实际含义感到困惑。

让我们从头开始。

var myList = new List<string>();

在内存中分配一个新的List<string>对象,并将引用放入myList变量。
目前,您的代码只创建了一个List<string>实例,但您可以将引用存储在不同的位置。

var theSameList = myList; 
var sameOldList = myList;
someObject.MyList = myList;

现在myListtheSameListsameOldListsomeObject.MyList(又存储在由编译器自动生成的SomeObject的私有字段中)< strong> all引用同一个对象。

看看这些:

var bob = new Person();
var guyIMetInTheBar = bob;
alice.Daddy = bob;
harry.Uncle = bob;
itDepartment.Head = bob;

只有Person的一个实例,并且很多引用它 如果我们的鲍勃长大一年,每个实例的Age都会增加,这是很自然的 这是同一个目标。

如果某个城市已重命名,您希望所有地图都以其新名称重新打印。

你觉得很奇怪

  

这些更改反映在执行分配的同一个类中

- 但等待,更改不会反映。引擎盖下没有复制品。它们就在那里,因为它是同一个对象,如果你更改它,无论你从哪里访问它,你都可以访问它的当前状态。

因此,重要的是 ,您需要在列表中添加项目:只要您引用相同的列表,就会看到要添加的项目。

至于你的第二个例子,我看到Jason已经提供了一个much better explanation,而不是我可能提供的,所以我不会进入那个。

如果我说:

就足够了
  1. 字符串在.NET中是不可变的,您无法出于各种原因修改string的实例。
  2. 即使它们是可变的(如List<T>,其内部状态可通过方法修改),在第二个示例中,您没有更改对象,您正在更改引用即可。

    var goodGuy = jack;
    alice.Lover = jack;
    alice.Lover = mike;
    
  3. alice情绪的改变会使jack成为坏人吗?当然不是。
    同样,更改myObject.MyString不会影响本地变量myString。你不对字符串本身做任何事情(事实上,你不能)。

答案 3 :(得分:0)

您会混淆这两种类型的对象。 List是一个类型字符串的列表..这意味着它可以采用字符串:)

当您调用Add方法时,它会将字符串文字添加到其字符串集合中。

当您调用DoSomething()方法时,可以使用与Main中相同的列表引用。因此,当您在控制台中打印时,您可以看到两个字符串。

答案 4 :(得分:0)

不要忘记,你的变量也是对象。在第一个示例中,您创建了一个List&lt;&gt;对象并将其分配给新对象。您只保留对列表的引用,在这种情况下,您现在拥有对同一列表的两个引用。

在第二个示例中,您将为您的实例指定一个特定的字符串对象。

答案 5 :(得分:0)

这是参考类型的行为和预期的方式。 myList和myObject.MyList是引用到堆内存中的同一个List对象。

在第二个示例中,字符串是不可变的,并按值传递,因此在行

myObject.MyString = myString;

myString的内容被复制到myObject.MyString(即通过值传递而不是引用

String有点特殊,因为它是一个引用类型和一个具有immutability特殊属性的值类型(一旦你创建了一个你不能改变它的字符串,只能创建一个新的,但这有点隐藏你通过实施)

答案 6 :(得分:0)

亚历克斯 - 你写了 -

  

在上面的示例中我会想到,因为SomeObject.DoSomething()返回void,所以这个程序只显示“我是从MyProgram.Main()添加的。”。但是,List实际上包含该行和“我是从SomeObject.DoSomething()添加的。”

事实并非如此。函数的VOID仅表示函数不返回值。这与您在DoSomething()方法中调用的this.MyList.Add方法无关。您必须引用同一个对象 - myList和SomeObject中的MyList。

答案 7 :(得分:0)

在第一个例子中......你正在使用一个可变对象,它总是通过reerence访问。 在不同对象中对MyList的所有引用都指向相同的内容

在另一种情况下,字符串的行为略有不同。声明字符串文字(即引号之间的文本)会创建一个String的新实例,与原始版本完全分开。 您无法修改字符串,只需创建一个新字符串

<强>更新

杰森是对的,它与字符串不变性无关......但...... 我不禁想到字符串不可变性在这里有所说明。不是在这个具体的例子中,但是如果SomeObject.DoSomething的代码是这样的:this.MyString += "I was updated in SomeObject.DoSomething().";,那么你必须解释新的String是由“连接”创建的,并且第一个字符串没有更新< / p>