繁体   English   中英

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

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

我一直认为默认情况下,带有类类型的方法参数作为引用参数传递。 显然情况并非如此。 在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);
    }
}

结果是AssignmentTest()失败,另外两个测试方法通过。 我假设问题是将新实例分配给testClass参数会破坏参数引用,但不知何故显式添加ref关键字会修复此问题。

谁能对这里发生的事情做出详细的解释? 我主要是想扩展我对C#的了解; 我没有任何具体的情况我试图解决......

几乎总是被遗忘的事情是,类不是通过引用传递的,对类的引用是通过值传递的。

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

此时,该方法拥有自己对类的引用的副本 分配该参考方法(该方法重新分配仅其自己的基准副本)内作用域。

取消引用该引用(如同,与类成员交谈)将按预期工作:除非您更改它以查看新实例(这是您在失败测试中执行的操作),否则您将看到基础类。

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

和往常一样,Jon Skeet提供了一个非常好的概述:

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

注意“参考参数”部分:

引用参数不传递函数成员调用中使用的变量的值 - 它们使用变量本身。

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

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

当您输入TestAssignmentMethod ,有两个对单个对象的引用: testObj ,它位于AssignmentTesttestClass ,它位于TestAssignmentMethod 如果你要通过testClasstestObj改变实际对象,那么两个引用都可以看到它们,因为它们都指向同一个对象。 在第一行虽然你执行

testClass = new TestRefClass() { TestInt = 1 }

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

如果您想要通过引用语义进行传递,则需要使用ref参数。

AssignmentTest使用TestAssignmentMethod它只更改 value传递对象引用

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

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美分

当类传递给方法时,正在发送它的内存空间地址的副本(正在向您发送一个方向)。 所以对该地址的任何操作都会对房子产生影响,但不会改变它自己的地址。 (这是默认的)

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

这就是我的看法。

暂无
暂无

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

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