简体   繁体   English

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

[英]Passing a class as a ref parameter in C# does not always work as expected. Can anyone explain?

I always thought that a method parameter with a class type is passed as a reference parameter by default. 我一直认为默认情况下,带有类类型的方法参数作为引用参数传递。 Apparently that is not always the case. 显然情况并非如此。 Consider these unit tests in C# (using MSTest). 在C#中考虑这些单元测试(使用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);
    }
}

The results are that AssignmentTest() fails and the other two test methods pass. 结果是AssignmentTest()失败,另外两个测试方法通过。 I assume the issue is that assigning a new instance to the testClass parameter breaks the parameter reference, but somehow explicitly adding the ref keyword fixes this. 我假设问题是将新实例分配给testClass参数会破坏参数引用,但不知何故显式添加ref关键字会修复此问题。

Can anyone give a good, detailed explanation of whats going on here? 谁能对这里发生的事情做出详细的解释? I'm mainly just trying to expand my knowledge of C#; 我主要是想扩展我对C#的了解; I don't have any specific scenario I'm trying to solve... 我没有任何具体的情况我试图解决......

The thing that is nearly always forgotten is that a class isn't passed by reference, the reference to the class is passed by value. 几乎总是被遗忘的事情是,类不是通过引用传递的,对类的引用是通过值传递的。

This is important. 这个很重要。 Instead of copying the entire class (pass by value in the stereotypical sense), the reference to that class (I'm trying to avoid saying "pointer") is copied. 不是复制整个类(在定型意义上传递值),而是复制对该类的引用 (我试图避免说“指针”)。 This is 4 or 8 bytes; 这是4或8个字节; much more palatable than copying the whole class and in effect means the class is passed "by reference". 比复制整个班级更加可口,实际上意味着班级是“通过引用”传递的。

At this point, the method has it's own copy of the reference to the class . 此时,该方法拥有自己对类的引用的副本 Assignment to that reference is scoped within the method (the method re-assigned only its own copy of the reference). 分配该参考方法(该方法重新分配仅其自己的基准副本)内作用域。

Dereferencing that reference (as in, talking to class members) would work as you'd expect: you'd see the underlying class unless you change it to look at a new instance (which is what you do in your failing test). 取消引用该引用(如同,与类成员交谈)将按预期工作:除非您更改它以查看新实例(这是您在失败测试中执行的操作),否则您将看到基础类。

Using the ref keyword is effectively passing the reference itself by reference (pointer to a pointer sort of thing). 使用 ref关键字有效地通过引用传递引用本身(指针指向事物的指针)。

As always, Jon Skeet has provided a very well written overview: 和往常一样,Jon Skeet提供了一个非常好的概述:

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

Pay attention to the "Reference parameters" part: 注意“参考参数”部分:

Reference parameters don't pass the values of the variables used in the function member invocation - they use the variables themselves. 引用参数不传递函数成员调用中使用的变量的值 - 它们使用变量本身。

If the method assigns something to a ref reference, then the caller's copy is also affected (as you have observed) because they are looking at the same reference to an instance in memory (as opposed to each having their own copy). 如果该方法将某些内容分配给ref引用,则调用者的副本也会受到影响(正如您所观察到的那样),因为它们正在查看对内存中实例的相同引用(而不是每个都有自己的副本)。

The default convention for parameters in C# is pass by value. C#中参数的默认约定是按值传递。 This is true whether the parameter is a class or struct . 无论参数是class还是struct都是如此。 In the class case just the reference is passed by value while in the struct case a shallow copy of the entire object is passed. class情况下,仅通过值传递引用,而在struct情况下,传递整个对象的浅表副本。

When you enter the TestAssignmentMethod there are 2 references to a single object: testObj which lives in AssignmentTest and testClass which lives in TestAssignmentMethod . 当您输入TestAssignmentMethod ,有两个对单个对象的引用: testObj ,它位于AssignmentTesttestClass ,它位于TestAssignmentMethod If you were to mutate the actual object via testClass or testObj it would be visible to both references since they both point to the same object. 如果你要通过testClasstestObj改变实际对象,那么两个引用都可以看到它们,因为它们都指向同一个对象。 In the first line though you execute 在第一行虽然你执行

testClass = new TestRefClass() { TestInt = 1 }

This creates a new object and points testClass to it. 这将创建一个新对象并将testClass指向它。 This doesn't alter where the testObj reference points in any way because testClass is an independent copy. 这不会改变testObj引用以任何方式指向的位置,因为testClass是一个独立的副本。 There are now 2 objects and 2 references which each reference pointing to a different object instance. 现在有2个对象和2个引用,每个引用指向不同的对象实例。

If you want pass by reference semantics you need to use a ref parameter. 如果您想要通过引用语义进行传递,则需要使用ref参数。

The AssignmentTest uses TestAssignmentMethod which only changes the object reference passed by value . AssignmentTest使用TestAssignmentMethod它只更改 value传递对象引用

So the object itself is passed by reference but the reference to the object is passed by value. 因此,对象本身通过引用传递,但对象的引用是按值传递的。 so when you do: 所以当你这样做时:

testClass = new TestRefClass() { TestInt = 1 };

You are changing the local copied reference passed to the method not the reference you have in the test. 您正在更改传递给方法的本地复制引用,而不是测试中的引用。

So here: 所以在这里:

[TestMethod]
public void AssignmentTest()
{
    var testObj = new TestRefClass() { TestInt = 0 };
    TestAssignmentMethod(testObj);
    Assert.IsTrue(testObj.TestInt == 1);
}

testObj is a reference variable. testObj是一个引用变量。 When you pass it to TestAssignmentMethod(testObj); 当你将它传递给TestAssignmentMethod(testObj); , the refernce is passed by value. ,引用按值传递。 so when you change it in the method, original reference still points to the same object . 因此,当您在方法中更改它时, 原始引用仍指向同一对象

My 2 cents 我的2美分

When class is passed to a method a copy of it's memory space address is being sent (a direction to you house is being sent). 当类传递给方法时,正在发送它的内存空间地址的副本(正在向您发送一个方向)。 So any operation on that address with effect the house but will not change the address it self. 所以对该地址的任何操作都会对房子产生影响,但不会改变它自己的地址。 (this is default) (这是默认的)

Passing class(object) by reference has an effect of passing it's actual address instead of copy of an address. 通过引用传递类(对象)具有传递它的实际地址而不是地址副本的效果。 That means if you assign a new object to argument passed by reference it will change the actual address (similar to relocation). 这意味着如果将新对象分配给通过引用传递的参数,它将更改实际地址(类似于重定位)。 :D :d

This is how I see it. 这就是我的看法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM