繁体   English   中英

参考类型由ref传递而没有ref

[英]Reference Type passed by ref and without ref

在以不同方式调用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属性的值,并在从方法返回后,原始对象属性被更改。

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

所以我的问题是:

1.为什么C#表现这种方式?

2. C#在传递给方法时是否复制了原始对象?

3.如果是,那么它如何更改像Name这样的原始对象属性,为什么不更改原始对象?

4. c#在传递给ref复制了原始对象吗?

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

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

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

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

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

现在,内存中有一个雇员对象(堆,因为引用类型几乎总是在堆中分配,期望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() ,它将创建一个新的字符串。 所以你不应该知道这一点(但是如果你编写不安全的代码,这很重要)。

它的行为方式是因为C#将指针值的副本传递给引用类型。 这有点拗口,所以这可能更具启发性:

当你写:

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

您正在创建Employee的实例并将指针存储在变量emp 我们假设emp的内存位置是0x000001 并且它的 (对象的位置)是0x0000AA

你打电话时:

ChangeName1(emp);

您传递的值为0x0000AA 在方法ChangeName1e值为0x0000AA ,但它的位置不是0x000001 它存储在内存中的不同位置。

但是,当你打电话:

ChangeName2(ref emp);

你正在传递emp的内存位置,即0x000001 所以在这个方法中,更新e也会更新emp


要解决这些成员问题 - 如上所述,您不会复制该对象。 ChangeName1ChangeName2引用同一个对象。 它们都引用存储在0x0000AA的对象。


有关进一步阅读,请参阅何时复制C#值/对象以及何时复制其引用?

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

这里,新指针“e”指向同一个对象“emp” - 所以变化反映出来。

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

这里“emp”作为参考传递 - 只是它的名字e。 (这种方式很容易理解)

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

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

static void ChangeToNull2(ref Employee e)
{
   e = null;
}

我想现在你明白了最新情况。

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

引用变量的工作方式与Pointers的工作方式类似。 裸指针是编程的一个非常基本的工具,但处理它们是非常危险的。 因此.NET团队选择不强制您默认处理它们。 但是因为它们非常重要,所以必须发明许多东西来重新制造它们(参考文献和代表们都是常见的)。

内存中的对象和您拥有的引用的(数量)完全没有实现。 内存中的对象可以有0,1个或多个引用点。 只要选择下次运行,GC就会收集没有任何引用的对象。

将引用设置为null不会强制收集,它只会使集合成为可能。 一个很高的适应性,给予更多的运行时间。 但绝不保证。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM