C# 为什么结构中的引用类型的行为类似于值类型?
我是C#编程的初学者。我现在正在学习C# 为什么结构中的引用类型的行为类似于值类型?,c#,string,value-type,reference-type,C#,String,Value Type,Reference Type,我是C#编程的初学者。我现在正在学习字符串,结构,值类型和引用类型。作为和中的公认答案,字符串是指针存储在堆栈上而实际内容存储在堆上的引用类型。此外,如中所述,structs是值类型。现在我试着用一个小例子来练习structs和strings: struct Person { public string name; } class Program { static void Main(string[] args) { Person person_1 =
字符串
,结构
,值类型
和引用类型
。作为和中的公认答案,字符串
是指针存储在堆栈上而实际内容存储在堆上的引用类型。此外,如中所述,structs
是值类型。现在我试着用一个小例子来练习structs
和strings
:
struct Person
{
public string name;
}
class Program
{
static void Main(string[] args)
{
Person person_1 = new Person();
person_1.name = "Person 1";
Person person_2 = person_1;
person_2.name = "Person 2";
Console.WriteLine(person_1.name);
Console.WriteLine(person_2.name);
}
}
上面的代码片段输出
Person 1
Person 2
这让我很困惑。如果
字符串
是引用类型,而结构
是值类型,那么person_1.name和person_2.name应该指向堆上相同的空格区域,不是吗?每个结构实例都有自己的字段person_1.name
是person_2.name
的自变量。这些字段不是静态的
person_2=person_1
按值复制结构
string
是不可变的这一事实并不需要解释这种行为
以下是与类相同的情况,以演示其区别:
class C { public string S; }
C c1 = new C();
C c2 = c1; //copy reference, share object
c1.S = "x"; //it appears that c2.S has been set simultaneously because it's the same object
这里,c1.S
和c2.S
指的是同一个变量。如果将其设置为struct
,则它们将成为不同的变量(就像在代码中一样)c2=c1
然后在以前是对象引用副本的位置上提交结构值的副本。理解这一点的最佳方法是完全理解变量是什么;简单地说,变量是保存值的占位符
那么这个值到底是多少呢?在引用类型中,存储在变量中的值是对给定对象的引用(可以说是地址)。在值类型中,值是对象本身
当您执行AnyType y=x时
实际发生的是,创建存储在x
中的值的副本,然后将其存储在y
中
因此,如果x
是引用类型,那么x
和y
都将指向同一对象,因为它们都持有相同引用的相同副本。如果x
是一种值类型,则x
和y
都将包含两个相同但不同的对象
一旦您理解了这一点,就应该开始理解为什么您的代码会以这种方式运行。让我们一步一步地研究它:
Person person_1 = new Person();
好的,我们正在创建一个值类型的新实例。根据我前面的解释,存储在person_1
中的值是新创建的对象本身。存储此值的位置(堆或堆栈)是一个实现细节,它与代码的行为完全无关
person_1.name = "Person 1";
现在我们正在设置变量name
,它恰好是person\u 1
的一个字段。同样根据前面的解释,name
的值是指向存储字符串“Person 1”
的内存中某处的引用。同样,值或字符串存储的位置也不相关
Person person_2 = person_1;
好的,这是有趣的部分。这里发生了什么?那么,在person\u 1
中存储的值的副本将被制作并存储在person\u 2
中。由于值恰好是值类型的实例,因此将创建所述实例的新副本并将其存储在person_2
中。此新副本有自己的字段name
,存储在该变量中的值也是存储在person\u 1.name
中的值的副本(参考“person 1”
)
现在我们只需重新分配变量person\u 2.name
。这意味着我们正在存储一个指向内存中某个新字符串的新引用。请注意,person_2.name
最初持有存储在person_1.name
中的值的副本,因此无论您对person_2.name
做什么,都不会影响存储在person_1.name
中的任何值,因为您只是在更改。。。没错,一份。这就是为什么你的代码会有这样的行为
作为练习,如果Person
是引用类型,请尝试以类似的方式推断代码的行为
字符串是引用类型,指针存储在堆栈上,而实际内容存储在堆上
不,不,首先,不要再考虑堆栈和堆了。在C#中,这几乎总是错误的思考方式。C#为您管理存储寿命
第二,尽管引用可以实现为指针,但从逻辑上讲,引用不是指针。参考文献就是参考文献。C#既有引用又有指针。不要把它们混在一起。在C#中永远没有指向字符串的指针。有对字符串的引用
第三,对字符串的引用可以存储在堆栈上,但也可以存储在堆上。当您有一个字符串引用数组时,数组内容在堆上
现在让我们来谈谈你的实际问题
Person person_1 = new Person();
person_1.name = "Person 1";
Person person_2 = person_1; // This is the interesting line
person_2.name = "Person 2";
让我们来说明代码的逻辑功能。Person结构只不过是一个字符串引用,因此您的程序与以下程序相同:
string person_1_name = null; // That's what new does on a struct
person_1_name = "Person 1";
string person_2_name = person_1_name; // Now they refer to the same string
person_2_name = "Person 2"; // And now they refer to different strings
当您说person2=person1时,这并不意味着变量person1现在是变量person2的别名。(在C#中有一种方法可以做到这一点,但不是这样。)它的意思是“将person1的内容复制到person2”。对字符串的引用是复制的值
如果不清楚,尝试绘制变量框和参考箭头;复制结构时,会复制箭头,而不是框的副本。想想字符串是字符数组。下面的代码与您的代码类似,但带有数组
public struct Lottery
{
public int[] numbers;
}
public static void Main()
{
var A = new Lottery();
A.numbers = new[] { 1,2,3,4,5 };
// struct A is in the stack, and it contains one reference to an array in RAM
var B = A;
// struct B also is in the stack, and it contains a copy of A.numbers reference
B.numbers[0] = 10;
// A.numbers[0] == 10, since both A.numbers and B.numbers point to same memory
// You can't do this with strings because they are immutable
B.numbers = new int[] { 6,7,8,9,10 };
// B.numbers now points to a new location in RAM
B.numbers[0] = 60;
// A.numbers[0] == 10, B.numbers[0] == 60
// The two structures A and B *are completely separate* now.
}
因此,如果您有一个包含引用(字符串、数组或类)的结构,并且希望实现ICloneable
,请确保还克隆了引用的内容
public class Person : ICloneable
{
public string Name { get; set; }
public Person Clone()
{
return new Person() { Name=this.Name }; // string copy
}
object ICloneable.Clone() { return Clone(); } // interface calls specific function
}
public struct Project : ICloneable
{
public Person Leader { get; set; }
public string Name { get; set; }
public int[] Steps { get; set; }
public Project Clone()
{
return new Project()
{
Leader=this.Leader.Clone(), // calls Clone for copy
Name=this.Name, // string copy
Steps=this.Steps.Clone() as int[] // shallow copy of array
};
}
object ICloneable.Clone() { return Clone(); } // interface calls specific function
}
我想强调一个事实,通过person\u 2.name=“person
public class Person : ICloneable
{
public string Name { get; set; }
public Person Clone()
{
return new Person() { Name=this.Name }; // string copy
}
object ICloneable.Clone() { return Clone(); } // interface calls specific function
}
public struct Project : ICloneable
{
public Person Leader { get; set; }
public string Name { get; set; }
public int[] Steps { get; set; }
public Project Clone()
{
return new Project()
{
Leader=this.Leader.Clone(), // calls Clone for copy
Name=this.Name, // string copy
Steps=this.Steps.Clone() as int[] // shallow copy of array
};
}
object ICloneable.Clone() { return Clone(); } // interface calls specific function
}
class StringClass
{
string value; //lets imagine this is a "value type" string, so it's like int
StringClass(string value)
{
this.value = value
}
}
struct Person
{
public StringClass name;
}
class Program
{
static void Main(string[] args)
{
Person person_1 = new Person();
person_1.name = new String("Person 1"); //imagine the reference value of name is "m1", which points somewhere into the memory where "Person 1" is saved
Person person_2 = person_1; //person_2.name holds the same reference, that is "m1" that was copied from person_1.name
person_2.name = new String("Person 2"); //person_2.name now holds a new reference "m2" to a new StringClass object in the memory, person_1.name still have the value of "m1"
person_1.name = person_2.name //this copies back the new reference "m2" to the original struct
Console.WriteLine(person_1.name);
Console.WriteLine(person_2.name);
}
}
Person 2
Person 2