2012-04-03 5 views
22

私はいつも、クラス型のメソッドパラメータは、デフォルトで参照パラメータとして渡されると考えました。必ずしもそうであるとは限りません。 C#で(MSTestを使用して)これらの単体テストを検討してください。C#でrefパラメータとしてクラスを渡しても、期待どおりに機能しない場合があります。誰でも説明できますか?

[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()が失敗し、他の2つのテスト方法が失敗します。 testClassパラメータに新しいインスタンスを割り当てると、パラメータ参照が破損することがあると思われますが、何らかの形で明示的にrefキーワードを追加するとこれが修正されます。

ここで何が起こっているのか、詳細な説明は誰にもありますか?私は主にC#の知識を広げようとしています。私は解決しようとしている特定のシナリオを持っていません...

答えて

29

ほとんど常にということを忘れてしまったのは、クラスが参照渡しされず、クラスへの参照が値渡されるということです。

これは重要です。クラス全体(ステレオタイプの意味での値渡し)をコピーする代わりに、そのクラスの参照(私は "ポインタ"と言うのを避けようとしています)をコピーします。これは4バイトまたは8バイトです。クラス全体をコピーするよりもはるかに美味しく、実際にはクラスが「参照によって」渡されることを意味します。

この時点で、方法には、クラスへの参照のコピーがあります。代入この参照はメソッド内でスコープされます(メソッドは参照の独自のコピーのみを再割り当てします)。

クラスメンバと話すときのように参照を間違えると、期待通りに動作します。新しいインスタンスを見るために変更しない限り、基礎となるクラスが表示されます(これは、失敗したテスト)。

キーワードを使用すると、参照(ポインタの種類のポインタ)によって参照自体が効果的に渡されます。

はいつものように、ジョンスキートは非常によく書かれた概要を提供しています

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

ご注意「を参照パラメータ」の部分に:

参照パラメータは渡しません関数メンバーの呼び出しで使用される変数の値 - 変数自体を使用します。

方法はref参照に何かを割り当てた場合は、呼び出し側のコピーも(あなたが観察していると)とは対照的に、彼らは(メモリ内のインスタンスに同じ参照を見ているので、影響を受けていますそれぞれ独自のコピーを持っています)。

4

C#のパラメータのデフォルトの規則は値渡しです。これは、パラメータがclassstructのいずれであっても当てはまります。 classのケースでは、参照は値によって渡され、structのケースでは、オブジェクト全体のシャローコピーが渡されます。 testObjAssignmentTestTestAssignmentMethodに住んでいるtestClassに住ん:

あなたがTestAssignmentMethodを入力すると、単一のオブジェクトへの2つの参照があります。 testClassまたはtestObjで実際のオブジェクトを変更する場合は、両方とも同じオブジェクトを指しているため、両方の参照に表示されます。最初の行では、

testClass = new TestRefClass() { TestInt = 1 } 

これは、新しいオブジェクトを作成し、それにtestClassポイントを実行しても。 testClassは独立したコピーであるため、これはtestObjの参照先が変更されることはありません。現在2つのオブジェクトと2つの参照があり、それぞれが異なるオブジェクトインスタンスを指して参照しています。

参照セマンティクスで渡す場合は、refパラメータを使用する必要があります。

+1

ニースの詳細な回答ですが、何らかの理由で、「パラメータが渡された参照であっても、常にパラメータが値渡されます」と言われると、いつも気になります。私はそのような賢明な区別がどのように照らされるかを見ない。 –

+0

@RobertHarvey不愉快ですが、残念ながらデフォルトの場合、常に真です(もちろん、「ref」または「out」が出席していない限り)。私が今までに読んだことのある最良の説明は、Jon SkeetのC#の本です。 –

+0

@RobertHarvey用語は残念ながら混乱しています:( – JaredPar

2

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

私の2セントのクラスは、それのメ​​モリ空間のアドレスのコピーが送られているメソッドに渡され

(あなたの家への方向が送信されています)。だから、そのアドレスのすべての操作は家に影響を与えますが、アドレス自体は変更されません。 (これはデフォルトです)

クラス(オブジェクト)を参照渡しすると、アドレスのコピーではなく実際のアドレスが渡されます。つまり、参照渡しの引数に新しいオブジェクトを割り当てると、実際のアドレスが変更されます(再配置に似ています)。 :D

これは私がそれを見る方法です。

関連する問題