引用类型的 C# 7 引用返回

C# 7 ref return for reference types

提问人:Arghya C 提问时间:1/18/2018 最后编辑:Cody Gray - on strikeArghya C 更新时间:2/14/2019 访问量:2127

问:

我正在浏览一些使用 C# 7 新功能并使用 ref locals & returns 功能的代码。

对于 ref 局部变量获取引用(对实际存储)的引用,以及更新原始项的值,这似乎很简单。value-types

一些解释将有助于理解内存引用在 的 ref 局部变量的情况下是如何工作的。我指向下面代码的最后一行:reference-types

// A simple class
public class CoolClass
{
    public string Name { get; set; }
    public int Id { get; set; }

    public CoolClass(string name, int id) => (Name, Id) = (name, id);
}

//Dummy method that returns first element with Id > 100
public CoolClass GetSpecialItem_Old(CoolClass[] arr)
{
    for (int i = 0; i < arr.Length; i++)
        if (arr[i].Id > 100)
        {
            return arr[i];
        }
    throw new Exception("Not found");
}

//Same as the other one, but returns by ref C# 7
public ref CoolClass GetSpecialItem_New(CoolClass[] arr)
{
    for (int i = 0; i < arr.Length; i++)
        if (arr[i].Id > 100)
        {
            return ref arr[i];
        }
    throw new Exception("Not found");
}

public void TestMethod()
{
    var arr = new CoolClass[]
    {
        new CoolClass("A", 10),
        new CoolClass("B", 101),
        new CoolClass("C", 11)
    };

    var byVal = GetSpecialItem_Old(arr); //gets back arr[1]
    byVal.Name = "byVal"; //arr[1] = { "byVal", 101 }
    byVal = new CoolClass("newByVal", 25); //arr[1] = { "byVal", 101 }

    ref var byRef = ref GetSpecialItem_New(arr); //gets back arr[1]
    byRef.Name = "byRef"; //arr[1] = { "byRef", 101 }
    //Here it has a different behaviour 
    byRef = new CoolClass("newByRef", 50); //arr[1] = { "newByRef", 50 }
}
C# .NET CLR 按引用传递 C#-7.0

评论

2赞 Caramiriel 1/18/2018
ref var byRef基本上是相同的引用,所以如果你“实际上”在引擎盖下做。arr[1]byRef = new ...arr[1] = new ...

答:

22赞 Eric Lippert 1/18/2018 #1

在我看来,C# 的原始设计者将该功能命名为“ref”是一个坏主意。它导致人们混淆引用类型和“ref”参数/返回。考虑“ref”的更好方法是“别名”。也就是说,ref 为现有变量提供了另一个名称

在程序中,无论 arr[1] 是值类型还是引用类型,都是另一个名称。如果是一个字符串变量(记住,数组元素是变量,你可以改变它们),那么也是一个字符串变量,它是同一个字符串变量,只是名称不同。byRefarr[1]arr[1]byref

请注意,这也是一个变量;如果更改 的值,则不会随车而来。它仍然是同一数组的同一插槽的别名,而不考虑 的值。arrarrbyRefarr

所以当你说

ref var byRef = ref GetSpecialItem_New(arr); //gets back arr[1]

然后

byRef.Name = "byRef"; 

arr[1].Name = "byRef";

当你说

byRef = new CoolClass("newByRef", 50);

这和

arr[1] = new CoolClass("newByRef", 50);

需要注意的是,如果您在分配后进行了更改,则仍然会有一个原始的别名。arrbyRefarr[1]

再次: 只是拼写 arr[1] 的另一种方式,因此它总是使用它在赋值时所拥有的值。对于值类型或引用类型,它没有区别。byRefarrbyRef

相反,不是 的别名。它是第二个变量,具有 的内容副本。当您指派给您时,您不是指派给 。您要分配给 ,这是一个不同的变量。byValarr[1]arr[1]byValarr[1]byVal

的内容是一个引用,并且该引用被完全复制到一个单独的存储位置。arr[1]byVal

评论

3赞 Arghya C 1/18/2018
谢谢,埃里克的解释。这些要点,使思考起来更容易。better way to think of "ref" is "alias"such that it always uses the value of arr that it had when byRef was assigned
0赞 Mihail Vladov 2/5/2019 #2

另一个有趣的问题是,在测试时,如何强制 Ref 返回和 ref 局部变量按照您的意愿行事? 您可以通过使用 JustMock 模拟该方法来做到这一点。GetSpecialItem_New

以下是问题中的方法:

public class Foo
{
    //Same as the other one, but returns by ref C# 7
    public ref CoolClass GetSpecialItem_New(CoolClass[] arr)
    {
        for (int i = 0; i < arr.Length; i++)
            if (arr[i].Id > 100)
            {
                return ref arr[i];
            }
        throw new Exception("Not found");
    }
}

下面介绍如何模拟该方法,以返回隔离测试的所需结果:

[TestMethod]
public void TestCoolClass()
{
    var expected = new CoolClass("42", 42);

    var arr = new CoolClass[]
    {
        new CoolClass("A", 10),
        new CoolClass("B", 101),
        new CoolClass("C", 11)
    };

    // Arrange
    var sut = Mock.Create<Foo>();
    Mock.Arrange(sut, s => s.GetSpecialItem_New(arr)).Returns(LocalRef.WithValue(expected).Handle).OccursOnce();

    // Act
    ref CoolClass actual = ref sut.GetSpecialItem_New(arr);

    // Assert
    Assert.AreEqual(expected, actual);
}

这是一篇帮助文章,详细解释了测试方法。

免責聲明。我是负责 JustMock 的开发人员之一。