如何避免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
相关产品推荐
相关产品推荐

