BAR*_*ART 32 c# struct value-type readonly-attribute
MutableSlab和ImmutableSlab实现之间的唯一区别是readonly应用于handle字段的修饰符:
using System;
using System.Runtime.InteropServices;
public class Program
{
class MutableSlab : IDisposable
{
private GCHandle handle;
public MutableSlab()
{
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
}
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
{
this.handle.Free();
}
}
class ImmutableSlab : IDisposable
{
private readonly GCHandle handle;
public ImmutableSlab()
{
this.handle = GCHandle.Alloc(new byte[256], GCHandleType.Pinned);
}
public bool IsAllocated => this.handle.IsAllocated;
public void Dispose()
{
this.handle.Free();
}
}
public static void Main()
{
var mutableSlab = new MutableSlab();
var immutableSlab = new ImmutableSlab();
mutableSlab.Dispose();
immutableSlab.Dispose();
Console.WriteLine($"{nameof(mutableSlab)}.handle.IsAllocated = {mutableSlab.IsAllocated}");
Console.WriteLine($"{nameof(immutableSlab)}.handle.IsAllocated = {immutableSlab.IsAllocated}");
}
}
Run Code Online (Sandbox Code Playgroud)
但是它们产生不同的结果:
mutableSlab.handle.IsAllocated = False
immutableSlab.handle.IsAllocated = True
Run Code Online (Sandbox Code Playgroud)
GCHandle是一个可变结构,当您复制它时,它的行为与带有的情况完全相同immutableSlab。
readonly修饰符是否创建字段的隐藏副本?这是否意味着它不仅是编译时检查?我在这里找不到有关此行为的任何信息。是否记录了这种行为?
Jon*_*eet 33
readonly修饰符是否创建字段的隐藏副本?
在常规结构类型的只读字段上(在构造函数或静态构造函数之外)调用方法或属性是,首先复制该字段。那是因为编译器不知道属性或方法访问是否会修改您调用它的值。
根据C#5 ECMA规范:
12.7.5.1节(普通成员)
这对成员访问进行了分类,包括:
- 如果我标识一个静态字段:
- 如果该字段是只读字段,并且引用发生在声明该字段的类或结构的静态构造函数之外,则结果是一个值,即E中静态字段I的值。
- 否则,结果是一个变量,即E中的静态字段I。
和:
- 如果T是一个结构类型,并且我标识了该结构类型的实例字段:
- 如果E是一个值,或者该字段是只读的,并且引用发生在声明该字段的struct的实例构造函数之外,则结果是一个值,即由给出的struct实例中的I字段的值E.
- 否则,结果是一个变量,即E给定的struct实例中的字段I。
我不确定为什么实例字段部分专门引用结构类型,而静态字段部分却没有。重要的是表达式是分类为变量还是值。这在函数成员调用中很重要...
12.6.6.1节(函数成员的调用,常规)
函数成员调用的运行时处理包括以下步骤,其中M是函数成员,如果M是实例成员,则E是实例表达式:
[...]
- 否则,如果E的类型是值类型V,并且M在V中声明或覆盖:
- [...]
- 如果E未归类为变量,则会创建E类型的临时局部变量,并将E的值分配给该变量。然后,将E重新分类为对该临时局部变量的引用。可以在M中以这种方式访问临时变量,但不能以其他任何方式。因此,只有当E是一个真变量时,调用方才可以观察到M对此所做的更改。
这是一个独立的示例:
using System;
using System.Globalization;
struct Counter
{
private int count;
public int IncrementedCount => ++count;
}
class Test
{
static readonly Counter readOnlyCounter;
static Counter readWriteCounter;
static void Main()
{
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readOnlyCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 1
Console.WriteLine(readWriteCounter.IncrementedCount); // 2
Console.WriteLine(readWriteCounter.IncrementedCount); // 3
}
}
Run Code Online (Sandbox Code Playgroud)
这是致电给的IL readOnlyCounter.IncrementedCount:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
Run Code Online (Sandbox Code Playgroud)
它将字段值复制到堆栈上,然后调用属性...,以便字段值最终不会改变;它count在副本中递增。
将其与IL的可读写字段进行比较:
ldsflda valuetype Counter Test::readWriteCounter
call instance int32 Counter::get_IncrementedCount()
Run Code Online (Sandbox Code Playgroud)
这将直接在字段上进行调用,因此字段值最终会在属性中更改。
当结构较大且成员未对其进行变异时,进行复制可能会效率很低。这就是为什么在C#7.2及更高版本中,readonly可以将修饰符应用于结构的原因。这是另一个例子:
ldsfld valuetype Counter Test::readOnlyCounter
stloc.0
ldloca.s V_0
call instance int32 Counter::get_IncrementedCount()
Run Code Online (Sandbox Code Playgroud)
使用readonly结构本身上的修饰符,该field1.NoOp()调用不会创建副本。如果删除readonly修饰符并重新编译,您将看到它创建了一个副本,就像在中所做的一样readOnlyCounter.IncrementedCount。
我写了一篇2014年的博客文章,发现该readonly领域导致Noda Time出现性能问题。幸运的是,现在可以使用readonly结构上的修饰符修复该问题。
| 归档时间: |
|
| 查看次数: |
1083 次 |
| 最近记录: |