以不同方式调用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#是否复制原始对象?
答案 0 :(得分:3)
在C#中,有两类对象:值类型和引用类型。
值类型是struct
和enum
,例如int
(System.Int32
)。这些类型在传递时始终会复制。如果您在方法中更改int
,则调用方内部的变量不会更改。
基本上,您在谈论引用类型-类,数组和接口。
在引用类型中,例如string
(System.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;
然后,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()
,它将创建一个新字符串。因此,您不应该知道这一点(但是,如果编写不安全的代码,这很重要)。答案 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
。
要对成员致词-如上所述,您不是要复制对象。 ChangeName1
和ChangeName2
都引用相同的对象。它们都引用存储在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不会强制进行收集,这使收集成为可能。高概率,给定更多运行时间。但是永远不能保证。