我正在研究一种方法,以安全且优化的方式将 TSource 类型的数值转换为另一个数值类型 TDestination。我正在使用 .NET 9 以及 .NET 中引入的通用 INumber 和 IMinMaxValue 接口。
这是我当前的实现:
public static TDestination ToNumberOrDefault<TSource, TDestination>(this TSource number, TDestination defaultValue)
where TSource : struct, INumber<TSource>, IMinMaxValue<TSource>
where TDestination : struct, INumber<TDestination>, IMinMaxValue<TDestination>
{
try
{
return TDestination.CreateChecked(number);
}
catch
{
return defaultValue;
}
}
问题: 性能问题:CreateChecked 方法似乎在执行转换之前在内部将源值转换为对象,这会引入装箱和拆箱开销,使其不太理想。 频繁使用:此方法在性能关键的代码路径中被频繁调用,因此我正在寻找更快的替代方案。
以 Int32.cs 中的代码为例:
else if (typeof(TOther) == typeof(long))
{
long actualValue = (long)(object)value;
result = checked((int)actualValue);
return true;
}
问题: 是否有更优化的方法来执行 TSource 和 TDestination 之间的这种类型转换,同时确保值落在 TDestination 的有效范围内? 我是否可以避免使用 try/catch 块和 CreateChecked,同时仍确保该值被安全转换或在无效时回退到默认值? .NET 9 中是否有更适合此类数字转换的替代 API 或模式。
限制: TSource 和TDestination 保证实现INumber 和IMinMaxValue。 在执行转换之前,我需要确保 TSource 的值在 TDestination.MinValue 和 TDestination.MaxValue 定义的范围内。
用法示例:
var sourceValue = 12345;
var result = sourceValue.ToNumberOrDefault<int, short>(default(short)); // Should return short value or fallback to default
我尝试过的: 使用 CreateChecked:可以工作,但由于装箱/拆箱而引入性能问题。 使用 Convert.ChangeType:与装箱/拆箱和潜在开销类似的问题。 手动转换为中间类型(例如双精度或小数)以进行范围检查:增加复杂性和潜在的精度问题。
问题:性能问题:
方法似乎在执行转换之前在内部将源值转换为对象,这会引入装箱和拆箱开销,使其不太理想CreateChecked
这里不存在性能问题,因为这里不会发生分配,将为传递给泛型参数的每个不同值类型编译泛型,并且 JIT 编译器足够聪明,不会执行额外的工作(装箱、转换等)。临时猫到
object
(即在(long)(object)value;
中)实际上是使用泛型时非常常见的技巧。
您可以轻松检查您的方法是否实际上不会分配:
int i = 0;
int k = 100L.ToNumberOrDefault(1); // "warm up"
var totalAllocatedBytes = GC.GetTotalAllocatedBytes(true);
for (int j = 0; j < 10_000; j++)
{
i = 100L.ToNumberOrDefault(1);
}
Console.WriteLine(GC.GetTotalAllocatedBytes(true) - totalAllocatedBytes);
这为我打印了
0
。对于实际性能测试,我建议使用类似 BenchmarkDotNet
的东西,它具有分配诊断功能(通过 DotMemoryDiagnoser
)
或者例如通过 sharplab.io
检查 JIT 程序集