In the previous article we introduced in C # read-only structure (Readonly struct) 1 and its close relative in
parameters 2 .
Today we will discuss a feature introduced from C# 8: read-only instance members in a mutable structure (when the structure is mutable, the instance members that will not change the state of the structure are declared as readonly
).
Reasons for introducing read-only instance members
Simply put, it is to improve performance .
We already know the read-only structure ( readonly struct
) and in
parameters can create a copy by reducing, to improve the performance of code running. When we create a read-only structure type, the compiler forces all members to be read-only (that is, no instance members modify their state). However, in some scenarios, for example, you have an existing API with publicly accessible fields or both mutable and immutable members. In this case, the type cannot be marked as readonly
(because this concerns all instance members).
Usually, this is not much impact, but in the use in
case parameters on the exception. For non-read-only structure in
parameters, the compiler will create a defensive copy of the parameters of calling each instance member, because it can not guarantee that this call does not modify its internal state. This may cause a large number of copies to be created, and the overall performance is worse than when passing the structure directly by value (because passing by value will only create a copy once when passing parameters).
You look at an example to understand, we define such a general structure, then used as in
arguments:
public struct Rect
{
public float w;
public float h;
public float Area
{
get
{
return w * h;
}
}
}
public class SampleClass
{
public float M(in Rect value)
{
return value.Area + value.Area;
}
}
After compilation, the class SampleClass
method M
code to run the logic is actually this :
public float M([In] [IsReadOnly] ref Rect value)
{
Rect rect = value; //防御性副本
float area = rect.Area;
rect = value; //防御性副本
return area + rect.Area;
}
Read-only instance members in variable structures
We put above a variable structure Rect
changed a bit, add a readonly
method GetAreaReadOnly
, as follows:
public struct Rect
{
public float w;
public float h;
public float Area
{
get
{
return w * h;
}
}
public readonly float GetAreaReadOnly()
{
return Area; //警告 CS8656 从 "readonly" 成员调用非 readonly 成员 "Rect.Area.get" 将产生 "this" 的隐式副本。
}
}
At this point, the code can be compiled, but a warning like this will be prompted: calling the non-readonly member "Rect.Area.get" from the "readonly" member will produce an implicit copy of "this".
National Cheng Kung University vernacular translation that is to say, in a read-only method we GetAreaReadOnly
call the non-read-only Area
attribute will have "this" defensive copy. After compiling a code demonstrates what method GetAreaReadOnly
the method body is actually operating logic is this :
[IsReadOnly]
public float GetAreaReadOnly()
{
Rect rect = this; //防御性副本
return rect.Area;
}
So in order to avoid creating unnecessary defensive copy and affect performance, we should give a read-only property or method calls the method body are plus readonly
modifiers (in this case, that is, to attribute Area
add readonly
modifier).
Call a read-only instance member in a variable structure
Let's modify the above example again:
public struct Rect
{
public float w;
public float h;
public readonly float Area
{
get
{
return w * h;
}
}
public readonly float GetAreaReadOnly()
{
return Area;
}
public float GetArea()
{
return Area;
}
}
public class SampleClass
{
public float CallGetArea(Rect vector)
{
return vector.GetArea();
}
public float CallGetAreaIn(in Rect vector)
{
return vector.GetArea();
}
public float CallGetAreaReadOnly(in Rect vector)
{
//调用可变结构体中的只读实例成员
return vector.GetAreaReadOnly();
}
}
Class SampleClass
is defined in three methods:
- The first method is our usual calling method before;
- In the second
in
argument to a variable structure, the non-read-only (method may modify the state of the structure) call; - In the third
in
parameter into the variable structure, read-only method call.
Let's focus on the difference between the second and third methods, or translate their IL code logic into easy-to-understand execution logic, as shown below :
public float CallGetAreaIn([In] [IsReadOnly] ref Rect vector)
{
Rect rect = vector; //防御性副本
return rect.GetArea();
}
public float CallGetAreaReadOnly([In] [IsReadOnly] ref Rect vector)
{
return vector.GetAreaReadOnly();
}
It can be seen that CallGetAreaReadOnly
when calling the (read-only) member method of the CallGetAreaIn
structure, a local defensive copy is created less than (the non-read-only member method of the structure is called), so there should be an advantage in execution performance .
Performance analysis of read-only instance members
Performance improvement in the structure when the larger obvious, so in order to highlight the difference in performance of the method three, when I tested Rect
added 30 decimal attribute type structural body, then the class SampleClass
is added in three The test method, the code is as follows:
public struct Rect
{
public float w;
public float h;
public readonly float Area
{
get
{
return w * h;
}
}
public readonly float GetAreaReadOnly()
{
return Area;
}
public float GetArea()
{
return Area;
}
public decimal Number1 {
get; set; }
public decimal Number2 {
get; set; }
//...
public decimal Number30 {
get; set; }
}
public class SampleClass
{
const int loops = 50000000;
Rect rectInstance;
public SampleClass()
{
rectInstance = new Rect();
}
[Benchmark(Baseline = true)]
public float DoNormalLoop()
{
float result = 0F;
for (int i = 0; i < loops; i++)
{
result = CallGetArea(rectInstance);
}
return result;
}
[Benchmark]
public float DoNormalLoopByIn()
{
float result = 0F;
for (int i = 0; i < loops; i++)
{
result = CallGetAreaIn(in rectInstance);
}
return result;
}
[Benchmark]
public float DoReadOnlyLoopByIn()
{
float result = 0F;
for (int i = 0; i < loops; i++)
{
result = CallGetAreaReadOnly(in rectInstance);
}
return result;
}
public float CallGetArea(Rect vector)
{
return vector.GetArea();
}
public float CallGetAreaIn(in Rect vector)
{
return vector.GetArea();
}
public float CallGetAreaReadOnly(in Rect vector)
{
return vector.GetAreaReadOnly();
}
}
Without the use of in
a method parameter, means that each incoming call is a new copy of the variable; and the use of in
modifier method, each new copy of the variable is not passed, but deliver the same copy of read-only reference .
DoNormalLoop
Methods, parameters without modifiers, pass in general structures, and call non-read-only methods of variable structures. This is a common practice in the past.DoNormalLoopByIn
Methods, parameters plusin
modifier, the general structure of the incoming, not read-only calling method for a variable structure.DoReadOnlyLoopByIn
Methods, parameters plusin
modifier, the general structure of the incoming call a read-only method for a variable structure.
Use the BenchmarkDotNet tool to test the running time of the three methods, and the results are as follows:
Method | Mean | Error | StdDev | Ratio | RatioSD |
---|---|---|---|---|---|
DoNormalLoop | 2.034 s | 0.0392 s | 0.0348 s | 1.00 | 0.00 |
DoNormalLoopByIn | 3.490 s | 0.0667 s | 0.0557 s | 1.71 | 0.03 |
DoReadOnlyLoopByIn | 1.041 s | 0.0189 s | 0.0202 s | 0.51 | 0.01 |
From the results, when the variable structure, using the in
read-only parameter method invocation structure, the performance is higher than the other two; using in
non-parametric method calls a read only variable structure, the longest running time, a serious impact In order to improve performance, such calls should be avoided.
to sum up
- When the structure is a mutable type, the members that will not cause change (that is, the state of the structure will not change) should be declared as
readonly
. - Examples of read-only invoked only when the members of the structure, using
in
the parameters, can effectively improve performance. readonly
Modifiers are required on read-only attributes. The compiler does not assume that the getter visitor does not modify the state. Therefore, it must be explicitly declared on the attribute.- Automatic property can be omitted
readonly
modifiers, because no matterreadonly
modifier is present, the compiler will automatically implement all the getter as read-only. - Do not use
in
non-read-only instance members call the structure parameters, because a negative impact on performance.
Author: Technical Zemin
Publisher: Technical Translation Station
https://ittranslator.cn/dotnet/csharp/2020/10/26/c-7-2-talk-about-readonly-struct.html Read-only structure in C# ↩︎
https://ittranslator.cn/dotnet/csharp/2020/11/02/understanding-in-modifier-csharp.html in parameters and performance analysis in C# ↩︎