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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9996359/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 12:03:57  来源:igfitidea点击:

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

c#reference

提问by jrizzo

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. I assume the issue is that assigning a new instance to the testClassparameter breaks the parameter reference, but somehow explicitly adding the refkeyword fixes this.

结果是AssignmentTest()失败,其他两种测试方法通过。我认为问题是将新实例分配给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#; I don't have any specific scenario I'm trying to solve...

任何人都可以对这里发生的事情给出一个很好的,详细的解释吗?我主要只是想扩展我的 C# 知识;我没有任何要解决的特定场景...

采纳答案by Adam Houldsworth

The thing that is nearly alwaysforgotten 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 referenceto that class (I'm trying to avoid saying "pointer") is copied. This is 4 or 8 bytes; much more palatable than copying the whole class and in effect means the class is passed "by reference".

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

At this point, the method has it's own copy of the reference to the class. Assignment tothat 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 refkeyword 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 refreference, then the caller's copy is also affected (as you have observed) because they are looking at the samereference to an instance in memory (as opposed to each having their own copy).

如果该方法为ref引用分配了一些东西,那么调用者的副本也会受到影响(正如您所观察到的),因为它们正在查看对内存中实例的相同引用(而不是每个都有自己的副本)。

回答by JaredPar

The default convention for parameters in C# is pass by value. This is true whether the parameter is a classor struct. In the classcase just the reference is passed by value while in the structcase a shallow copy of the entire object is passed.

C# 中参数的默认约定是按值传递。无论参数是 aclass还是 ,都是如此struct。在这种class情况下,仅通过值传递引用,而在这种struct情况下传递整个对象的浅拷贝。

When you enter the TestAssignmentMethodthere are 2 references to a single object: testObjwhich lives in AssignmentTestand testClasswhich lives in TestAssignmentMethod. If you were to mutate the actual object via testClassor testObjit would be visible to both references since they both point to the same object. In the first line though you execute

当您输入 时,TestAssignmentMethod有 2 个对单个对象的引用:testObj哪个位于AssignmentTesttestClass哪个位于TestAssignmentMethod. 如果您要通过改变实际对象,testClass或者testObj它对两个引用都是可见的,因为它们都指向同一个对象。在第一行虽然你执行

testClass = new TestRefClass() { TestInt = 1 }

This creates a new object and points testClassto it. This doesn't alter where the testObjreference points in any way because testClassis an independent copy. There are now 2 objects and 2 references which each reference pointing to a different object instance.

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

If you want pass by reference semantics you need to use a refparameter.

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

回答by Aliostad

The AssignmentTestuses TestAssignmentMethodwhich only changesthe object reference passed by value.

AssignmentTest用途TestAssignmentMethod,其仅改变所述对象引用传递由值

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);
}

testObjis a reference variable. When you pass it to TestAssignmentMethod(testObj);, the refernce is passed by value. so when you change it in the method, original reference still points to the same object.

testObj是参考变量。当您将其传递给 时TestAssignmentMethod(testObj);,引用是按值传递的。所以当你在方法中改变它时,原始引用仍然指向同一个对象

回答by Max

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.

这就是我的看法。