只接受数字类型的通用(int double等)?

时间:2011-01-06 22:10:22

标签: c#

在我正在编写的程序中,我需要编写一个函数来获取任何数字类型(int,short,long等)并将其移入特定偏移量的字节数组。

存在一个Bitconverter.GetBytes()方法,该方法接受数字类型并将其作为字节数组返回,此方法仅采用数字类型。

到目前为止,我有:

    private void AddToByteArray<T>(byte[] destination, int offset, T toAdd) where T : struct
    {
        Buffer.BlockCopy(BitConverter.GetBytes(toAdd), 0, destination, offset, sizeof(toAdd));
    }

所以基本上我的目标是,例如,对AddToByteArray(数组,3,(短)10)的调用需要10并将其存储在数组的第4个插槽中。显式转换是存在的,因为我确切地知道我想要它占用多少字节。在某些情况下,我希望一个足够小的数字能够真正占用4个字节。另一方面,有时我想要将int压缩到一个字节。我这样做是为了创建一个自定义的网络数据包,如果这会让任何想法涌入你的脑袋。

如果泛型的where子句支持“where T:int || long || etc”之类的东西,我会好的。 (并且无需解释为什么他们不支持,原因相当明显)

非常感谢任何帮助!

编辑:我意识到我可以做一堆重载,每个类型一个我想支持...但是我问这个问题因为我想要避免这个:)

6 个答案:

答案 0 :(得分:3)

我不同意这不可能做到;只是我建议的设计有点奇怪(并且参与其中)。

这是个主意。

想法

使用一种方法定义接口IBytesProvider<T>

public interface IBytesProvider<T>
{
    byte[] GetBytes(T value);
}

然后在具有静态BytesProvider<T>属性的Default类中实现此功能。

如果这听起来很熟悉,那是因为它正是EqualityComparer<T>Comparer<T>类的工作原理(在大量LINQ扩展方法中大量使用)。

实施

以下是我建议您进行设置的方法。

public class BytesProvider<T> : IBytesProvider<T>
{
    public static BytesProvider<T> Default
    {
        get { return DefaultBytesProviders.GetDefaultProvider<T>(); }
    }

    Func<T, byte[]> _conversion;

    internal BytesProvider(Func<T, byte[]> conversion)
    {
        _conversion = conversion;
    }

    public byte[] GetBytes(T value)
    {
        return _conversion(value);
    }
}

static class DefaultBytesProviders
{
    static Dictionary<Type, object> _providers;

    static DefaultBytesProviders()
    {
        // Here are a couple for illustration. Yes, I am suggesting that
        // in reality you would add a BytesProvider<T> for each T
        // supported by the BitConverter class.
        _providers = new Dictionary<Type, object>
        {
            { typeof(int), new BytesProvider<int>(BitConverter.GetBytes) },
            { typeof(long), new BytesProvider<long>(BitConverter.GetBytes) }
        };
    }

    public static BytesProvider<T> GetDefaultProvider<T>()
    {
        return (BytesProvider<T>)_providers[typeof(T)];
    }
}

支付

现在,终于,一旦你完成了这一切,你所做的就是致电:

byte[] bytes = BytesProvider<T>.Default.GetBytes(toAdd);

不需要重载。

答案 1 :(得分:2)

您可以通过首先将方法分为两部分来完成此操作,一部分将值转换为字节数组,另一部分插入它们。然后只使用重载:

        public static void AddToByteArray(byte[] destination, int offset, long value)
        { InsertBytes(destination, offset, BitConverter.GetBytes(value)); }

        public static void AddToByteArray(byte[] destination, int offset, int value)
        { InsertBytes(destination, offset, BitConverter.GetBytes(value)); }

        public static void AddToByteArray(byte[] destination, int offset, short value)
        { InsertBytes(destination, offset, BitConverter.GetBytes(value)); }

        private static void InsertBytes(byte[] destination, int offset, byte[] bytes)
        {
            Buffer.BlockCopy(bytes, 0, destination, offset, bytes.Length);
        }

答案 2 :(得分:1)

无论如何这都行不通,因为要使用的BitConverter.GetBytes()的重载在编译时而不是在运行时解决,所以传递为T的泛型参数不会用于帮助确定GetBytes()重载。由于没有接受object的重载,因此即使可以约束T某些特定类型的类型,此方法也无法正常工作。所以你在这里倍加紧张。

这里唯一真正的选择是为您要接受的每种数字类型重载AddToByteArray方法。我知道你不想这样做,但你无能为力。 (您可以接受object的参数,并使用反射根据参数类型调用GetBytes()的特定重载,但由于反射和装箱开销,这将是狗慢...)

答案 3 :(得分:1)

作为Dan Tao解决方案的简化,结合SLaks的建议,这是一个完整的通用BitConverter:

public class BitConverter<T>
{
    public static readonly Func<T, byte[]> GetBytes = x => new byte[] { };

    static BitConverter()
    {
        BitConverter<byte>.GetBytes = x => new byte[] { x };
        BitConverter<bool>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<char>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<double>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<Int16>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<Int32>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<Int64>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<Single>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<UInt16>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<UInt32>.GetBytes = x => BitConverter.GetBytes(x);
        BitConverter<UInt64>.GetBytes = x => BitConverter.GetBytes(x);
    }
}

您的示例将如下所示:

private void AddToByteArray<T>(byte[] destination, int offset, T toAdd) where T : struct
{
    Buffer.BlockCopy(BitConverter<T>.GetBytes(toAdd), 0, destination, offset, sizeof(toAdd));
}

您只需要为静态构造函数的每个预期类型提供GetBytes()方法。顺便说一下,这不仅限于结构类型,例如:

BitConverter<MemoryStream>.GetBytes = x => x.ToArray();
BitConverter<string>.GetBytes = x => Encoding.Default.GetBytes(x);

如果泛型类型没有GetBytes()方法,它将返回一个空数组(但您可能希望更改代码以引发异常!)。

答案 4 :(得分:0)

这是C#中的一个真正的问题,没有所有数字类型实现的通用接口,您可以限制为structnew(),但仍然允许使用无参数构造函数的结构。如果你真的想限制它,你可能不得不为所有数字类型使用定义的重载。

答案 5 :(得分:0)

如果您只关心在小端系统(如Windows)上运行,您可以在IConvertible上添加约束(我相信所有数字类型都支持),使用它将值转换为64位整数,获取该字节,然后丢弃您不需要的字节。像这样:

private byte[] NumberToBytes<T>(T value)
    where T : new(), struct, IConvertible
{
    var longValue = value.ToUInt64();
    var bytes = BitConverter.GetBytes(longValue);
    Array.Resize(ref bytes, sizeof(T));
    return bytes;
}

当然,这假设您只使用整数 - 它不适用于float,double或decimal。如上所述,它只适用于小端系统;对于big-endian,你需要保留数组中的最后sizeof(T)个元素,而不是第一个。