[英]Passing a class as a ref parameter in C# does not always work as expected. Can anyone explain?
我一直认为默认情况下,带有类类型的方法参数作为引用参数传递。 显然情况并非如此。 在C#中考虑这些单元测试(使用MSTest)。
[TestClass]
public class Sandbox
{
private class TestRefClass
{
public int TestInt { get; set; }
}
private void TestDefaultMethod(TestRefClass testClass)
{
testClass.TestInt = 1;
}
private void TestAssignmentMethod(TestRefClass testClass)
{
testClass = new TestRefClass() { TestInt = 1 };
}
private void TestAssignmentRefMethod(ref TestRefClass testClass)
{
testClass = new TestRefClass() { TestInt = 1 };
}
[TestMethod]
public void DefaultTest()
{
var testObj = new TestRefClass() { TestInt = 0 };
TestDefaultMethod(testObj);
Assert.IsTrue(testObj.TestInt == 1);
}
[TestMethod]
public void AssignmentTest()
{
var testObj = new TestRefClass() { TestInt = 0 };
TestAssignmentMethod(testObj);
Assert.IsTrue(testObj.TestInt == 1);
}
[TestMethod]
public void AssignmentRefTest()
{
var testObj = new TestRefClass() { TestInt = 0 };
TestAssignmentRefMethod(ref testObj);
Assert.IsTrue(testObj.TestInt == 1);
}
}
结果是AssignmentTest()
失败,另外两个测试方法通过。 我假设问题是将新实例分配给testClass
参数会破坏参数引用,但不知何故显式添加ref
关键字会修复此问题。
谁能对这里发生的事情做出详细的解释? 我主要是想扩展我对C#的了解; 我没有任何具体的情况我试图解决......
几乎总是被遗忘的事情是,类不是通过引用传递的,对类的引用是通过值传递的。
这个很重要。 不是复制整个类(在定型意义上传递值),而是复制对该类的引用 (我试图避免说“指针”)。 这是4或8个字节; 比复制整个班级更加可口,实际上意味着班级是“通过引用”传递的。
此时,该方法拥有自己对类的引用的副本 。 分配给该参考方法(该方法重新分配仅其自己的基准副本)内作用域。
取消引用该引用(如同,与类成员交谈)将按预期工作:除非您更改它以查看新实例(这是您在失败测试中执行的操作),否则您将看到基础类。
使用
ref
关键字有效地通过引用传递引用本身(指针指向事物的指针)。
和往常一样,Jon Skeet提供了一个非常好的概述:
http://www.yoda.arachsys.com/csharp/parameters.html
注意“参考参数”部分:
引用参数不传递函数成员调用中使用的变量的值 - 它们使用变量本身。
如果该方法将某些内容分配给ref
引用,则调用者的副本也会受到影响(正如您所观察到的那样),因为它们正在查看对内存中实例的相同引用(而不是每个都有自己的副本)。
C#中参数的默认约定是按值传递。 无论参数是class
还是struct
都是如此。 在class
情况下,仅通过值传递引用,而在struct
情况下,传递整个对象的浅表副本。
当您输入TestAssignmentMethod
,有两个对单个对象的引用: testObj
,它位于AssignmentTest
和testClass
,它位于TestAssignmentMethod
。 如果你要通过testClass
或testObj
改变实际对象,那么两个引用都可以看到它们,因为它们都指向同一个对象。 在第一行虽然你执行
testClass = new TestRefClass() { TestInt = 1 }
这将创建一个新对象并将testClass
指向它。 这不会改变testObj
引用以任何方式指向的位置,因为testClass
是一个独立的副本。 现在有2个对象和2个引用,每个引用指向不同的对象实例。
如果您想要通过引用语义进行传递,则需要使用ref
参数。
AssignmentTest
使用TestAssignmentMethod
, 它只更改 value传递的对象引用 。
因此,对象本身通过引用传递,但对象的引用是按值传递的。 所以当你这样做时:
testClass = new TestRefClass() { TestInt = 1 };
您正在更改传递给方法的本地复制引用,而不是测试中的引用。
所以在这里:
[TestMethod]
public void AssignmentTest()
{
var testObj = new TestRefClass() { TestInt = 0 };
TestAssignmentMethod(testObj);
Assert.IsTrue(testObj.TestInt == 1);
}
testObj
是一个引用变量。 当你将它传递给TestAssignmentMethod(testObj);
,引用按值传递。 因此,当您在方法中更改它时, 原始引用仍指向同一对象 。
我的2美分
当类传递给方法时,正在发送它的内存空间地址的副本(正在向您发送一个方向)。 所以对该地址的任何操作都会对房子产生影响,但不会改变它自己的地址。 (这是默认的)
通过引用传递类(对象)具有传递它的实际地址而不是地址副本的效果。 这意味着如果将新对象分配给通过引用传递的参数,它将更改实际地址(类似于重定位)。 :d
这就是我的看法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.