引用类型的 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 byRef
与 arr[1]
的引用基本相同,所以如果您 byRef = new ...
您“实际上”在后台执行 arr[1] = new ...
。
【参考方案1】:
在我看来,C# 的原始设计者将该特性命名为“ref”这一事实是个坏主意。它会导致混淆引用类型和“ref”参数/返回。考虑“ref”的更好方法是“alias”。也就是说,ref 给你一个现有变量的另一个名字。
在您的程序中,byRef
是 arr[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 返回的主要内容,如果未能解决你的问题,请参考以下文章