为什么C#不支持返回引用?


141

我已经读过.NET支持返回引用,但是C#不支持。有特殊原因吗?为什么我不能做这样的事情:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
嗯...请引用?
RPM1984

5
C#具有值和引用类型,都可以返回您的意思。
重新运行

我说的是return ref x
Tom Sarduy

2
4年后,您可以使用C# 7:)
Arghya C

Answers:


188

这个问题是我在2011年6月23日发布的博客的主题。感谢您提出的好问题!

C#团队正在考虑将其用于C#7。有关详细信息,请参见https://github.com/dotnet/roslyn/issues/5233

更新:该功能已纳入C#7!


你是对的; .NET确实支持托管引用返回到变量的方法。.NET还支持包含对其他变量的托管引用的局部变量。(但是请注意,.NET不支持包含对其他变量的托管引用的字段数组,因为这会使垃圾回收的情况变得过于复杂。此外,“对变量的托管引用”类型不能转换为对象,因此不能用作泛型类型或方法的类型参数。)

评论员“ RPM1984”出于某种原因要求对此事实进行引用。RPM1984我建议您阅读CLI规范“分区I”的第8.2.1.1节“托管指针和相关类型”,以获取有关.NET功能的信息。

完全有可能创建同时支持这两个功能的C#版本。然后,您可以做类似的事情

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

然后用

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

我凭经验知道,可以构建支持这些功能的C#版本,因为我已经这样做了。高级程序员,尤其是那些移植非托管​​C ++代码的人,经常要求我们提供更多类似于C ++的能力,以使用引用来做事情,而不必费心去真正使用指针和在各处固定内存。通过使用托管引用,您可以获得这些好处,而无需付出破坏垃圾收集性能的代价。

我们已经考虑了此功能,并实际实施了足够多的功能以展示给其他内部团队以获取他们的反馈。但是,根据我们的研究,目前我们认为该功能没有足够广泛的吸引力或令人信服的使用案例,无法使其真正成为受支持的语言功能。我们还有其他更高的优先级以及有限的时间和精力,因此我们不会在不久的将来实现此功能。

同样,正确执行此操作将需要对CLR进行一些更改。目前,CLR将引用返回方法视为合法无法验证,因为我们没有检测到这种情况的检测器:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3返回M2局部变量的内容,但是该变量的生命周期已经结束!可以编写一个检测器,该检测器确定使用明显违反堆栈安全性的引用返回。我们要做的就是编写一个这样的检测器,如果检测器不能证明堆栈的安全性,那么我们将不允许在程序的那部分使用ref返回。这样做并不是大量的开发工作,但是要确保我们确实掌握了所有情况,这对测试团队来说是沉重的负担。这只是将功能成本增加到目前收益不超过成本的另一件事。

如果您能为我描述为什么要使用此功能,我将不胜感激。我们从真实客户那里获得的关于他们为什么想要它的信息越多,有一天它越有可能将其纳入产品。这是一个可爱的小功能,如果有足够的兴趣,我希望能够以某种方式将其提供给客户。

(另请参阅相关问题,是否有可能在C#中返回对变量的引用?并且可以在C ++之类的C#函数中使用引用吗?


4
@EricLippert:我没有令人信服的例子,这只是我想知道的。出色而引人注目的回应
Tom Sarduy

1
@Eric:以您为例,从回来后,还不更适合y 生存M2吗?我希望此功能能像lambdas一样捕获本地人。还是您提出的行为是因为CLR是如何处理这种情况的?
菲德

3
@Eric:恕我直言,使属性返回对值类型的引用的能力是.net语言中的主要遗漏。如果Arr是一个值类型的数组(例如Point),则可以说Arr(3).X = 9,并且知道一个人并没有更改Arr(9).X甚至SomeOtherArray(2)的值。 X; 如果Arr是某个引用类型的数组,则将不存在此类保证。数组的索引运算符返回引用这一事实非常有用。我非常遗憾,没有其他类型的集合可以提供这种功能。
supercat

4
埃里克(Eric),提供关于场景的反馈的最佳方法是什么(如您在上一段中建议的那样),以最大程度地发现场景?MS Connect?UserVoice(具有任意10个帖子/投票限制)?还有吗
罗曼·斯塔科夫

1
@ThunderGr:这是C#的“不安全”哲学-如果编写的代码可能是内存不安全的,则C#坚持认为您将其标记为“不安全”,以便对内存安全负责。如果有问题的变量为非托管类型,则C#已经具有该功能的不安全版本。问题是C#团队是否应该制作一个不安全的版本来处理托管类型。如果这样做可以使开发人员轻松编写可怕的错误,那将不会发生。C#不是C ++,这是一种易于编写可怕错误的语言。C#设计上是安全的。
埃里克·利珀特

21

您正在谈论的是返回对值类型的引用的方法。我知道的C#中唯一的内置示例是值类型的数组访问器:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

现在创建该结构的数组:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

在这种情况下points[0],数组索引器将返回对struct的引用。不可能编写具有相同的“返回引用”行为的自己的索引器(例如,用于自定义集合)。

我没有设计C#语言,所以我不知道不支持C#的所有原因,但我认为简短的答案可能是:没有它,我们可以相处得很好。


8
汤姆(汤姆)正在询问返回变量引用的方法。变量不必为值类型,尽管当然,这通常是人们在需要引用返回方法时想要的。否则,进行大量分析;您是正确的,在C#语言中,唯一的一个复杂表达式产生对用户然后可以操作的变量的引用的地方就是数组索引器。(当然,在接收者和字段之间的成员访问运算符“。”,但这显然是对变量的访问。)
Eric Lippert

1

您可以随时执行以下操作:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.