[英]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
属性的值,并在从方法返回后,原始对象属性被更改。
但是当将对象设置为null
, ChangeToNull1
方法不会更改原始对象,但ChangeToNull2
方法会更改原始对象。
所以我的问题是:
1.为什么C#表现这种方式?
2. C#在传递给方法时是否复制了原始对象?
3.如果是,那么它如何更改像Name
这样的原始对象属性,为什么不更改原始对象?
4. c#在传递给ref
复制了原始对象吗?
在C#中,有两种对象:值类型和引用类型。
值类型是struct
和enum
,例如int
( System.Int32
)。 这些类型在传递时总是被复制。 如果更改方法中的int
,则调用方内的变量不会更改。
您正在谈论引用类型 - 类,数组和接口,基本上。
在引用类型中,例如string
( System.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;
然后, e2
与e1
相同 - 它也是指针,指向同一地址。 如果我们采用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
关于字符串的一些评论
System.String
是一个引用类型,它重载operator ==()
,因此比较两个字符串将给出正确的结果。 string s1 = "abc";
和string s2 = "abc";
, s1
和s2
可以指向相同的地址(用于存储器保存)。 这是可能的,因为字符串是不可变的 - 例如,如果你在字符串上调用Replace()
,它将创建一个新的字符串。 所以你不应该知道这一点(但是如果你编写不安全的代码,这很重要)。 它的行为方式是因为C#将指针值的副本传递给引用类型。 这有点拗口,所以这可能更具启发性:
当你写:
var emp = new Employee { Name = "ABC" };
您正在创建Employee
的实例并将指针存储在变量emp
。 我们假设emp
的内存位置是0x000001
。 并且它的值 (对象的位置)是0x0000AA
。
你打电话时:
ChangeName1(emp);
您传递的值为0x0000AA
。 在方法ChangeName1
, e
值为0x0000AA
,但它的位置不是0x000001
。 它存储在内存中的不同位置。
但是,当你打电话:
ChangeName2(ref emp);
你正在传递emp
的内存位置,即0x000001
。 所以在这个方法中,更新e
也会更新emp
。
要解决这些成员问题 - 如上所述,您不会复制该对象。 ChangeName1
和ChangeName2
引用同一个对象。 它们都引用存储在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.