C# 奇怪的对象行为
在处理自定义对象时,我注意到 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
实际上包含该行和“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().”
覆盖该字符串。我想我一定是误会了什么。
I noticed something in C# when dealing with custom objects that I found to be a little odd. I am certain it is just a lack of understanding on my part so maybe someone can enlighten me.
If I create a custom object and then I assign that object to the property of another object and the second object modifies the object assigned to it, those changes are reflected in the same class that did the assigning even though nothing is returned.
You want that in English? Here is an example:
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()");
}
}
In the above sample I would have thought that, because SomeObject.DoSomething()
returns void, this program would only display "I was added from MyProgram.Main()."
. However, the List<string>
in fact contains both that line and "I was added from SomeObject.DoSomething()"
.
Here is another example. In this example the string remains unchanged. What is the difference and what am I missing?
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().";
}
}
This program sample ends up displaying "I was set in MyProgram.Main()"
. After seeing the results of the first sample I would have assumed that the second program would have overwritten the string with "I was set in SomeObject.DoSomething()."
. I think I must be misunderstanding something.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
这并不奇怪,也不奇怪。创建类时,您创建了引用类型。当您传递对对象的引用时,对它们所引用的对象的修改对于持有该对象引用的任何人都是可见的。
因此,在此代码块中,您实例化
List
的新实例,并将对该实例的引用分配给变量myList
。然后将"I was added from MyProgram.Main()."
添加到myList
引用的列表中。然后,您将对同一列表的引用分配给myObject.MyList
(明确地说,myList
和myObject.MyList
都引用相同的列表)List
!然后调用myObject.DoSomething()
,将"I was added from SomeObject.DoSomething()"
添加到myObject.MyList
。由于myList
和myObject.MyList
都引用相同的List
,因此它们打个比方,我有一张写有电话号码的纸,我把它复印给你。现在我拨打那个号码。并告诉线路另一端的人在他们的房子上挂一个横幅,上面写着
“我是从 MyProgram.Main() 添加的。”
在他们的房子上挂上横幅,上面写着“我是从 SomeObject.DoSomething() 添加的”
。好吧,住在有该电话号码的房子里的人现在在他们的房子外面会有两条横幅。一个说,另一个说
有道理吗?
现在,在第二个示例中,情况有点棘手。
首先创建一个新的
字符串
,其值为“I was set in MyProgram.Main()”
,并将对该字符串的引用分配给myString
>。然后将对同一字符串的引用分配给myObject.MyString
。同样,myString
和myObject.MyString
都引用了同一个string
,其值为"I was set in MyProgram.Main( )”
。但是然后你调用 myObject.DoSomething ,其中有这样有趣的行好吧,现在你已经创建了一个新的字符串,其值为
"I was set in SomeObject.DoSomething ()."
并将对该字符串
的引用分配给myObject.MyString
。请注意,您从未更改过myString
所保存的引用。所以现在,myString
和myObject.MyString
引用了不同的字符串!我们再类比一下。我有一张纸,上面有网址。我把那张纸复印下来给你。我们都有一张纸,上面有相同的网址。您划掉该网址并写下另一个地址。它不会影响我在纸上看到的内容!
最后,这个线程中的很多人都在抱怨
string
的不变性。这里发生的事情与string
的不变性无关。This isn't odd, or strange. When you create a class, you create reference type. When you pass references to objects around, modifications to the objects they refer to are visible to anyone that holds a reference to that object.
So in this block of code, you instantiate a new instance of
List<string>
and assign a reference to that instance to the variablemyList
. Then you add"I was added from MyProgram.Main()."
to the list referred to bymyList
. Then you assign a refernce to that same list tomyObject.MyList
(to be explicit, bothmyList
andmyObject.MyList
are referring to the sameList<string>
! Then you invokemyObject.DoSomething()
which adds"I was added from SomeObject.DoSomething()"
tomyObject.MyList
. Since bothmyList
andmyObject.MyList
are referring to the sameList<string>
, they will both see this modification.Let's go by way of analogy. I have a piece of paper with a telephone number on it. I photocopy that piece of paper and give it to you. We both have a piece of paper with the same telephone number on it. Now I call up that number and tell the person on the other end of the line to put a banner up on their house that says
"I was added from MyProgram.Main()."
You call up the person on the other end of the line to put a banner up on their house that says"I was added from SomeObject.DoSomething()"
. Well, the person who lives at the house that has that telephone number is now going to have two banners outside their house. One that saysand another that says
Make sense?
Now, in your second example, it's a little trickier.
You start by creating a new
string
whose value is"I was set in MyProgram.Main()"
and assign a reference to that string tomyString
. Then you assign a reference to that same string tomyObject.MyString
. Again, bothmyString
andmyObject.MyString
are referring to that samestring
whose value is"I was set in MyProgram.Main()"
. But then you invokemyObject.DoSomething
which has this interesting lineWell, now you've created a new
string
whose value is"I was set in SomeObject.DoSomething()."
and assign a reference to thatstring
tomyObject.MyString
. Note that you never changed the reference thatmyString
holds. So now,myString
andmyObject.MyString
are referring to different strings!Let's go by analogy again. I have a piece of paper with a web address on it. I photocopy that piece of paper and give it to you. We both have a piece of paper with the same web address on it. You cross out that web address and write down a different address. It doesn't affect what I see on my piece of paper!
Finally, a lot of people in this thread are yammering about the immutability of
string
. What is going on here has nothing to do with the immutability ofstring
.这是绝对正确的:
这一行将
myList
的引用分配给 myObject 的属性。为了证明这一点,请在
myList
和myObject.MyList
上调用GetHashCode()
。如果您愿意,我们正在讨论指向同一内存位置的不同指针。
It's absolutely correct:
This line assign a reference of
myList
to the myObject's property.To prove this this, call
GetHashCode()
onmyList
and onmyObject.MyList
.we are talking about different pointers to same memory location, if you wish.
方法是否返回某些内容与其内部发生的情况无关。
您似乎对作业的实际含义感到困惑。
让我们从头开始吧。
在内存中分配一个新的
List
对象,并将对其的引用放入myList
变量中。目前,您的代码仅创建一个
List
实例,但您可以在不同位置存储对其的引用。现在
myList
、theSameList
、sameOldList
和someObject.MyList
(依次存储在私有字段中SomeObject
由编译器自动生成)全部引用同一个对象。看一下这些:
只有一个
Person
实例,并且有许多对它的引用。如果我们的鲍勃长大一岁,每个实例的
年龄
也会增加,这是很自然的事情。这是同一个对象。
如果一座城市被重命名,您会期望所有地图都以新名称重新打印。
你觉得奇怪的是
,但是等等,更改没有反映。幕后没有复制。它们就在那里,因为它是同一个对象,如果你改变它,无论你从哪里访问它,你都会访问它的当前状态。
,将项目添加到列表中的位置并不重要:只要您引用同一个列表,您就会看到正在添加的项目。
因此 你的第二个例子,我看到杰森已经为你提供了 更好的解释超出了我所能提供的范围,所以我不会深入讨论。
就足够了
string
的实例,即使它们是可变的(例如
List
其内部状态可通过方法修改),在第二个示例中,您不会更改对象,而是会更改对象。更改参考。alice
的情绪变化会让jack
变成坏人吗?当然不是。同样,更改
myObject.MyString
不会影响局部变量myString
。您不对字符串本身执行任何操作(事实上,您不能)。Whether or not a method returns something, has nothing to do with what happens inside it.
You seem to be confused regarding what assignment actually means.
Let's start from the beginning.
allocates a new
List<string>
object in memory and puts a reference to it intomyList
variable.There is currently just one instance of
List<string>
created by your code but you can store references to it in different places.Right now
myList
,theSameList
,sameOldList
andsomeObject.MyList
(which is in turn stored in a private field ofSomeObject
automagically generated by compiler) all refer to the same object.Have a look at these:
There is just one instance of
Person
, and many references to it.It's only natural that if our Bob grew a year older, each instance's
Age
would have increased.It's the same object.
If a city was renamed, you'd expect all maps to be re-printed with its new name.
You find it strange that
—but wait, changes are not reflected. There's no copying under the hood. They're just there, because it's the same object, and if you change it, wherever you access it from, you access its current state.
So it matters not where you add an item to the list: as long as you're referring to the same list, you'll see the item being added.
As for your second example, I see Jason has already provided you with a much better explanation than I could possibly deliver so I won't go into that.
It will suffice if I say:
string
for a variety of reasons.Even if they were mutable (like
List<T>
that has its internal state modifiable via methods), in your second example, you're not changing the object, you're changing the reference.Would
alice
's change of mood makejack
a bad guy? Certainly not.Similarly, changing
myObject.MyString
doesn't affect local variablemyString
. You don't do anything to the string itself (and in fact, you can't).您混淆了两种类型的对象。
List 是字符串类型的列表.. 这意味着它可以接受字符串:)
当您调用 Add 方法时,它将字符串文字添加到其字符串集合中。
当您调用 DoSomething() 方法时,它可以使用与 Main 中相同的列表引用。因此,当您在控制台中打印时,您可以看到这两个字符串。
You are confusing both type of objects.
A List is a List of type string .. which means it can take strings :)
When you call the Add method it adds the string literal to its collection of strings.
At the time you call your DoSomething() method, the same list reference is available to it as the one you had in Main. Hence you could see both strings when you printed in the console.
不要忘记,您的变量也是对象。在第一个示例中,您创建一个 List<>对象并将其分配给您的新对象。您仅持有对列表的引用,在本例中,您现在持有对同一列表的两个引用。
在第二个示例中,您将特定的字符串对象分配给您的实例。
Don't forget, that your variables are objects too. In the first example, you create a List<> object and assign it to your new object. You only hold a reference to a list, in this case, you now hold two references to the same list.
In the second example you assign a specific string object to your instance.
亚历克斯 - 你写的 -
事实并非如此。函数的VOID只是意味着该函数没有返回值。这与您在 DoSomething() 方法中调用的 this.MyList.Add 方法无关。您必须引用同一个对象 - myList 和 SomeObject 中的 MyList。
Alex - you wrote -
This is not the case. The VOID of the function just means the function does not return a value. This has nothing to do with the this.MyList.Add method you are invoking in the DoSomething() method. You do have to references to the same object - myList and the MyList in the SomeObject.
这是引用类型的行为方式,也是预期的。 myList 和 myObject.MyList 是对堆内存中同一个 List 对象的引用。
在第二个示例中,字符串是不可变的并且按值传递,因此在行
The Contents of myString are copied to myObject.MyString (即通过 value 传递,而不是通过 >引用)
字符串有点特殊,因为它是一种引用类型和一种具有不变性特殊属性的值类型(一旦创建了一个字符串,就不能更改它,只能创建一个新字符串,但这是实施过程中对您有所隐藏)
This is how reference types behave and is expected. myList and myObject.MyList are references to the same List object in heap memory.
In the second example strings are immutable and are passed by value, so on the line
The contents of myString are copied to myObject.MyString (i.e. passed by value not by reference)
String is a bit special because it is a reference type and a value type with the special property of immutability (once you have created a string you can't change it only make a new one, but this is somewhat hidden from you by the implementation)
在第一个示例中......您正在使用可变对象,并且它始终通过引用来访问。 不同对象中对 MyList 的所有引用都指向同一事物。
在另一种情况下,字符串的行为略有不同。声明字符串文字(即引号之间的文本)会创建一个新的字符串实例,与原始版本完全分离。 您不能修改字符串,只能创建一个新字符串。
更新
杰森是对的,它与字符串不变性无关......但是......
我不禁认为字符串不变性在这里有它的用处。不在这个具体示例中,但如果
SomeObject.DoSomething
的代码是这样的:this.MyString += "I was Updated in SomeObject.DoSomething().";
,那么你必须解释新的字符串是通过“串联”创建的,并且第一个字符串没有更新In the first example ... you are working with an mutable objects, and it is always accessed by reerence. All references to MyList in different objects refer to the same thing.
In the other case, strings behave a bit differently. Declaring a string literal (i.e. text between quotes) creates a new instance of a String, completely separated from the original version. You CAN NOT modify a string, just create a new one.
UPDATE
Jason is right, it has nothing to do with String immutability ... but ....
I can't help but think that string immutabiity has its word in here. Not in THIS concrete example, but if
SomeObject.DoSomething
's code was this :this.MyString += "I was updated in SomeObject.DoSomething().";
, then you would have to explain that new String is created by the "concatenation", and the first string is not updated