如何解决.NET泛型电报类中同类型参数的类型转换问题?
问题解决:UInt16/UInt32泛型电报头与电报类实现
问题背景
需要实现两类支持UInt16、UInt32类型的组件:
Header<T>:表示电报头,生成对应字节数组(T为UInt16时占4字节,UInt32时占8字节)Telegram<T>:依赖Header<T>构建完整电报字节数组
在.NET Framework 4.7.2环境下,Telegram<T>构造函数将int类型的bData.Length转换为T时,直接转换会编译报错;通过object中转虽能编译,但运行时会抛出类型转换异常。同时原Header<T>的ToByteArray方法存在逻辑错误(UInt32分支错误转换为UInt16)。
一、核心问题修复:安全类型转换与泛型约束
1. 修正Header<T>的泛型约束与字节转换逻辑
首先收紧泛型约束,避免无效类型传入,同时修复UInt32分支的转换错误:
public class Header<T> where T : struct, IComparable, IFormattable, IConvertible { public T Offset { get; } public T LenData { get; } public Header(T off, T len) { var typeCode = off.GetTypeCode(); if (typeCode != TypeCode.UInt16 && typeCode != TypeCode.UInt32) { throw new NotSupportedException($"不支持类型 {typeof(T)}"); } Offset = off; LenData = len; } public byte[] ToByteArray() { int typeSize = Marshal.SizeOf(typeof(T)); byte[] bTemp = new byte[2 * typeSize]; var typeCode = Offset.GetTypeCode(); if (typeCode == TypeCode.UInt16) { ushort offset = Convert.ToUInt16(Offset); ushort len = Convert.ToUInt16(LenData); Array.ConstrainedCopy(BitConverter.GetBytes(offset), 0, bTemp, 0, typeSize); Array.ConstrainedCopy(BitConverter.GetBytes(len), 0, bTemp, typeSize, typeSize); } else if (typeCode == TypeCode.UInt32) { uint offset = Convert.ToUInt32(Offset); uint len = Convert.ToUInt32(LenData); Array.ConstrainedCopy(BitConverter.GetBytes(offset), 0, bTemp, 0, typeSize); Array.ConstrainedCopy(BitConverter.GetBytes(len), 0, bTemp, typeSize, typeSize); } else { throw new NotSupportedException($"不支持类型 {typeof(T)}"); } return bTemp; } }
2. 修复Telegram<T>的构造函数类型转换
使用Convert.ChangeType实现int到T的安全转换,同时添加范围检查避免溢出:
class Telegram<T> where T : struct, IComparable, IFormattable, IConvertible { public Header<T> Header { get; } public Telegram(T offset, byte[] bData) { if (bData == null) throw new ArgumentNullException(nameof(bData)); var typeCode = offset.GetTypeCode(); long length = bData.Length; // 检查数据长度是否在当前类型的取值范围内 switch (typeCode) { case TypeCode.UInt16: if (length > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(bData), "数据长度超出UInt16最大值"); break; case TypeCode.UInt32: if (length > uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(bData), "数据长度超出UInt32最大值"); break; default: throw new NotSupportedException($"不支持类型 {typeof(T)}"); } // 安全转换int到目标泛型类型 T len = (T)Convert.ChangeType(bData.Length, typeof(T)); Header = new Header<T>(offset, len); } }
3. 测试代码验证
static void Main(string[] args) { byte[] bData = new byte[16]; // 原有正常逻辑 Header<UInt16> ui16Header = new Header<ushort>(12345, (ushort)bData.Length); byte[] b0 = ui16Header.ToByteArray(); // 现在可正常实例化 Telegram<UInt16> telegram16 = new Telegram<ushort>(7000, bData); Telegram<UInt32> telegram32 = new Telegram<uint>(70000, bData); // 测试超出范围的场景(会抛出预期异常) // byte[] bigData = new byte[ushort.MaxValue + 1]; // Telegram<UInt16> badTelegram = new Telegram<ushort>(1000, bigData); }
二、关于继承与接口的适用性
对于当前仅支持UInt16、UInt32两种类型的场景,泛型方案更简洁高效:
- 泛型配合约束可直接限制类型范围,无需额外的类层次结构
- 继承+接口方案需要定义基类/接口,再分别实现UInt16、UInt32的子类,代码冗余度更高
- 若后续需扩展更多数值类型,泛型方案的修改成本更低,只需调整约束与转换逻辑
只有当不同类型的电报头需要完全差异化的业务逻辑时,才推荐使用继承+接口的方式拆分实现。
内容的提问来源于stack exchange,提问作者Wolfgang Roth
相关产品推荐
相关产品推荐

