当前位置:网站首页>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 orinit
Modified 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
f
It's aref
Field , alsoe
yesthis
, bef
In the method it is surrounded, it refers to escape safetyOtherwise, if
f
It's aref
Field , bef
Reference escape safety range ande
The same escape safety rangeOtherwise, if
e
Is a reference type , bef
The reference escape security scope of is the method that calls itotherwise
f
Reference escape safety range ande
identical
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
, amongx
It is safe to escape in the calling method , thate2
Must be reference escape safe in the calling methodabout
e1 = ref e2
, amonge1
It's a local variable , thate2
The reference escape safety range of must be at least the same ase1
The 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
边栏推荐
- Tencent video VIP member, weekly card special price of 9 yuan! Tencent official direct charging, members take effect immediately!
- C# 11 对 ref 和 struct 的改进
- Using positive and negative traversal to solve the problem of "the shortest distance of characters"
- Use split to solve the "most common words" problem
- 7-11 重排链表 (25 分)
- Drawing polygons with < polygon / > circular array in SVG tag
- Use DFS to solve the problem of "number of dictionary rows"
- Establishing and traversing binary tree
- Blazor University (12)组件 — 组件生命周期
- Summary of interface automation interview questions for software testing
猜你喜欢
Passing object type parameters through openfeign
2022年度Top9的任务管理系统
Openfeign details show
Tencent video price rise: earn more than 7.4 billion a year! Pay attention to me to receive Tencent VIP members, and the weekly card is as low as 7 yuan
MYSQL05_ Ordr by sorting, limit grouping, group by grouping
类似Jira的十大项目管理软件
【新版发布】ComponentOne 新增 .NET 6 和 Blazor 平台控件支持
MySQL port is occupied when building xampp
再战leetcode (290.单词规律)
Response processing of openfeign
随机推荐
TP5 inherits base and uses the variables in base
2022年P气瓶充装培训试题及模拟考试
Using stack to solve the problem of "mini parser"
全网讲的最细,软件测试度量,怎样优化软件测试成本提高效率---火爆
Creating wechat voucher process with PHP
Service avalanche effect
OLED multi-level menu record
Blazor University (11)组件 — 替换子组件的属性
Use split to solve the "most common words" problem
[software testing] understand the basic knowledge of software testing
由于3²+4²=5²,所以称‘3,4,5‘为勾股数,求n(包括n)以内所有勾股数数组。
TP5 email (2020-05-27)
A set of C interview questions about memory alignment. Many people make mistakes!
一套关于 内存对齐 的C#面试题,做错的人很多!
基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客?
7-11 重排链表 (25 分)
最通俗易懂的依赖注入与控制反转
对.NET未来的一点感悟
Miniapi of. Net7 (special section): NET7 Preview3
Tips in MATLAB