小心C#中的只读结构体成员

示例

  • 我们先来看一段结构体的代码 (基于 VS2022 + .NET 8.0)
C# 复制代码
public struct MyStruct(int number)
{
    public int Number = number;
    public void SetNumber(int number) => Number = number;
}
public class Program
{
    private static MyStruct myStruct = new(1);
    public static void Main()
    {
        int before = myStruct.Number;
        myStruct.SetNumber(2);
        int after = myStruct.Number;
        Console.WriteLine($"before: {before}");
        Console.WriteLine($" after: {after}");
        Console.ReadKey();
    }
}

输出如下:
before: 1
** after: 2**

修改为只读

  • private static readonly MyStruct myStruct = new(1);

输出如下:
before: 1
** after: 1**

  • 我们看到,修改只读结构体成员的字段失败了,但是编译器竟然没有报错
  • 如果我们直接操作 myStruct.Number = 2; 编译器是会报错的,但是加了一个方法间接的修改,编译器就歇菜了

内部原理

我们查看反汇编代码,可以看到,在实际操作只读结构体成员字段的时候,会把该字段的值拷贝一份到一个新的堆栈变量上,然后再基于拷贝后的这个变量计算

ASM 复制代码
    17:         myStruct.SetNumber(2);
 mov         rcx,7FF9BD68E500h
 mov         edx,9
 call        CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE (07FFA1D15B6F0h)
 mov         rcx,26AE19DB1D0h         //rcx保存结构体Number的地址
 mov         rcx,qword ptr [rcx]      //拷贝Number的值到rcx
 mov         qword ptr [rbp+70h],rcx  //rcx的值赋值到临时变量
 lea         rcx,[rbp+70h]
 mov         edx,2
 call        ConsoleTest_NET_8.MyStruct.SetNumber(Int32) (07FF9BD6A2BC8h)

导致的问题

  • 我们先来看一段自旋锁的代码,基于 SpinLock
C# 复制代码
public class Program
{
    private static readonly SpinLock spinLock = new(false);
    public static void Main()
    {
        int sum = 0;
        Parallel.For(0, ushort.MaxValue, i =>
        {
            bool lockToken = false;
            try
            {
                spinLock.Enter(ref lockToken);
                sum++;
            }
            finally
            {
                if (lockToken)
                {
                    spinLock.Exit();
                }
            }
        });
        Console.WriteLine(sum);
        Console.ReadKey();
    }
}
  • 我们期望的输出是: 65535, 但实际不是,因为隐藏的只读机制导致了字段值的拷贝, 这就造成了隐藏的 BUG

结论

  • 警惕把结构体成员变量设置只读

  • 在确定结构体内的字段只读时,可以使用 readonly 直接修饰 结构体本身或者字段,比如

    C# 复制代码
    public readonly struct MyStruct(int number)
    {
        public readonly int Number = number;
    }
相关推荐
白露与泡影14 分钟前
Arthas 实战指南:从方法耗时定位到 JVM 变量热修改
服务器·jvm·c#
EIP低代码平台5 小时前
EIP低代码平台系统-字典功能讲解
低代码·c#·工作流
吉普赛的歌6 小时前
YARP负载均衡配置了多个相同接口导致的报错
c#·yarp
张人玉7 小时前
C# WinForms——工厂管理系统(C# WinForms)
数据库·sqlite·c#·winform
夜莺悠吟9 小时前
关于对 C# 中 ImplicitUsings,GlobalUsings 的讨论
c#·.net
Liu.7749 小时前
uni-app 组件 uni-easyinput 常见 Bug 及解决方案
uni-app·bug
-银雾鸢尾-16 小时前
C#中的拓展方法
开发语言·c#
EIP低代码平台1 天前
EIP低代码平台-系统参数配置详解|落地三级等保+高灵活私有化部署方案
低代码·c#·权限·工作流·netcore
王莎莎-MinerU1 天前
MCP 解决的是工具接入,科研 Agent 还缺的是科学证据接口标准化
开发语言·网络·人工智能·深度学习·pdf·c#·php
-银雾鸢尾-1 天前
C#中Object类内的方法
开发语言·c#