c# getresponsestream返回byte[]_从 ReadInt32 开始的奇妙旅程(C#)

用 BinaryReader/BinaryWriter 时,由于它们不支持泛型参数,必须明确调用指定版本的方法才能工作(比如 ReadInt32),这用起来就很麻烦。如果可以支持泛型参数(比如 Read<int>),就可以自动量产代码了。所以,我们来为 BinaryReader/BinaryWriter 做个泛型扩展吧!

if-else 版本

从最简单的方法开始。早先 C# 的 switch-case 不支持类型匹配,只能使用常量,这种情况下使用 if-else 确定泛型参数类型是很直接的想法。

public static T Read<T>(this BinaryReader reader)
{
    var type = typeof(T);
    if (type == typeof(int)) return (T)(object)reader.ReadInt32();
    if (type == typeof(float)) return (T)(object)reader.ReadSingle();
    // ...
    throw new Exception($"Type {type.FullName} is not supported.");
}

你可能注意到从 int 到 T 的转换必须通过装箱中介才能完成,这不是一个好办法。其实你可以使用一个显式布局的结构体来解决这个问题。(更正:似乎不行!)

[StructLayout(LayoutKind.Explicit)]
struct GenericUnion<TKnown, TResult>
{
    [FieldOffset(0)]
    public TKnown known;

    [FieldOffset(0)]
    public TResult result;
}


public static T Read1<T>(this BinaryReader reader)
{
    var type = typeof(T);
    if (type == typeof(int)) return new GenericUnion<int, T> { known = reader.ReadInt32() }.result;
    if (type == typeof(float)) return new GenericUnion<float, T> { known = reader.ReadSingle() }.result;
    // ...
    throw new Exception($"Type {type.FullName} is not supported.");
}

switch-case TypeCode 版本

当然,我们不喜欢 if-else,又慢又丑。其实 C# 已经支持对基本类型的 switch-case,就是通过 TypeCode 实现。

public static T Read<T>(this BinaryReader reader)
{
    switch (Type.GetTypeCode(typeof(T)))
    {
        case TypeCode.Int32: return (T)(object)reader.ReadInt32();
        case TypeCode.Single: return (T)(object)reader.ReadSingle();
        // ...
        default: throw new Exception($"Type {typeof(T).FullName} is not supported.");
    }
}

这里也存在返回值类型转换问题,你可以使用和之前一样的解决方案来处理类型转换,下面不再赘述。

switch-case Name 版本

其实也可以使用类型名来做 switch-case(更安全的做法是使用 FullName)。

public static T Read<T>(this BinaryReader reader)
{
    switch (typeof(T).Name)
    {
        case "Int32": return (T)(object)reader.ReadInt32();
        case "Single": return (T)(object)reader.ReadSingle();
        // ...
        default: throw new Exception($"Type {typeof(T).FullName} is not supported.");
    }
}

如果使用 C# 6,可以使用 nameof 来获取类型名以避免手写类型名。

switch-case Type 版本

从 C# 7 开始,你可以直接使用类型来进行 switch-case。但可惜的是,这个特性不适用于我们要解决问题。

public static T Read<T>(this BinaryReader reader)
{
    T result;
    switch (default(T))
    {
        case int _: { if (reader.ReadInt32() is T value) result = value; else result = default; break; }
        case float _: { if (reader.ReadSingle() is T value) result = value; else result = default; break; }
        // ...
        default: throw new Exception($"Type {typeof(T).FullName} is not supported.");
    }
    return result;
}

首先 switch 的输入只接受变量而不接受类型,我们需要构造一个没有意义的 default(T);然后,对于 string 需要进行特殊处理,因为 string 是引用类型,默认值是 null,会和 case null 匹配。在上面的示例代码中,case int _ 这里有一个下划线,代表一个没有意义的变量名,因为我们不关心它的值,只关心它的类型。

另外,上面的代码在处理返回值类型转换时使用了新的 is 类型匹配,这也是 C# 7 的新特性。仍然非常可惜,不是非常适合我们要解决的问题。新的 is 类型匹配支持将一种类型的变量直接转换为另一种类型的变量,但这个变量仅在转换成功时才能访问。所以,尽管我们已经确认转换一定会成功,还是需要使用额外的 if-else 来确保编译可以成功。(其中 else 后面的代码永远不会执行,只是为了告诉编译器 result 一定会被初始化)。

超级类版本

在以上谈到的方法中,最后都需要一个毫无必要的类型转换,并且还没有一个非常优雅的转换方法。为此,我们可以引入泛型接口来解决这个问题。

interface BinaryReadable<T>
{
    T Read(BinaryReader reader);
}

class BinaryReadable : BinaryReadable<int>, BinaryReadable<float> // ...
{
    int BinaryReadable<int>.Read(BinaryReader reader)
    {
        return reader.ReadInt32();
    }

    float BinaryReadable<float>.Read(BinaryReader reader)
    {
        return reader.ReadSingle();
    }

    // ...
}

static BinaryReadable _binaryReadable = new BinaryReadable();

public static T Read<T>(this BinaryReader reader)
{
    return ((BinaryReadable<T>)_binaryReadable).Read(reader);
}

在这个解决方案中,我们通过一个实现超多接口的类——我称之为超级类,来实现各种数据类型的读写;在使用时,将这个类的对象转化为匹配类型的接口来调用。最终,我们直接返回了 T 类型的结果,而无需进行额外的转换。

泛型生成版本

群友还提供了这样一个解决方案:

public static class ReaderWriterExtensions
{
    struct BinaryReadable<T>
    {
        internal static Func<BinaryReader, T> read;
    }

    static ReaderWriterExtensions()
    {
        BinaryReadable<int>.read = r => r.ReadInt32();
        BinaryReadable<float>.read = r => r.ReadSingle();
        // ...
    }

    public static T Read<T>(this BinaryReader reader)
    {
        return BinaryReadable<T>.read(reader);
    }
}

个人认为这个方法也很巧妙,利用了泛型的自动生成类型特性。一般情况下我们认为类的静态成员是全局的、唯一的,但泛型类中静态成员原本并不存在,直到类型参数被指定时才会生成一个特定的类型,这个类型拥有全部独有的静态成员。因此,在上面的代码中,通过传递不同的类型参数生成了不同的类型,同时也生成了不同的 read 实例。而且这个方法从理论上讲,支持运行时注册新类型。你在想什么?你可以将自己定义的类型的读写方法注册进去,然后可以像其他类型一样调用泛型版本的读写!

结语

目前来讲,我最喜欢的解决方案是最后一个。但本质上这个问题还是源于 C# 的局限性。如果你还有其他解决方案,不妨在评论里分享出来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值