引用传递的引用类型,不带引用

时间:2018-11-19 06:15:51

标签: c#

以不同方式调用4个方法时,我得到不同的结果:

static void Main(string[] args)
{
   var emp = new Employee { Name = "ABC" };

   ChangeName1(emp);
   Console.WriteLine(emp.Name); //XYZ

   ChangeName2(ref emp);
   Console.WriteLine(emp.Name); //XYZ

   ChangeToNull1(emp);
   Console.WriteLine(emp.Name); // XYZ

   ChangeToNull2(ref emp);
   Console.WriteLine(emp.Name); // Null Reference Exception

   Console.ReadLine();
}

static void ChangeName1(Employee e)
{
   e.Name = "XYZ";
}
static void ChangeName2(ref Employee e)
{
   e.Name = "XYZ";
}
static void ChangeToNull1(Employee e)
{
   e = null;
}
static void ChangeToNull2(ref Employee e)
{
   e = null;
}

如您所见,前两种方法是在更改Name属性的值,并且从该方法返回后,原始对象的属性将被更改。

但是当将对象设置为null时,ChangeToNull1方法不会更改原始对象,但是ChangeToNull2方法会更改原始对象。

所以我的问题是:

1。。为什么C#具有这种方式?

2。。C#在传递给方法时会复制原始对象吗?

3。如果可以,它将如何更改诸如Name之类的原始对象属性,为什么不更改原始对象呢?

4。。c#在ref传递时c#是否复制原始对象?

4 个答案:

答案 0 :(得分:3)

在C#中,有两类对象:值类型和引用类型。

值类型是structenum,例如intSystem.Int32)。这些类型在传递时始终会复制。如果您在方法中更改int,则调用方内部的变量不会更改。

基本上,您在谈论引用类型-类,数组和接口。

在引用类型中,例如stringSystem.String),有两个部分:对象和指针。例如,让我们看看您的Employee。假设我声明了一个名为e1且类型为Employee的变量,并为其分配了名称"abc"

Employee e1 = new Employee { Name = "abc" };

现在,内存中有一个employee对象(堆,因为引用类型几乎总是在堆中分配,所以期望stackalloc),其中包含Name="abc"。 (在堆栈中)还有一个指向该对象的指针。假设我们有此内存映像:

 0x123 - An employee object          0x45F - the variable `e1` - pointer to the employee
|-------------------------|         |------------------|
|     Name = "abc"        |         |     0x123        |
|-------------------------|         |------------------|

如果您在没有ref的情况下通过它,则会复制 e1-0x123 的值,但不会复制员工!因此,如果您更改名称,原来的员工 将会被更改!但是,如果更改指针,将不会发生任何事情,因为指针e1已复制

通过ref传递时,会复制指针的地址-0x45F。因此,如果您更改参数,则它 会更改e1,因为它不是复制的,而是地址。

编辑:

如果我将引用类型变量分配给另一个变量,例如:

var e1 = new Employee { Name = "abc" };
Employee e2 = employee;

然后,e2e1相同-它也是一个指针,指向相同的地址。如果我们获取prevoius内存映像,现在地址0x4AC中有一个名为e2的变量,其中还包含对象对象的地址0x123。因此,如果我们更改e2.Name

e2.Name = "new";

然后,e1.Name现在也变成了"new"。 关于引用类型的最后一个重要事实是,比较(==个引用类型(我是在没有重载运算符==的情况下进行交谈),不会检查它们是否包含相同的值,但是如果它们指向同一个对象。我们来看一个例子:

var e1 = new Employee { Name = "abc" };
Employee e2 = e1;
var e3 = new Employee { Name = "abc" };
var e4 = new Employee { Name = "123" };

Console.WriteLine(e1 == e4); // false
Console.WriteLine(e1 == e3); // false, since they don't point to the same object, they just contain the same values
Console.WriteLine(e1 == e2); // true, since they point to the same object

有关字符串的一些评论:

  1. 尽管类System.String是引用类型,但它会重载operator ==(),因此比较两个字符串将得出正确的结果。
  2. 编译器通常会优化字符串,因此如果string s1 = "abc";string s2 = "abc";s1s2可以指向同一地址(以节省内存)。可能因为字符串是不可变的-例如,如果您在字符串上调用Replace(),它将创建一个新字符串。因此,您不应该知道这一点(但是,如果编写不安全的代码,这很重要)。

答案 1 :(得分:2)

之所以这样,是因为C#将指针值的副本传递给引用类型。这有点令人,舌,所以这可能更能说明问题:

撰写时:

var emp = new Employee { Name = "ABC" }; 

您正在创建Employee的实例,并将指向该对象的 pointer 存储在变量emp中。 假设emp的存储位置为0x000001。并且那个(对象的位置)的0x0000AA

致电时:

ChangeName1(emp);

您正在传递值0x0000AA。在方法ChangeName1中,e的值为0x0000AA,但它的位置不是0x000001。它存储在内存中的其他位置。

但是,当您打电话时:

ChangeName2(ref emp);

您正在传递emp的存储位置,即0x000001。因此,在此方法中,更新e也会更新emp


要对成员致词-如上所述,您不是要复制对象。 ChangeName1ChangeName2都引用相同的对象。它们都引用存储在0x0000AA上的对象。


有关更多信息,请参见When is a C# value/object copied and when is its reference copied?

答案 2 :(得分:1)

myObj = {NY:"good",FL:"bad",CA:"decent"}

在这里,新指针“ e”指向同一个对象“ emp”-因此更改会反映出来。

static void ChangeName1(Employee e)
{
   e.Name = "XYZ";
}

此处将“ emp”作为参考传递-只是将其命名为e。 (这样容易理解)

static void ChangeName2(ref Employee e)
{
   e.Name = "XYZ";
}

此处,新指针“ e”指向同一个对象“ emp”-当您设置e = Null时。新指针为空。不是原始对象。

static void ChangeToNull1(Employee e)
{
   e = null;
}

我想现在您已经知道发生了什么事。

答案 3 :(得分:1)

“ emp”是一个参考变量。指向引用类型实例的变量。

引用变量的工作方式与指针的工作方式相似。裸指针是编程的非常基本的工具,但是处理它们非常危险。因此,.NET团队选择默认情况下不强迫您处理它们。但是,由于它们是如此重要,因此必须发明许多东西来代替它们(参考文献和代表文献是两种常见的文献)。

内存中的对象和对其持有的引用的(数量)完全未实现。内存中的对象可以指向0、1或许多引用。每当选择下一步运行时,GC将收集没有任何引用的对象。

将引用设置为null不会强制进行收集,这使收集成为可能。高概率,给定更多运行时间。但是永远不能保证。