Ref返回C#7.0中的限制
我试图理解以下摘自一篇关于C#7.0中与ref返回相关的新特性的官方博客文章Ref返回C#7.0中的限制,c#,c#-7.0,C#,C# 7.0,我试图理解以下摘自一篇关于C#7.0中与ref返回相关的新特性的官方博客文章 只能返回“可安全返回”的引用:已安全返回的引用 传递给您,以及指向对象中字段的对象。 Ref局部变量被初始化到某个存储位置,不能变异为指向另一个存储位置。 不幸的是,博客文章没有给出任何代码示例。如果有人能通过实际例子和解释,对以粗体突出显示的限制进行更多说明,我将不胜感激 提前感谢。您可以在上找到有关此功能的精彩讨论 1。您只能返回“安全返回”的引用:那些 传递给您,以及指向对象中字段的 以下示例显示安全引用的返
提前感谢。您可以在上找到有关此功能的精彩讨论 1。您只能返回“安全返回”的引用:那些 传递给您,以及指向对象中字段的 以下示例显示安全引用的返回,因为它来自调用者:
public static ref TValue Choose<TValue>(ref TValue val)
{
return ref val;
}
将不编译。您也无法将新引用分配给已存在的引用,如
aReference = ref anOtherValue;
要通过引用传递某物,必须将其分类为变量。C#规范(§5变量)定义了七类变量:静态变量、实例变量、数组元素、值参数、引用参数、输出参数和局部变量
class ClassName {
public static int StaticField;
public int InstanceField;
}
void Method(ref int i) { }
void Test1(int valueParameter, ref int referenceParameter, out int outParameter) {
ClassName instance = new ClassName();
int[] array = new int[1];
outParameter=0;
int localVariable = 0;
Method(ref ClassName.StaticField); //Static variable
Method(ref instance.InstanceField); //Instance variable
Method(ref array[0]); //Array element
Method(ref valueParameter); //Value parameter
Method(ref referenceParameter); //Reference parameter
Method(ref outParameter); //Output parameter
Method(ref localVariable); //Local variable
}
第一点实际上是说,您可以引用分类为引用参数、输出参数、静态变量和实例变量的返回变量
ref int Test2(int valueParameter, ref int referenceParameter, out int outParameter) {
ClassName instance = new ClassName();
int[] array = new int[1];
outParameter=0;
int localVariable = 0;
return ref ClassName.StaticField; //OK, "ones that point into fields in objects"
return ref instance.InstanceField; //OK, "ones that point into fields in objects"
return ref array[0]; //OK, array elements are also "safe to return" by reference
return ref valueParameter; //Error
return ref referenceParameter; //OK, "ones that were passed to you"
return ref outParameter; //OK, "ones that were passed to you"
return ref localVariable; //Error
}
注意,例如,值类型的字段,应该考虑“安全返回”封闭变量的状态。不总是允许这样做,例如引用类型的实例字段:
struct StructName {
public int InstacneField;
}
ref int Test3() {
StructName[] array = new StructName[1];
StructName localVariable = new StructName();
return ref array[0].InstacneField; //OK, array[0] is "safe to return"
return ref localVariable.InstacneField; //Error, localVariable is not "safe to return"
}
ref return方法的结果被视为“安全返回”,如果此方法不接受任何非“安全返回”参数:
ref int ReturnFirst(ref int i, ref int ignore) => ref i;
ref int Test4() {
int[] array = new int[1];
int localVariable = 0;
return ref ReturnFirst(ref array[0], ref array[0]); //OK, array[0] is "safe to return"
return ref ReturnFirst(ref array[0], ref localVariable); //Error, localVariable is not "safe to return"
}
虽然我们知道ReturnFirst(ref array[0],ref localVariable)
将返回“safe to return”引用(ref array[0]
),但编译器无法通过单独分析Test4
方法来推断它。因此,在这种情况下,ReturnFirst
方法的结果被视为不“安全返回”
第二点是,ref局部变量声明必须包括初始值设定项:
int localVariable = 0;
ref int refLocal1; //Error, no initializer
ref int refLocal2 = ref localVariable; //OK
此外,ref局部变量不能重新分配到指向其他存储位置:
int localVariable1 = 0;
int localVariable2 = 0;
ref int refLocal = ref localVariable1;
ref refLocal = ref localVariable2; //Error
refLocal = ref localVariable2; //Error
实际上,没有有效的语法来重新分配ref局部变量。您得到了一些答案,澄清了限制,但没有解释限制背后的原因 限制背后的理由是,我们决不能允许死变量使用别名。如果在一个普通方法中有一个普通的局部,并且返回一个对它的引用,那么在使用该引用时,该局部是死的 现在,有人可能会指出,由ref返回的local可以被提升到闭包类的字段中。是的,那会解决问题。但该功能的要点是允许开发人员编写高性能、接近机器的低成本机制,并自动提升到关闭状态,然后承担收集压力等负担,以实现这一目标 事情可能会变得有点棘手。考虑:
ref int X(ref int y) { return ref y; }
ref int Z( )
{
int z = 123;
return ref X(ref z);
}
在这里,我们以一种偷偷摸摸的方式将一个参考返回到本地z!这也必须是非法的。但是现在考虑一下:
ref double X(ref int y) { return ref whatever; }
ref double Z( )
{
int z = 123;
return ref X(ref z);
}
现在我们可以知道返回的ref不是z
的ref。那么,如果传入的ref的类型与返回的ref的类型都不同,我们能说这是合法的吗
这个怎么样
struct S { public int s; }
ref int X(ref S y) { return ref y.s; }
ref int Z( )
{
S z = default(S);
return ref X(ref z);
}
现在我们再次返回了一个死变量的ref
当我们第一次设计此功能时(在2010年IIRC),有许多复杂的建议来处理这些情况,但我最喜欢的建议只是“使所有这些都非法”。您不能返回由ref返回方法返回的引用,即使它不可能是死的
我不知道C#7团队最终执行了什么规则。本页上的其他答案是完整和有用的,但我想补充一点,那就是
out
参数(函数需要完全初始化)被计算为“安全返回”,以便
ref int Test2(int valueParameter, ref int referenceParameter, out int outParameter) {
ClassName instance = new ClassName();
int[] array = new int[1];
outParameter=0;
int localVariable = 0;
return ref ClassName.StaticField; //OK, "ones that point into fields in objects"
return ref instance.InstanceField; //OK, "ones that point into fields in objects"
return ref array[0]; //OK, array elements are also "safe to return" by reference
return ref valueParameter; //Error
return ref referenceParameter; //OK, "ones that were passed to you"
return ref outParameter; //OK, "ones that were passed to you"
return ref localVariable; //Error
}
有趣的是,将这一事实与另一个新的C#7功能相结合,可以模拟局部变量的通用内联声明功能:
辅助功能:public static class _myglobals
{
/// <summary> Helper function for declaring local variables inline. </summary>
public static ref T local<T>(out T t)
{
t = default(T);
return ref t;
}
};
using System;
using /* etc... */
using System.Xml;
using Microsoft.Win32;
using static _myglobals; // <-- puts function 'local(...)' into global name scope
namespace MyNamespace
{
// ...
var s = "abc" + (local(out int i) = 2) + "xyz"; // <-- inline declaration of local 'i'
i++;
Console.WriteLine(s + i); // --> abc2xyz3
if ((local(out OpenFileDialog dlg) = new OpenFileDialog // <--- inline local 'dlg'
{
InitialDirectory = Environment.CurrentDirectory,
Title = "Pick a file",
}).ShowDialog() == true)
{
MessageBox.Show(dlg.FileName);
}
演练:局部变量d
为“内联声明”,并根据offs字段计算第一级比较的结果进行初始化。如果此结果不确定,则返回第二级排序(基于大小字段)。但是在另一种情况下,我们仍然可以返回第一级结果,因为它保存在本地d
中
请注意,上述操作也可以在没有辅助功能的情况下通过C#7完成:
包括在源文件的顶部:public static class _myglobals
{
/// <summary> Helper function for declaring local variables inline. </summary>
public static ref T local<T>(out T t)
{
t = default(T);
return ref t;
}
};
using System;
using /* etc... */
using System.Xml;
using Microsoft.Win32;
using static _myglobals; // <-- puts function 'local(...)' into global name scope
namespace MyNamespace
{
// ...
var s = "abc" + (local(out int i) = 2) + "xyz"; // <-- inline declaration of local 'i'
i++;
Console.WriteLine(s + i); // --> abc2xyz3
if ((local(out OpenFileDialog dlg) = new OpenFileDialog // <--- inline local 'dlg'
{
InitialDirectory = Environment.CurrentDirectory,
Title = "Pick a file",
}).ShowDialog() == true)
{
MessageBox.Show(dlg.FileName);
}
示例2:public static class _myglobals
{
/// <summary> Helper function for declaring local variables inline. </summary>
public static ref T local<T>(out T t)
{
t = default(T);
return ref t;
}
};
using System;
using /* etc... */
using System.Xml;
using Microsoft.Win32;
using static _myglobals; // <-- puts function 'local(...)' into global name scope
namespace MyNamespace
{
// ...
var s = "abc" + (local(out int i) = 2) + "xyz"; // <-- inline declaration of local 'i'
i++;
Console.WriteLine(s + i); // --> abc2xyz3
if ((local(out OpenFileDialog dlg) = new OpenFileDialog // <--- inline local 'dlg'
{
InitialDirectory = Environment.CurrentDirectory,
Title = "Pick a file",
}).ShowDialog() == true)
{
MessageBox.Show(dlg.FileName);
}
这个毫无意义的示例代码的要点是,尽管我们没有以任何方式更改ref localvariablerpi
本身(因为'ya不能),但它现在确实有了一个不同的(最终的)引用
更严重的是,ref local现在允许的是一种我称之为值类型戳记的技术,它允许高效的IL-in循环体,需要访问值类型数组中每个元素的多个字段。通常,这是外部初始化(
newobj
/initobj
)之间的权衡