当前位置:网站首页>Improvement of ref and struct in C 11
Improvement of ref and struct in C 11
2022-04-23 03:08:00 【Dotnet cross platform】
Preface
C# 11 There's a heavyweight feature coming in that can make performance conscious developers ecstatic , This feature is mainly around an important underlying performance facility ref and struct A series of improvements to .
However, the improvement of this part involves many contents , Not necessarily in .NET 7(C# 11) finish , Therefore, part of the content is postponed to C# 12 It's also possible . Of course , It is still very promising to be in C# 11 We'll see all of them in time .
This article only introduces this feature , because C# 11 In addition to this feature , There are many other improvements , There is no end to an article , The rest we'll wait until .NET 7 Let's talk about it when it's officially released .
background
C# since 7.0 Version introduces a new ref struct Used to represent objects on the stack that cannot be boxed , But there were great limitations at that time , Can't even be used for generic constraints , Nor can it serve as struct Field of . stay C# 11 in , Due to characteristics ref Field push , You need to allow types to hold references to other value types , Things in this area have finally made substantial progress .
These facilities are designed to allow developers to write high-performance code using secure code , Without having to face unsafe pointers . Next I'm going to C# 11 even to the extent that 12 The upcoming improvements in this area are introduced .
ref Field
C# Previously, you couldn't hold references to other value types in types , But in C# 11 in , This will become possible . from C# 11 Start , Will allow ref struct Definition ref Field .
readonly ref struct Span<T>
{
private readonly ref T _field;
private readonly int _length;
public Span(ref T value)
{
_field = ref value;
_length = 1;
}
}
Intuitive to see , Such a feature will allow us to write the above code , This code constructs a Span<T>, It holds on to other T References to objects .
Of course ,ref struct It can also be default To initialize :
Span<int> span = default;
But such _field It will be an empty reference , But we can go through Unsafe.IsNullRef Method to check :
if (Unsafe.IsNullRef(ref _field))
{
throw new NullReferenceException(...);
}
in addition ,ref The modifiability of fields is also a very important thing , So it introduces :
readonly ref: A read-only reference to an object , The reference itself cannot be in a constructor orinitModified outside the methodref readonly: A reference to a read-only object , The object pointed to by this reference cannot be in the constructor or init Modified outside the methodreadonly ref readonly: A read-only reference to a read-only object , It's a combination of the above two
for example :
ref struct Foo
{
ref readonly int f1;
readonly ref int f2;
readonly ref readonly int f3;
void Bar(int[] array)
{
f1 = ref array[0]; // That's all right.
f1 = array[0]; // error , because f1 The referenced value cannot be modified
f2 = ref array[0]; // error , because f2 Itself cannot be modified
f2 = array[0]; // That's all right.
f3 = ref array[0]; // error : because f3 Itself cannot be modified
f3 = array[0]; // error : because f3 The referenced value cannot be modified
}
}
Life cycle
All this looks beautiful , But is there really no problem ?
Suppose we have the following code to use the above things :
Span<int> Foo()
{
int v = 42;
return new Span<int>(ref v);
}
v Is a local variable , After the function returns, its life cycle will end , Then the above code will lead to Span<int> Held v The reference to becomes invalid . By the way , The above code is completely legal , because C# Not previously supported ref Field , Therefore, the above code is unlikely to have an escape problem . however C# 11 Joined the ref Field , Objects on the stack may pass through ref Field and reference escape , So the code becomes unsafe .
If we have one CreateSpan Method to create a reference Span :
Span<int> CreateSpan(ref int v)
{
// ...
}
This leads to a series of previous C# No problem ( because ref The life cycle of is the current method ), But in C# 11 Because there may be ref Field, resulting in unsafe code written in a safe way :
Span<int> Foo(int v)
{
// 1
return CreateSpan(ref v);
// 2
int local = 42;
return CreateSpan(ref local);
// 3
Span<int> span = stackalloc int[42];
return CreateSpan(ref span[0]);
}
therefore , stay C# 11 You have to introduce disruptive changes , The above code is not allowed to compile . But that doesn't completely solve the problem .
In order to solve the problem of escape , C# 11 Formulated the reference escape safety rules . For a e In the field f:
If
fIt's arefField , alsoeyesthis, befIn the method it is surrounded, it refers to escape safetyOtherwise, if
fIt's arefField , befReference escape safety range andeThe same escape safety rangeOtherwise, if
eIs a reference type , befThe reference escape security scope of is the method that calls itotherwise
fReference escape safety range andeidentical
because C# Methods in can return references , So according to the above rules , One ref struct The method in will not be able to return a right or wrong ref References to fields :
ref struct Foo
{
private ref int _f1;
private int f2;
public ref int P1 => ref _f1; // That's all right.
public ref int P2 => ref _f2; // error , Because of the violation of Rule 4
}
In addition to citing escape safety rules , There is also the right ref Rules for assignment :
about
x.e1 = ref e2, amongxIt is safe to escape in the calling method , thate2Must be reference escape safe in the calling methodabout
e1 = ref e2, amonge1It's a local variable , thate2The reference escape safety range of must be at least the same ase1The reference escape safety range is as large as
therefore , According to the above rules , The following code is OK :
readonly ref struct Span<T>
{
readonly ref T _field;
readonly int _length;
public Span(ref T value)
{
// That's all right. , because x yes this,this Safe escape range and value The safe range of reference escape is to call methods , Meet the rules 1
_field = ref value;
_length = 1;
}
}
So naturally , You need to label the life cycle on fields and parameters , Help the compiler determine the escape range of the object .
And when we write code , You don't need to remember so many of the above rules , Because with the lifecycle annotation, everything becomes explicit and intuitive .
scoped
stay C# 11 in , Introduced scoped Keyword is used to limit the escape safety range :
| local variable s | Reference escape safety range | Escape safety range |
|---|---|---|
Span<int> s |
The current method | Calling method |
scoped Span<int> s |
The current method | The current method |
ref Span<int> s |
Calling method | Calling method |
scoped ref Span<int> s |
The current method | Calling method |
ref scoped Span<int> s |
The current method | The current method |
scoped ref scoped Span<int> s |
The current method | The current method |
among ,scoped ref scoped It's redundant , Because it can be ref scoped Implication . And we just need to know scoped The method used to escape to the current range , Is it very simple ?
In this way , We can escape the parameters ( Life cycle ) The annotation :
Span<int> CreateSpan(scoped ref int v)
{
// ...
}
then , The previous code has become no problem , Because it's all scoped ref:
Span<int> Foo(int v)
{
// 1
return CreateSpan(ref v);
// 2
int local = 42;
return CreateSpan(ref local);
// 3
Span<int> span = stackalloc int[42];
return CreateSpan(ref span[0]);
}
scoped It can also be used on local variables :
Span<int> Foo()
{
// error , because span Cannot escape the current method
scoped Span<int> span1 = default;
return span1;
// That's all right. , Because the escape safety range of the initializer is to call the method , because span2 You can escape to calling methods
Span<int> span2 = default;
return span2;
// span3 and span4 It's the same , Because the escape safety range of the initializer is the current method , Add do not add scoped It makes no difference
Span<int> span3 = stackalloc int[42];
scoped Span<int> span4 = stackalloc int[42];
}
in addition ,struct Of this Also added. scoped ref The escape range of , That is, the reference escape safety range is the current method , The escape safety range is to call the method .
The rest is and out、in Parameter matching , stay C# 11 in ,out The parameter will default to scoped ref, and in The parameter remains at the default of ref:
ref int Foo(out int r)
{
r = 42;
return ref r; // error , because r The reference escape security scope of is the current method
}
This is very useful , For example, the following common situation :
Span<byte> Read(Span<byte> buffer, out int read)
{
// ..
}
Span<int> Use()
{
var buffer = new byte[256];
// If not modified out Reference escape safety range , This will report an error , Because the compiler needs to consider read Can be regarded as ref Field returns
// If modified out Reference escape safety range , Then there will be no problem , Because the compiler doesn't need to consider read Can be regarded as ref Field returns
int read;
return Read(buffer, out read);
}
Here are some more examples :
Span<int> CreateWithoutCapture(scoped ref int value)
{
// error , because value The reference escape security scope of is the current method
return new Span<int>(ref value);
}
Span<int> CreateAndCapture(ref int value)
{
// That's all right. , because value The safe range of escape is limited to value Reference escape safety range , This scope is to call methods
return new Span<int>(ref value)
}
Span<int> ComplexScopedRefExample(scoped ref Span<int> span)
{
// That's all right. , because span The escape safety scope of is to call the method
return span;
// That's all right. , because refLocal The reference escape security scope of is the current method 、 The escape safe range is to call the method
// stay ComplexScopedRefExample In the call of, it is passed to a scoped ref Parameters ,
// It means that the compiler does not need to consider the reference escape safety range when calculating the life cycle , Just consider the escape safety range
// Therefore, the safe escape range of the value it returns is the calling method
Span<int> local = default;
ref Span<int> refLocal = ref local;
return ComplexScopedRefExample(ref refLocal);
// error , because stackLocal Reference escape safety range 、 Escape safety ranges are current methods
// stay ComplexScopedRefExample In the call of, it is passed to a scoped ref Parameters ,
// It means that the compiler does not need to consider the reference escape safety range when calculating the life cycle , Just consider the escape safety range
// Therefore, the safe escape range of the value it returns is the current method
Span<int> stackLocal = stackalloc int[42];
return ComplexScopedRefExample(ref stackLocal);
}
unscoped
In the above design , There is still a problem that has not been solved :
struct S
{
int _field;
// error , because this The reference escape security scope of is the current method
public ref int Prop => ref _field;
}
So introduce a unscoped, Allow the escape scope to be extended to the calling method , therefore , The above method can be rewritten as :
struct S
{
private int _field;
// That's all right. , The reference escape security scope is extended to call methods
public unscoped ref int Prop => ref _field;
}
This unscoped You can also put it directly into struct On :
unscoped struct S
{
private int _field;
public unscoped ref int Prop => ref _field;
}
Empathy , Nested struct No problem :
unscoped struct Child
{
int _value;
public ref int Value => ref _value;
}
unscoped struct Container
{
Child _child;
public ref int Value => ref _child.Value;
}
Besides , If you need to restore the previous out If the escape range , It can also be in out Parameter unscoped:
ref int Foo(unscoped out int r)
{
r = 42;
return ref r;
}
But about unscoped The design of is still in the preliminary stage , Not in C# 11 Is provided in .
ref struct constraint
from C# 11 Start ,ref struct It can be used as a generic constraint , So you can write the following method :
void Foo<T>(T v) where T : ref struct
{
// ...
}
therefore ,Span<T> The function of has also been extended , It can be stated that Span<Span<T>> 了 , For example, in byte perhaps char On , It can be used for high-performance string processing .
Reflection
With so many things on it , Reflection naturally needs to be supported . therefore , Reflection API And joined in ref struct Related support .
The actual cases
With the above infrastructure , We can use security code to build some high-performance wheels .
Fixed length list on stack
struct FrugalList<T>
{
private T _item0;
private T _item1;
private T _item2;
public readonly int Count = 3;
public unscoped ref T this[int index] => index switch
{
0 => ref _item1,
1 => ref _item2,
2 => ref _item3,
_ => throw new OutOfRangeException("Out of range.")
};
}
Stack linked list
ref struct StackLinkedListNode<T>
{
private T _value;
private ref StackLinkedListNode<T> _next;
public T Value => _value;
public bool HasNext => !Unsafe.IsNullRef(ref _next);
public ref StackLinkedListNode<T> Next => HasNext ? ref _next : throw new InvalidOperationException("No next node.");
public StackLinkedListNode(T value)
{
this = default;
_value = value;
}
public StackLinkedListNode(T value, ref StackLinkedListNode<T> next)
{
_value = value;
_next = ref next;
}
}
In addition to these two examples , Others, such as parsers and serializers , for example Utf8JsonReader、Utf8JsonWriter You can use these things .
Future plans
Advanced lifecycle
Although the above life cycle design can meet the needs of most users , But it's not flexible enough , Therefore, it is possible to expand on this basis in the future , Introduce advanced lifecycle annotations . for example :
void M(scoped<'a> ref MyStruct s, scoped<'b> Span<int> span) where 'b >= 'a
{
s.Span = span;
}
The above method gives parameters s and span Two life cycles are declared respectively 'a and 'b, And constraints 'b The life cycle of is not less than 'a, So in this method ,span Can be safely assigned to s.Span.
Although this will not be included in C# 11 in , However, if developers' demand for relevant products increases in the future , It is possible to be subsequently added to C# Medium .
summary
That's all C# 11( Or after ) Yes ref and struct Improved . With this infrastructure , Developers will be able to easily write high-performance code in a safe way without any heap memory overhead . Although these improvements can only directly benefit a small number of developers who are very concerned about performance , However, these improvements will lead to the overall improvement of the code quality and performance of the subsequent basic library .
If you're worried that this will increase the complexity of the language , It doesn't have to be , Because most people don't use these things , It will only affect a small number of developers . So for most people , Just write the original code , Enjoy other basic libraries, and the author can use the above facilities to write good things .
版权声明
本文为[Dotnet cross platform]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230301333303.html
边栏推荐
- ASP. Net and ASP NETCORE multi environment configuration comparison
- Recommend reading | share the trader's book list and ask famous experts for trading advice. The trading is wonderful
- 全网讲的最细,软件测试度量,怎样优化软件测试成本提高效率---火爆
- [Euler plan question 13] sum of large numbers
- Openfeign service call
- Use split to solve the "most common words" problem
- Fight leetcode again (290. Word law)
- Tips in MATLAB
- Laravel new route file
- FileNotFoundError: [Errno 2] No such file or directory
猜你喜欢

微软是如何解决 PC 端程序多开问题的——内部实现

be based on. NETCORE development blog project starblog - (1) why do you need to write your own blog?

荐读 | 分享交易员的书单,向名家请教交易之道,交易精彩无比

腾讯视频涨价:一年多赚74亿!关注我领取腾讯VIP会员,周卡低至7元

2022年度Top9的任务管理系统

Tencent video VIP member, weekly card special price of 9 yuan! Tencent official direct charging, members take effect immediately!

Some problems encountered in setting Django pure interface, channel and MySQL on the pagoda panel

Source Generator实战

TP5 customization in extend directory succeeded and failed. Return information

ASP.NET 6 中间件系列 - 条件中间件
随机推荐
編碼電機PID調試(速度環|比特置環|跟隨)
全网讲的最细,软件测试度量,怎样优化软件测试成本提高效率---火爆
Service avalanche effect
FileNotFoundError: [Errno 2] No such file or directory
Maui initial experience: Cool
利用正反遍历来解决“字符的最短距离”问题
搭建XAMPP时mysql端口被占用
Tencent video VIP member, weekly card special price of 9 yuan! Tencent official direct charging, members take effect immediately!
C syntax pattern matching [switch expression]
交换二叉树中每个结点的左和右
Openfeign details show
Miniapi of. Net7 (special section): NET7 Preview3
SQL statement - DDL
C read / write binary file
树莓派开发笔记(十二):入手研华ADVANTECH工控树莓派UNO-220套件(一):介绍和运行系统
Blazor University (11) component - replace attributes of subcomponents
微软是如何解决 PC 端程序多开问题的——内部实现
Use of MySQL command line client and common commands
Notes sur le développement de la tarte aux framboises (XII): commencer à étudier la suite UNO - 220 de la tarte aux framboises de contrôle industriel advantech (i): Introduction et fonctionnement du s
数据挖掘系列(3)_Excel的数据挖掘插件_估计分析