Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/293.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么结构中的引用类型的行为类似于值类型?_C#_String_Value Type_Reference Type - Fatal编程技术网

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 =

我是C#编程的初学者。我现在正在学习
字符串
结构
值类型
引用类型
。作为和中的公认答案,
字符串
是指针存储在堆栈上而实际内容存储在堆上的引用类型。此外,如中所述,
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