在C#中将类作为参数传递并不总是按预期方式工作。谁能解释一下?
我一直认为带有类类型的方法参数默认作为参考参数传递。显然情况并非总是如此。考虑一下C#中的这些单元测试(使用MSTest)。在C#中将类作为参数传递并不总是按预期方式工作。谁能解释一下?
[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
关键字通过引用有效地传递引用本身(指向指针类的东西)。
与往常一样,乔恩斯基特提供了一个很好的书面概述:
http://www.yoda.arachsys.com/csharp/parameters.html
,请注意“参照参数”部分:
参考参数没有通过 函数成员调用中使用的变量值 - 它们使用变量本身。
如果该方法分配一些到ref
参考,然后调用者的副本也会受到影响(如你观察到的),因为他们正在寻找在相同参考实例在内存中(而不是每个都有自己的副本)。
在C#中参数的默认约定是通过值传递。无论参数是class
还是struct
,都是如此。在class
的情况下,只是参考值按值传递,而在struct
的情况下则传递整个对象的浅表副本。
当你进入TestAssignmentMethod
有2只引用一个对象:testObj
它生活在AssignmentTest
和testClass
其住在TestAssignmentMethod
。如果要通过testClass
或testObj
对实际对象进行变异,则两个引用都可以看到它们,因为它们都指向相同的对象。在第一行,虽然你执行
testClass = new TestRefClass() { TestInt = 1 }
这将创建一个新的对象,并指出testClass
它。这不会改变testObj
参考点以何种方式指向的位置,因为testClass
是独立副本。现在有2个对象和2个引用,每个引用指向不同的对象实例。
如果你想通过引用语义传递,你需要使用ref
参数。
的AssignmentTest
TestAssignmentMethod
使用其中仅改变由值通过对象引用。
因此,对象本身是通过引用传递的,但对象的引用是按值传递的。所以,当你这样做:
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
这就是我的看法。
好的,详细的答案,但由于某种原因,当有人说“参数总是按值传递,即使它是你传递的参考值时,我总是觉得它很刺激。”我不明白这种迂腐的区别是如何阐明的。 – 2012-04-03 15:31:02
@RobertHarvey刺激,但不幸的是,在默认情况下,总是如此(当然,除非'ref'或'out'出席)。我认为我读过的最好的解释是Jon Skeet在他的C#书中。 – 2012-04-03 15:32:28
@RobertHarvey术语不幸混淆:( – JaredPar 2012-04-03 15:32:33