2012-04-03 41 views
22

我一直认为带有类类型的方法参数默认作为参考参数传递。显然情况并非总是如此。考虑一下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#的知识;我没有任何具体的场景,我试图解决...

回答

29

几乎总是忘记的是一个类不是通过引用传递的,对类的引用是通过值传递的。

这很重要。而不是复制整个类(按照传统意义上的值传递),那个参考到那个类(我试图避免说“指针”)被复制。这是4或8个字节;比复制整个班级更可口,实际上意味着班级通过“参考”传递。

在这一点上,方法有它自己的副本引用类。作业该引用在该方法范围内(该方法仅重新分配了其自身的参考副本)。

取消引用该引用(如在与类成员交谈中)将按照您的预期工作:除非将其更改为查看新实例(您在失败时执行的操作测试)。

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

与往常一样,乔恩斯基特提供了一个很好的书面概述:

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

,请注意“参照参数”部分:

参考参数没有通过 函数成员调用中使用的变量值 - 它们使用变量本身。

如果该方法分配一些到ref参考,然后调用者的副本也会受到影响(如你观察到的),因为他们正在寻找在相同参考实例在内存中(而不是每个都有自己的副本)。

4

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

当你进入TestAssignmentMethod有2只引用一个对象:testObj它生活在AssignmentTesttestClass其住在TestAssignmentMethod。如果要通过testClasstestObj对实际对象进行变异,则两个引用都可以看到它们,因为它们都指向相同的对象。在第一行,虽然你执行

testClass = new TestRefClass() { TestInt = 1 } 

这将创建一个新的对象,并指出testClass它。这不会改变testObj参考点以何种方式指向的位置,因为testClass是独立副本。现在有2个对象和2个引用,每个引用指向不同的对象实例。

如果你想通过引用语义传递,你需要使用ref参数。

+1

好的,详细的答案,但由于某种原因,当有人说“参数总是按值传递,即使它是你传递的参考值时,我总是觉得它很刺激。”我不明白这种迂腐的区别是如何阐明的。 – 2012-04-03 15:31:02

+0

@RobertHarvey刺激,但不幸的是,在默认情况下,总是如此(当然,除非'ref'或'out'出席)。我认为我读过的最好的解释是Jon Skeet在他的C#书中。 – 2012-04-03 15:32:28

+0

@RobertHarvey术语不幸混淆:( – JaredPar 2012-04-03 15:32:33

2

AssignmentTestTestAssignmentMethod使用其中仅改变由值通过对象引用。

因此,对象本身是通过引用传递的,但对象的引用是按值传递的。所以,当你这样做:

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

我的2美分

当类被传递到正在发送它的内存空间地址的副本的方法(你家的方向发送)。因此,对该地址进行的任何操作都会影响房屋,但不会改变自己的地址。 (这是默认值)

通过引用传递类(对象)具有传递其实际地址而不是地址副本的效果。这意味着如果您将新对象分配给通过引用传递的参数,它将更改实际地址(类似于重定位)。 :D

这就是我的看法。