2012-04-03 39 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

這就是我的看法。