-
[译]C# 7系列,Part 9: ref structs ref结构
原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/02/c-7-series-part-9-ref-structs/
背景
在之前的文章中,我解释了许多新的C#特性,每一个特性都是为了增强语言或者解决问题而引入的。具体来说,我解释了值类型和引用类型、按值传递参数、按引用传递参数、ref局部变量和ref返回结果以及in参数。这其中许多功能是为高性能场景设计的。
ref和in参数可以帮助避免复制值,从而减少内存分配。当你有分配在堆栈的局部变量作为方法的实际参数传递时,这么做是有效率的的,在这种情况下,所有的分配都在堆栈上;不需要堆分配。
对于高性能和原生开发场景,你可能希望“仅限堆栈”类型始终停留在执行堆栈上,因此对这种类型的对象的操作只能发生在堆栈上,在作用域中公开给托管堆的任何对这种类型的外部引用都应该被禁止。
ref结构
ref struct是仅在堆栈上的值类型:
- 表现一个顺序结构的布局;(译注:可以理解为连续内存)
- 只能在堆栈上使用。即用作方法参数和局部变量;
- 不能是类或正常结构的静态或实例成员;
- 不能是异步方法或lambda表达式的方法参数;
- 不能动态绑定、装箱、拆箱、包装或转换。
ref struct也被称为嵌入式引用。
示例
下面的代码定义了一个ref结构。
public ref struct MyRefStruct { public int MyIntValue1; public int MyIntValue2; [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() => throw new NotSupportedException(); }
请注意,我已经覆盖了从System.Object继承的Equals、GetHashCode和ToString方法。因为ref结构不允许装箱,所以你将无法调用这两个(译注:原文两个,应该是三个)基方法。
你可以在方法参数或局部变量中使用MyRefStruct作为常规值类型,但不能在其他地方使用它。
你也可以创建只读ref结构,只需将readonly指令添加到ref结构声明中即可。
public readonly ref struct MyRefStruct { public readonly int MyIntValue1; public readonly int MyIntValue2; public MyRefStruct(int value1, int value2) { this.MyIntValue1 = value1; this.MyIntValue2 = value2; } [EditorBrowsable(EditorBrowsableState.Never)] public override bool Equals(object obj) => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override int GetHashCode() => throw new NotSupportedException(); [EditorBrowsable(EditorBrowsableState.Never)] public override string ToString() => throw new NotSupportedException(); }
与常规只读结构一样,需要将所有实例字段/属性设置为只读。
元数据
ref结构在C# 7.2中可用。此功能需要编译器级别更改才能工作,以便与以前编译器生成的程序集向后兼容。编译器会为ref结构声明发出[Obsolete]和[IsByRefLike]特性。
如果任何旧的程序集引用了包含ref结构类型的库,[Obsolete]属性将影响并阻止代码编译。
下面是为上面的ref结构声明生成的IL。
.class public sequential ansi sealed beforefieldinit Demo.MyRefStruct extends [System.Runtime]System.ValueType { .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [System.Runtime]System.ObsoleteAttribute::.ctor(string, bool) = ( 01 00 52 54 79 70 65 73 20 77 69 74 68 20 65 6d 62 65 64 64 65 64 20 72 65 66 65 72 65 6e 63 65 73 20 61 72 65 20 6e 6f 74 20 73 75 70 70 6f 72 74 65 64 20 69 6e 20 74 68 69 73 20 76 65 72 73 69 6f 6e 20 6f 66 20 79 6f 75 72 20 63 6f 6d 70 69 6c 65 72 2e 01 00 00 ) .custom instance void [System.Runtime]System.Runtime.CompilerServices.IsReadOnlyAttribute::.ctor() = ( 01 00 00 00 ) // Fields .field public initonly int32 MyIntValue1 .field public initonly int32 MyIntValue2 // Methods .method public hidebysig specialname rtspecialname instance void .ctor ( int32 value1, int32 value2 ) cil managed { // Method begins at RVA 0x2090 // Code size 16 (0x10) .maxstack 8 // (no C# code) IL_0000: nop // this.MyIntValue1 = value1; IL_0001: ldarg.0 IL_0002: ldarg.1 IL_0003: stfld int32 Demo.MyRefStruct::MyIntValue1 // this.MyIntValue2 = value2; IL_0008: ldarg.0 IL_0009: ldarg.2 IL_000a: stfld int32 Demo.MyRefStruct::MyIntValue2 // (no C# code) IL_000f: ret } // end of method MyRefStruct::.ctor .method public hidebysig virtual instance bool Equals ( object obj ) cil managed { // Method begins at RVA 0x20a1 // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::Equals .method public hidebysig virtual instance int32 GetHashCode () cil managed { .custom instance void [System.Runtime]System.ComponentModel.EditorBrowsableAttribute::.ctor(valuetype [System.Runtime]System.ComponentModel.EditorBrowsableState) = ( 01 00 01 00 00 00 00 00 ) // Method begins at RVA 0x20a8 // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::GetHashCode .method public hidebysig virtual instance string ToString () cil managed { .custom instance void [System.Runtime]System.ComponentModel.EditorBrowsableAttribute::.ctor(valuetype [System.Runtime]System.ComponentModel.EditorBrowsableState) = ( 01 00 01 00 00 00 00 00 ) // Method begins at RVA 0x20af // Code size 6 (0x6) .maxstack 8 // throw new NotSupportedException(); IL_0000: newobj instance void [System.Runtime]System.NotSupportedException::.ctor() // (no C# code) IL_0005: throw } // end of method MyRefStruct::ToString } // end of class Demo.MyRefStruct
Span<T>和Memory<T>
有了类ref类型的支持,现在就可以为所有连续内存访问提供统一的类型。System.Span<T>表示内存的连续空间,可用于执行堆栈、托管堆和非托管堆的通用内存操作。
下面是ReadOnlySpan<T>的一个简单用法,用于去掉字符串的开始的空格。
internal class Program { private static void Main(string[] args) { string text = " I am using C# 7.2 Span<T>!"; Console.WriteLine(TrimStart(text).ToArray()); } private static ReadOnlySpan<char> TrimStart(ReadOnlySpan<char> text) { if (text.IsEmpty) { return text; } int i = 0; char c; while ((c = text[i]) == ' ') { i++; } return text.Slice(i); } }
结论
C# 7.2为高性能场景添加了语言特性,并为低级别的原生开发和互操作性场景提供了效率。ref结构还可以与stackalloc、Span<T>、fixed buffers和Ranges(C# 7.3)一起用于生产力。
注意:要使用这个特性,需要Visual Studio 2017 15.5或更高版本。