引用类型的 C# 7 ref 返回

Posted

技术标签:

【中文标题】引用类型的 C# 7 ref 返回【英文标题】:C# 7 ref return for reference types 【发布时间】:2018-06-26 21:06:07 【问题描述】:

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

对于value-types 来说,ref 局部变量获取一个引用(对实际存储)并更新它会更新原始项目的值,这似乎很简单。

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

// 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 

【问题讨论】:

ref var byRefarr[1] 的引用基本相同,所以如果您 byRef = new ... 您“实际上”在后台执行 arr[1] = new ... 【参考方案1】:

在我看来,C# 的原始设计者将该特性命名为“ref”这一事实是个坏主意。它会导致混淆引用类型和“ref”参数/返回。考虑“ref”的更好方法是“alias”。也就是说,ref 给你一个现有变量的另一个名字

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

注意arr也是一个变量;如果您更改了arr 的值,那么byRef 不会随之而来。无论arr 的值如何,它仍然是同一数组的同一槽的别名。

所以当你说

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

需要注意的是,如果您在分配byRef 后更改了arr,您仍然会有一个别名到原始 arr[1]

再次重申:byRef 只是arr[1] 的另一种拼写方式,因此它始终使用分配byRef 时的arr 值。对于值类型或引用类型,没有不同。

相比之下,byVal 不是arr[1] 的别名。它是一个 second 变量,具有arr[1] 内容的副本。当您分配给byVal 时,您并没有分配给arr[1]。您正在分配给byVal,这是一个不同的变量。

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

【讨论】:

感谢埃里克的解释。 better way to think of "ref" is "alias"such that it always uses the value of arr that it had when byRef was assigned 这两个点让我们更容易思考。【参考方案2】:

另一个有趣的问题是如何强制Ref returns and ref locals 在测试时表现得如你所愿? 您可以通过使用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);

这是help article 详细解释测试方法。

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

【讨论】:

以上是关于引用类型的 C# 7 ref 返回的主要内容,如果未能解决你的问题,请参考以下文章

引用类型参数,ref按引用传值

“ref”关键字和引用类型[重复]

C#7.0 ref引用传递

C#中ref的作用是啥?

c# 值传递 引用传递

通过引用传递值类型而不在c#中初始化[重复]