在C#中将类作为ref参数传递并不总是按预期工作。 谁有人解释一下?

我一直认为默认情况下,带有类类型的方法参数作为引用参数传递。 显然情况并非如此。 在C#中考虑这些unit testing(使用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个字节; 比复制整个class级更加可口,实际上意味着class级是“通过引用”传递的。

此时,该方法拥有自己对类的引用的副本 。 对该引用的赋值在方法范围内(该方法仅重新分配其自己的引用副本)。

取消引用该引用(如同,与类成员交谈)将按预期工作:除非您更改它以查看新实例(这是您在失败测试中执行的操作),否则您将看到基础类。

使用ref关键字有效地通过引用传递引用本身(指针指向事物的指针)。

与往常一样,Jon Skeet提供了一个非常好的书面概述:

http://www.yoda.arachsys.com/csharp/parameters.html

注意“参考参数”部分:

引用参数不传递函数成员调用中使用的变量的值 – 它们使用变量本身。

如果方法将某些内容分配给ref引用,则调用者的副本也会受到影响(正如您所观察到的那样),因为它们正在查看对内存中实例的相同引用(而不是每个实例都有自己的副本)。

C#中参数的默认约定是按值传递。 无论参数是class还是struct都是如此。 在class情况下,只是通过值传递引用,而在struct情况下,传递整个对象的浅表副本。

当您输入TestAssignmentMethod ,有两个对单个对象的引用: testObj ,它位于AssignmentTesttestClass ,它位于TestAssignmentMethod 。 如果你通过testClasstestObj改变实际对象,那么两个引用都可以看到它们,因为它们都指向同一个对象。 在第一行虽然你执行

 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

这就是我的看法。