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

如何避免强命名数值结构体重复的运算符重载样板代码?

如何避免强命名数值结构体重复的运算符重载样板代码?

我完全懂你这种被重复样板代码淹没的痛苦——30多个readonly record struct,每个都要写几乎一模一样的加减乘除、比较运算符,维护起来简直是噩梦,而且还不能自己写源生成器,确实头疼。结合你的场景,我给你两个实用的解决方案:

方案一:用T4文本模板自动生成代码(最省心的重复代码消除方式)

既然你不想自己维护源生成器,那Visual Studio自带的T4模板是绝佳替代——它不需要复杂配置,只要写一个模板文件,就能自动帮你生成所有30个结构体的完整代码,包括你需要的特定创建方法、属性,以及通用的运算符重载。

举个简单的T4模板示例,你可以根据自己的需求扩展:

<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#
// 定义所有需要生成的强类型,每个类型包含:结构体名称、创建方法前缀、单位属性名称
var types = new[] {
    new { StructName = "Potential", FactoryPrefix = "From", UnitProperty = "InVolt" },
    new { StructName = "Duration", FactoryPrefix = "From", UnitProperty = "InSeconds" },
    new { StructName = "PotentialRate", FactoryPrefix = "From", UnitProperty = "InVoltsPerSecond" }
    // 剩下的27个类型都加在这里
};
#>
namespace YourNamespace {
<# foreach (var type in types) { #>
    public readonly record struct <#= type.StructName #> : IComparable<<#= type.StructName #>> {
        private readonly double _internalValue;

        private <#= type.StructName #>(double value) {
            _internalValue = value;
        }

        public static <#= type.StructName #> <#= type.FactoryPrefix #><#= type.UnitProperty.Substring(2) #>(double value) => new(value);
        public double <#= type.UnitProperty #> => _internalValue;

        // 通用运算符重载,自动生成
        public static <#= type.StructName #> operator +(<#= type.StructName #> left, <#= type.StructName #> right) 
            => new(left._internalValue + right._internalValue);

        public static <#= type.StructName #> operator -(<#= type.StructName #> left, <#= type.StructName #> right) 
            => new(left._internalValue - right._internalValue);

        public static <#= type.StructName #> operator *(<#= type.StructName #> value, double factor) 
            => new(value._internalValue * factor);

        public static double operator /(<#= type.StructName #> left, <#= type.StructName #> right) 
            => left._internalValue / right._internalValue;

        public static bool operator >(<#= type.StructName #> left, <#= type.StructName #> right) 
            => left._internalValue > right._internalValue;

        public static bool operator >=(<#= type.StructName #> left, <#= type.StructName #> right) 
            => left._internalValue >= right._internalValue;

        public static bool operator <(<#= type.StructName #> left, <#= type.StructName #> right) 
            => left._internalValue < right._internalValue;

        public static bool operator <=(<#= type.StructName #> left, <#= type.StructName #> right) 
            => left._internalValue <= right._internalValue;

        public int CompareTo(<#= type.StructName #> other) 
            => _internalValue.CompareTo(other._internalValue);

        // 这里可以添加每个类型特有的逻辑,比如类型转换运算符
        <# if (type.StructName == "PotentialRate") { #>
        public static implicit operator <#= type.StructName #>(Potential potential) 
            => new(potential.InVolt / 1); // 示例转换逻辑,按需修改
        <# } #>
    }
<# } #>
}

只要维护这个模板里的类型列表和特有的逻辑,就能自动生成所有结构体的代码,完全避免重复编写样板代码,而且比源生成器更容易维护。

方案二:用静态工具类封装通用逻辑(减少每个结构体的重复代码)

如果不想用代码生成,那可以把通用的运算符逻辑封装到一个静态类里,每个结构体只需要一行代码调用这些逻辑,大大减少重复:

首先定义一个接口来约束所有强类型:

public interface IStrongDouble<TSelf> where TSelf : IStrongDouble<TSelf>
{
    double InternalValue { get; }
    static abstract TSelf Create(double value);
}

然后写一个静态工具类,封装所有通用运算:

public static class StrongDoubleOperations
{
    public static TSelf Add<TSelf>(TSelf left, TSelf right) where TSelf : IStrongDouble<TSelf>
        => TSelf.Create(left.InternalValue + right.InternalValue);

    public static TSelf Subtract<TSelf>(TSelf left, TSelf right) where TSelf : IStrongDouble<TSelf>
        => TSelf.Create(left.InternalValue - right.InternalValue);

    public static TSelf MultiplyByFactor<TSelf>(TSelf value, double factor) where TSelf : IStrongDouble<TSelf>
        => TSelf.Create(value.InternalValue * factor);

    public static double Divide<TSelf>(TSelf left, TSelf right) where TSelf : IStrongDouble<TSelf>
        => left.InternalValue / right.InternalValue;

    public static bool GreaterThan<TSelf>(TSelf left, TSelf right) where TSelf : IStrongDouble<TSelf>
        => left.InternalValue > right.InternalValue;

    // 其他比较逻辑同理
}

最后,每个结构体只需要实现接口,并调用这些静态方法来实现运算符:

public readonly record struct Potential : IStrongDouble<Potential>
{
    private readonly double _internalValue;

    private Potential(double valueInVolt) => _internalValue = valueInVolt;

    public static Potential FromVolt(double valueInVolt) => new(valueInVolt);
    public double InVolt => _internalValue;

    // 运算符只需要一行代码,调用工具类
    public static Potential operator +(Potential left, Potential right) 
        => StrongDoubleOperations.Add(left, right);

    public static Potential operator -(Potential left, Potential right) 
        => StrongDoubleOperations.Subtract(left, right);

    // 其他运算符同理
    public int CompareTo(Potential other) 
        => _internalValue.CompareTo(other._internalValue);

    // 特有的类型转换逻辑
    public static implicit operator PotentialRate(Potential potential) 
        => PotentialRate.FromVoltsPerSecond(potential.InVolt / 60);
}

这种方式虽然还是要每个结构体写运算符,但每个运算符只有一行代码,大大减少了样板代码的维护量。

关于你之前尝试的接口运算符问题

你遇到的CS0563错误,本质是C#的运算符重载规则限制:二元运算符的两个参数中必须有一个是定义运算符的类型。你在接口里定义operator +(TSelf left, TSelf right)时,TSelf是泛型参数,编译器无法将它识别为“包含类型”,所以报错。就算你把参数改成IStrongDouble<TSelf>,编译器也无法自动将结构体实例匹配到这个运算符,因为C#不会隐式把结构体转成接口来匹配运算符——这就是为什么你必须强制转换才能使用,体验很差。

所以接口默认实现的方式在这个场景下确实走不通,上面的两个方案会更实用。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 09:04:32