如何在C#中模拟Java泛型通配符?附Java泛型类平均比较代码问题
解决C#中模拟Java泛型通配符的问题
先把你提供的Java代码清晰列出来,方便理解场景:
class Generic_Class<T extends Number> { T[] nums; // Number或其子类的数组 Generic_Class(T[] o) { nums = o; } // 所有情况下返回类型均为double double average() { double sum = 0.0; for(int i=0; i < nums.length; i++) sum += nums[i].doubleValue(); return sum / nums.length; } // 尝试定义方法boolean sameAvg(Generic_Class<T> ob)时,使用Generic_Class<T>会报错: // 类型不兼容... }
这个问题的核心是:Java里如果想让sameAvg方法接受任意数值类型的Generic_Class实例(比如Generic_Class<Integer>和Generic_Class<Double>互相调用),需要用通配符Generic_Class<? extends Number>,但C#没有直接的通配符语法,咱们可以用以下几种方式实现类似效果:
方案1:利用协变接口(推荐C# 8+)
C#的协变特性(通过out关键字标记泛型参数)可以实现类似Java? extends的效果。我们先定义一个协变接口,用来暴露平均值计算的方法,然后让泛型类实现这个接口:
// 定义协变接口,out表示这个泛型参数是协变的 public interface ICalculatableAverage<out T> where T : struct, INumber<T> { double Average(); } public class GenericClass<T> : ICalculatableAverage<T> where T : struct, INumber<T> { private T[] _numbers; public GenericClass(T[] numbers) { _numbers = numbers; } public double Average() { double total = 0.0; foreach (var num in _numbers) { total += double.CreateChecked(num); // 安全转换为double } return total / _numbers.Length; } // 现在可以接受任何实现ICalculatableAverage的数值类型实例 public bool SameAvg(ICalculatableAverage<INumber<INumber<T>>> other) { // 用极小值判断浮点数相等,避免精度问题 return Math.Abs(this.Average() - other.Average()) < 1e-9; } // 更通用的版本,直接兼容所有数值类型 public bool SameAvg<TOther>(ICalculatableAverage<TOther> other) where TOther : struct, INumber<TOther> { return Math.Abs(this.Average() - other.Average()) < 1e-9; } }
调用示例:
var intList = new GenericClass<int>(new[] { 1, 2, 3 }); var doubleList = new GenericClass<double>(new[] { 1.0, 2.0, 3.0 }); Console.WriteLine(intList.SameAvg(doubleList)); // 输出True
方案2:直接使用泛型方法
如果不想额外定义接口,也可以在泛型类里写一个泛型方法,让它接受任意数值类型的GenericClass实例:
public class GenericClass<T> where T : struct, INumber<T> { private T[] _numbers; public GenericClass(T[] numbers) { _numbers = numbers; } public double Average() { double total = 0.0; foreach (var num in _numbers) { total += double.CreateChecked(num); } return total / _numbers.Length; } // 泛型方法,约束TOther为数值类型 public bool SameAvg<TOther>(GenericClass<TOther> other) where TOther : struct, INumber<TOther> { return Math.Abs(this.Average() - other.Average()) < 1e-9; } }
这种方式更简洁,直接通过泛型参数TOther兼容所有数值类型的泛型实例,调用起来和方案1一样顺畅。
方案3:兼容旧版C#(无INumber接口)
如果你的项目用的是C# 7及更早版本,没有INumber泛型接口,可以用IConvertible来约束类型,手动转换为double:
public class GenericClass<T> where T : struct, IConvertible { private T[] _numbers; public GenericClass(T[] numbers) { _numbers = numbers; } public double Average() { double total = 0.0; foreach (var num in _numbers) { total += num.ToDouble(null); } return total / _numbers.Length; } public bool SameAvg<TOther>(GenericClass<TOther> other) where TOther : struct, IConvertible { return Math.Abs(this.Average() - other.Average()) < 1e-9; } }
核心思路总结
Java的? extends Number通配符本质是允许“向上转型”,接受任何Number子类的泛型实例。在C#里,我们要么用协变接口实现这种向上兼容性,要么用泛型方法直接兼容不同的数值类型参数,两种方式都能解决你遇到的类型不兼容问题。
内容的提问来源于stack exchange,提问作者user9559069
相关产品推荐
相关产品推荐

