.NET 7/C#11中如何使用泛型INumber执行简单数学运算?
.NET 7/C# 11 基于INumber的泛型直方图实现问题及修复
问题背景与初始代码
我希望在.NET 7/C# 11中实现一个绑定到INumber<TValue>的泛型直方图类,编写了以下代码:
public class Histogram<TValue> where TValue : INumber<TValue> { public List<KeyValuePair<int, int>> CalcHistogram(List<TValue> Values, TValue CorrectValue, TValue BinSize) { // ... foreach (TValue value in Values) { // 版本1:手动计算分箱编号 TValue bn = (value - CorrectValue) / BinSize; int binNumber = bn < 0 ? (int)(bn - 0.5) : (int)(bn + (TValue)0.5); // 版本2:使用Math.Round计算分箱编号 int binNumber2 = (int)Math.Round((value - CorrectValue) / BinSize, MidpointRounding.AwayFromZero); // .... } return new List<KeyValuePair<int, int>>(); } }
编译错误列表
- 错误CS0019:
bn < 0无法将运算符 '<' 应用于TValue和int类型的操作数。原以为IComparable支持该操作,将0转换为TValue也无法解决(见错误3)。 - 错误CS0019:
bn - 0.5无法将运算符 '-' 应用于TValue和double类型的操作数,原以为可以直接对两个“数字”执行减法。 - 错误CS0030:
(TValue)0.5无法将类型double转换为TValue,原以为double实现了INumber就可以直接转换。 - 错误CS0019:
binNumber * BinSize无法将运算符 '*' 应用于int和TValue类型的操作数。 - 错误CS1503:
Math.Round(...)参数1无法从TValue转换为decimal,参数2无法从MidpointRounding转换为int。
核心疑问:能否像使用float或double一样,对泛型INumber<TValue>执行运算、转换、比较等操作?
解决方案
可以像操作基础数值类型一样处理INumber<TValue>,但需要使用INumber<TValue>提供的泛型静态API,而非直接使用基础类型的语法。以下是针对每个错误的修复思路及完整修正代码:
修复错误1:泛型类型与零值的比较
不能直接将TValue与int类型的0比较,需使用TValue.Zero获取泛型类型的零值,INumber<TValue>继承了IComparable<TValue>,支持直接比较:
bool isNegative = bn < TValue.Zero;
修复错误2&3:泛型类型与常量的运算
不能直接将TValue与double常量0.5运算,需通过TValue.CreateChecked(0.5)或TValue.CreateTruncating(0.5)将0.5转换为TValue类型,同时用int.CreateChecked替代强制转换避免溢出风险:
var half = TValue.CreateChecked(0.5); int binNumber = bn < TValue.Zero ? int.CreateChecked(bn - half) : int.CreateChecked(bn + half);
修复错误4:int与TValue的乘法运算
需先将int类型的binNumber转换为TValue类型,再执行乘法:
TValue binOffset = TValue.CreateChecked(binNumber) * BinSize;
修复错误5:泛型数值的舍入操作
Math.Round不支持TValue类型,可使用INumber<TValue>提供的泛型舍入方法TValue.Round,或先将TValue转换为double类型再调用Math.Round:
// 方案1:推荐使用泛型舍入方法 TValue roundedBn = TValue.Round(bn, MidpointRounding.AwayFromZero); int binNumber2 = int.CreateChecked(roundedBn); // 方案2:转换为double后舍入(适合浮点类型的TValue) double bnDouble = double.CreateChecked(bn); int binNumber2 = (int)Math.Round(bnDouble, MidpointRounding.AwayFromZero);
修正后的完整代码
public class Histogram<TValue> where TValue : INumber<TValue> { public List<KeyValuePair<int, int>> CalcHistogram(List<TValue> values, TValue correctValue, TValue binSize) { var histogramCounts = new Dictionary<int, int>(); var half = TValue.CreateChecked(0.5); foreach (TValue value in values) { TValue binRatio = (value - correctValue) / binSize; // 手动舍入实现 int binNumber = binRatio < TValue.Zero ? int.CreateChecked(binRatio - half) : int.CreateChecked(binRatio + half); // 泛型舍入方法实现 TValue roundedRatio = TValue.Round(binRatio, MidpointRounding.AwayFromZero); int binNumber2 = int.CreateChecked(roundedRatio); // 更新直方图计数(以binNumber为例) if (histogramCounts.ContainsKey(binNumber)) histogramCounts[binNumber]++; else histogramCounts[binNumber] = 1; } return histogramCounts.OrderBy(kvp => kvp.Key).ToList(); } }
使用INumber的关键要点
- 用
TValue.Zero、TValue.One等静态属性替代基础类型的常量值 - 用
TValue.CreateChecked/TValue.CreateTruncating实现安全的类型转换,避免强制转换的风险 - 优先使用
TValue的静态方法(如TValue.Round、TValue.Add)替代Math类的非泛型方法 - 所有运算的操作数必须为相同的
TValue类型,需先将基础类型转换为TValue再参与运算
内容的提问来源于stack exchange,提问作者Qrt
相关产品推荐
相关产品推荐

