You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确保C#结构体字段始终有效?音符存储场景下的方案抉择

最优方案分析:结构体存储有效音符的选择

你想用只读结构体存储音符(核心是一个int值,同时附带音符名称、八度等派生字段),但C#中结构体可通过默认构造(如default(Note)或直接声明未初始化变量)生成无效状态。针对你列出的四种方案,下面逐个分析并给出最优选择:

方案1:无参构造函数初始化默认值

readonly struct Number
{
    public readonly int Value;
    public readonly int ValueTimesTen;

    public Number()
    {
        this = new(1);
    }

    public Number(int value)
    {
        if (value is > 0 and <= 10)
        {
            Value = value;
            ValueTimesTen = Value * 10;
        }
        else
        {
            throw new ArgumentException("Number has to be between 1 and 10.");
        }
    }
}

缺陷:这种方法只能覆盖手动调用构造的场景,C#仍允许通过default(Number)或Number num;创建默认实例,此时所有字段会被强制设为默认值(Value=0、ValueTimesTen=0),完全绕开你的无参构造,无效状态依然存在,无法彻底解决问题。

方案2:属性拦截无效值

readonly struct Number
{
    private readonly int _value;

    public int Value
    {
        get
        {
            if (GetValid(_value))
            {
                return _value;
            }
            else
            {
                return 1;
            }
        }
    }

    public int ValueTimesTen => Value * 10;

    private static bool GetValid(int number)
    {
        return (number is > 0 and <= 10);
    }

    public Number(int value)
    {
        if (GetValid(value))
        {
            _value = value;            
        }
        else
        {
            throw new ArgumentException("Number has to be between 1 and 10.");
        }
    }
}

缺陷:如你所说,实际场景中派生字段(音符名称、八度等)的计算开销很大,每次访问属性都重新计算会严重影响性能。而且这种方案只是“掩盖”了无效状态,结构体私有字段_value依然可能是无效值(比如默认实例的_value=0),逻辑上存在不一致性,不是彻底的解决方案。

方案3:改用类

优势:类的实例必须通过构造函数创建,不存在默认构造绕开的问题(除非你显式编写无参构造),能完全保证实例始终处于有效状态。对于不可变的小对象,虽然结构体栈分配有性能优势,但现代.NET的GC对小对象回收效率极高,性能差异可忽略不计。

你可以把类设计为不可变值类(所有字段设为readonly,只提供构造函数和只读属性),完全保留结构体的不可变特性,同时彻底规避无效实例问题。

方案4:允许无效状态,靠外部代码保证

缺陷:这是最危险的方案,完全依赖开发人员自律,后续维护或多人协作时极易出现疏漏,导致无效实例流入业务逻辑,引发难以排查的bug。除非代码范围极小且完全由你自己维护,否则绝不推荐。

总结:最优选择是方案3(改用类)

虽然结构体看起来更贴合“值类型”语义,但C#值类型的默认构造行为导致无法彻底避免无效实例。改用不可变类能彻底解决问题,性能上的差异在绝大多数场景下可以忽略。如果实在想保留值类型特性,.NET 7+的required修饰符或工厂方法也无法阻止default实例生成,所以类的方案依然是最可靠的。

内容的提问来源于stack exchange,提问作者Xavier Evans

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 11:25:36