C#7 ref返回参考类型

时间:2018-01-17 21:33:23

标签: c# .net clr pass-by-reference c#-7.0

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

value-types似乎非常简单,其中ref本地变量获取引用(对于实际存储),并且更新它会更新原始项的值。

reference-types的ref本地化的情况下,一点解释将有助于理解内存引用如何工作。我指着下面代码的最后一行:

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

2 个答案:

答案 0 :(得分:16)

C#的原始设计师将该功能命名为&#34; ref&#34;在我看来是一个坏主意。它导致人们混淆引用类型和&#34; ref&#34;参数/回报。更好的方式来考虑&#34; ref&#34;是&#34;别名&#34;。也就是说,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);

请注意,如果您在分配arr后更改了byRef,则您仍然拥有原始 arr[1]的别名。

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

相反,byVal不是arr[1]的别名。它是第二个变量,其内容为arr[1]副本。当您分配到byVal时,您未分配给arr[1]。您已分配给byVal,这是不同的变量。

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

答案 1 :(得分:-1)

另一个有趣的问题是如何强制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的开发人员之一。