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

如何避免C#强类型double结构体的重复运算符重载实现?

强类型double结构体的重复代码复用方案咨询

我们在大型测量处理应用的代码库中,曾因统一使用double类型导致值混淆的bug,因此创建了Potential、PotentialRate、Duration等约30个强类型结构体。这些结构体需要支持运算,但运算符重载逻辑完全重复,产生了大量违反DRY原则的样板代码。

已尝试的方案及问题

  • 无法使用源代码生成器(因维护难度大已弃用)
  • 试过带默认实现的泛型接口IStrongDouble<TSelf>复用代码,但碰到CS0563编译错误;调整运算符参数为接口类型后,运算符无法被正常识别;尝试隐式转换到接口又受C#限制失败
  • 泛型参数化类型方案不适用:一是各类型有特定的创建和读取方式,二是部分类型存在跨类型转换的特定运算符重载

示例代码

强类型结构体示例

public readonly record struct Potential
{
    private Potential(double valueInVolt)
    {
        _internalValue = valueInVolt;
    }

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

    public double InVolt => _internalValue;

    // [...更多运算符重载实现...]

    private readonly double _internalValue;
}

重复的运算符重载实现

public static [Type] operator +([Type] left, [Type] right) => new(left._internalValue + right._internalValue);
public static [Type] operator -([Type] left, [Type] right) => new(left._internalValue - right._internalValue);
public static [Type] operator *([Type] value, double factor) => new(value._internalValue * factor);
public static double operator /([Type] left, [Type] right) => left._internalValue / right._internalValue;
public static bool operator >([Type] left, [Type] right) => left._internalValue > right._internalValue;
public static bool operator >=([Type] left, [Type] right) => left._internalValue >= right._internalValue;
public static bool operator <([Type] left, [Type] right) => left._internalValue < right._internalValue;
public static bool operator <=([Type] left, [Type] right) => left._internalValue <= right._internalValue;
public int CompareTo([Type] other) => _internalValue.CompareTo(other._internalValue);
// 等等

跨类型转换运算符示例

public static Duration operator /(Current left, CurrentRate right) =>
    Duration.FromSeconds(left._internalValue / right._internalValue);

可行的复用方案

1. C# 11+静态抽象接口成员(最优解)

C# 11及以上支持静态抽象接口成员,能直接在接口中定义运算符的默认实现,彻底解决之前的编译问题,且无额外开销:

public interface IStrongDouble<TSelf> where TSelf : struct, IStrongDouble<TSelf>
{
    double InternalValue { get; }
    // 要求每个结构体实现创建实例的方法
    static abstract TSelf Create(double value);

    // 通用运算符的默认实现
    public static TSelf operator +(TSelf left, TSelf right)
    {
        return TSelf.Create(left.InternalValue + right.InternalValue);
    }

    public static TSelf operator -(TSelf left, TSelf right)
    {
        return TSelf.Create(left.InternalValue - right.InternalValue);
    }

    public static TSelf operator *(TSelf value, double factor)
    {
        return TSelf.Create(value.InternalValue * factor);
    }

    public static double operator /(TSelf left, TSelf right)
    {
        return left.InternalValue / right.InternalValue;
    }

    public static bool operator >(TSelf left, TSelf right)
    {
        return left.InternalValue > right.InternalValue;
    }

    public static bool operator >=(TSelf left, TSelf right)
    {
        return left.InternalValue >= right.InternalValue;
    }

    public int CompareTo(TSelf other)
    {
        return InternalValue.CompareTo(other.InternalValue);
    }
}

// 结构体只需实现接口要求的成员,自定义跨类型运算符单独写
public readonly record struct Potential : IStrongDouble<Potential>
{
    private Potential(double valueInVolt) => _internalValue = valueInVolt;
    public static Potential FromVolt(double valueInVolt) => new(valueInVolt);
    public double InVolt => _internalValue;
    // 显式实现接口的InternalValue,避免暴露内部细节
    double IStrongDouble<Potential>.InternalValue => _internalValue;
    // 实现接口要求的Create方法
    public static Potential Create(double value) => new(value);
    private readonly double _internalValue;

    // 自定义跨类型运算符写在这里
    // ...
}

2. 接口约束+扩展方法(兼容旧C#版本)

如果不能升级C#版本,可通过内部接口暴露内部值,结合扩展方法实现通用逻辑:

// 内部接口,仅用于访问内部值和创建实例
internal interface IStrongDouble<T> where T : struct, IStrongDouble<T>
{
    double InternalValue { get; }
    static abstract T Create(double value);
}

// 结构体实现接口
public readonly record struct Potential : IStrongDouble<Potential>
{
    private Potential(double valueInVolt) => _internalValue = valueInVolt;
    public static Potential FromVolt(double valueInVolt) => new(valueInVolt);
    public double InVolt => _internalValue;
    double IStrongDouble<Potential>.InternalValue => _internalValue;
    public static Potential Create(double value) => new(value);
    private readonly double _internalValue;
}

// 扩展方法类封装通用运算逻辑
public static class StrongDoubleOperators
{
    public static T Add<T>(this T left, T right) where T : struct, IStrongDouble<T>
    {
        return T.Create(left.InternalValue + right.InternalValue);
    }

    public static T Subtract<T>(this T left, T right) where T : struct, IStrongDouble<T>
    {
        return T.Create(left.InternalValue - right.InternalValue);
    }

    public static T MultiplyByFactor<T>(this T value, double factor) where T : struct, IStrongDouble<T>
    {
        return T.Create(value.InternalValue * factor);
    }

    public static double Divide<T>(this T left, T right) where T : struct, IStrongDouble<T>
    {
        return left.InternalValue / right.InternalValue;
    }

    public static bool IsGreaterThan<T>(this T left, T right) where T : struct, IStrongDouble<T>
    {
        return left.InternalValue > right.InternalValue;
    }
}

这种方法用扩展方法替代运算符重载,虽然不能直接用+、-等符号,但能大幅减少重复代码,且无反射开销。

3. IDE代码片段模板

如果以上方案都不适用,可利用Visual Studio或Rider的代码片段功能,把重复的运算符重载逻辑做成模板,创建结构体时一键插入,减少手动编写的工作量。虽然没从代码层面复用,但能降低维护成本。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 21:19:53