byte[]、sbyte[]、int[]以及Array的故事

数组转换与复制
本文探讨了.NET中byte[]、sbyte[]及int[]等数组类型的转换规则与Array.Copy方法的行为特性,包括不同数组间的类型转换及复制时的类型检查。

byte[]、sbyte[]、int[]以及Array的故事

很久没有搞比较底层一点的东西了,最近又开始搞,于是乎又发现了一些很鸡毛蒜皮的事情。也许有人已经发现过了,那就请原谅我就再来挖掘一遍。

 

byte[]、sbyte[]、int[]等数组,是一种特殊的类型,他们都继承自Array。不过这个继承还不是一般的继承关系,编译器和CLI都做了一些特殊的工作。我们先看普通的继承关系:

class Human
{
}

class Man : Human
{
}

class Woman : Human
{
}

 

如果我们试图写下列的代码:

Human human = new Man();
Woman woman = (Woman)human;
 

那么结果是什么,你懂的。

 

好吧,你不懂。那我给说一下,Man和Woman都是从Human这个类继承的,因此Man和Woman是兄妹关系,兄妹之间实际上是不能互相转换的。虽然上面的代码是可以编译通过的,但是运行起来就会抛异常,告诉你Man类型的对象是不能转换成Woman的。

 

不过换到byte[]、sbyte[]等数组里面,就不是这么简单了。比如说:

byte[] source = {1, 2, 3};
sbyte[] target = (sbyte[])(Array)source;
foreach(var item in target)
{
    Console.WriteLine(item);
}

 

你猜怎么着?哎?为啥居然能跑呢?按道理byte[]和sbyte[]都是从Array派生的,那么他们之间自然也是兄妹关系了。看来CLI为我们做了一些事情。好了,那么当我们有这么一个函数:

static public void Copy(sbyte[] source, sbyte[] target)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (target == null)
    {
        throw new ArgumentNullException("target");
    }
    int copyLength = Math.Min(source.Length, target.Length);
    Array.Copy(source, target, copyLength);
}

 

然后这么调用:

sbyte[] target = GetTarget();
sbyte[] another = new sbyte[3];
Copy(target, another);

 

在Copy函数内会出现除了ArgumentNullException之外的其它异常吗?这个怎么看也是不能吧?错了,可能会出现InvalidCastException异常,假如GetTarget的代码是:

 

sbyte[] GetTarget()
{
    return (sbyte[])(Array)new byte[] {1, 2, 3};
}
 

 

 

这就奇怪了,为啥咧?因为Array.Copy会判断要复制的数组,其元素类型之间是什么关系。这个还不是简单的相等关系,说这个之前,我们再看另一个古怪的例子:

byte[] source = { 1, 2, 3 };
int[] intArray = (int[])(Array)source;
 

这个照样还是能够编译的,不过运行时会抛出来InvalidCastException。飞鸽传书:http://www.freeeim.com/,为什么sbyte[]和byte[]之间可以互相转换,同样是兄妹关系的byte[]和int[]就不能互相转换呢?其实,这个问题不需要深究都会知道,sbyte和byte[]的元素所占字节数都一样,所以理论上是可以直接转换来访问的。而int[]和byte[]之间,如果因为其元素一个是4字节另一个是1字节,而导致不允许直接进行转换,那也很正常。不过,既然sbyte[]和byte[]之间都可以强制转换,而且转换之后都可以访问元素,为啥就不能通过Array.Copy来复制呢?嗯,不用想,Array.Copy对于源数组和目标数组的元素类型进行了判断,至于咋判断的Reflector看不到,我们就先猜测吧:Array.Copy只要发现源和目标数组的元素类型不一样,那就不能够复制。真是这样吗?我们再看另一个例子:

byte[] source = {1, 2, 3};
int[] intArray = new int[source.Length];
Array.Copy(source, intArray, source.Length);
foreach(var item in intArray)
{
    Console.WriteLine(item);
}
 

 

怎么样,这个代码你觉得跑起来会如何?抛InvalidCastException?ArrayTypeMismatchException?其实什么都不会抛出来。原来,Array.Copy检验的条件是,如果源和目标数组的元素类型是内置的值类型,只要能做宽转换(widening conversion)那就可以成功复制。比如byte->int,反之就会抛异常。而用户自定义的值类型就没有这个优惠政策了,哪怕你重写了类型转换操作符,或者实现了IConvertible,都不行。当然了,实际上Array.Copy的判断比这里写的复杂,具体还是参考MSDN吧。

 

byte[]、sbyte[]之间的强制转换和Array.Copy的问题,如果分别独立的看,可能没有什么问题。但如果我们合起来看,就会出现一些意想不到的状况。比如说,你写了一个函数:

代码
        /*
         * 将第index位的元素修改为value值,然后将整个数组的元素复制一遍。
         * 比如原来是{1,2,3},调用SomeBusiness(source, 8, 1)完成后就变成
         * {1,8,3,1,8,3}
         */
        static public sbyte[] SomeBusiness(sbyte[] source, sbyte value, int index)
        {
            source[index] = value; // 你看,这个地方可以用的,所以下面一句看起来也应该可以。
            return (sbyte[])(Array)Duplicate((byte[])(Array)source);
        }

        static public byte[] Duplicate(byte[] source)
        {
            var result = source.ToList(); // 竟然出现ArrayTypeMismatchException?百思不得其解。
            result.AddRange(result);
            return result.ToArray();
        }

 

尤其是SomeBusiness和Duplicate不是同一个人开发的时候。

解释代码using System; namespace Basic.Net.Sockets { public class ByteBuffer { //数组的最大长度 public const int MIN_BUFF_LENGTH = 8196; //public const int MAX_LENGTH = 1024000; //固定长度的中间数组 protected byte[] _tempBytes = new byte[MIN_BUFF_LENGTH]; //当前数组长度 protected int _length = 0; //当前Pop指针位置 protected int _position = 0; public ByteBuffer() { _tempBytes.Initialize(); _length = 0; _position = 0; } public int length { get { return _length; } } public int position { get { return _position; } set { _position = value; } } public byte[] ToByteArray() { //分配大小 byte[] bytes = new byte[_length]; //调整指针 Array.Copy(_tempBytes, 0, bytes, 0, _length); return bytes; } public void PushByte(byte by) { _CheckBuffer(1); _tempBytes[_length++] = by; } public void PushSByte(sbyte by) { PushByte((byte)by); } public void PushByteArray(byte[] sourceBytes) { _PushByteArray(sourceBytes, 0, sourceBytes.Length); } public void PushUShort(ushort Num) { _CheckBuffer(2); _tempBytes[_length++] = (byte)(((Num & 0xff00) >> 0x08) & 0xff); _tempBytes[_length++] = (byte)((Num & 0x00ff) & 0xff); } public void PushShort(short Num) { PushUShort((ushort)Num); } public void PushUInt(uint Num) { _CheckBuffer(4); _tempBytes[_length++] = (byte)(((Num & 0xff000000) >> 0x18) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x00ff0000) >> 0x10) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x0000ff00) >> 0x08) & 0xff); _tempBytes[_length++] = (byte)((Num & 0x000000ff) & 0xff); } public void PushSingle(float Num) { _CheckBuffer(4); byte[] bytes = BitConverter.GetBytes(Num); _tempBytes[_length++] = bytes[0]; _tempBytes[_length++] = bytes[1]; _tempBytes[_length++] = bytes[2]; _tempBytes[_length++] = bytes[3]; } public void PushInt(int Num) { PushUInt((uint)Num); } public void PushULong(ulong Num) { _CheckBuffer(8); _tempBytes[_length++] = (byte)(((Num & 0xff00000000000000) >> 0x38) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x00ff000000000000) >> 0x30) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x0000ff0000000000) >> 0x28) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x000000ff00000000) >> 0x20) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x00000000ff000000) >> 0x18) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x0000000000ff0000) >> 0x10) & 0xff); _tempBytes[_length++] = (byte)(((Num & 0x000000000000ff00) >> 0x08) & 0xff); _tempBytes[_length++] = (byte)((Num & 0x00000000000000ff) & 0xff); } public void PushLong(long Num) { PushULong((ulong)Num); } public void PushUTF(string value) { byte[] bytes = System.Text.Encoding.UTF8.GetBytes(value); PushShort((short)bytes.Length); PushByteArray(bytes); } public byte PopByte() { byte ret = _tempBytes[_position++]; return ret; } public sbyte PopSByte() { return (sbyte)PopByte(); } public ushort PopUShort() { //溢出 if (_position + 2 > _length) { return 0; } ushort ret = (ushort)(_tempBytes[_position] << 0x08 | _tempBytes[_position + 1]); _position += 2; return ret; } public short PopShort() { return (short)PopUShort(); } public uint PopUInt() { if (_position + 4 > _length) return 0; uint ret = (uint)(_tempBytes[_position] << 0x18 | _tempBytes[_position + 1] << 0x10 | _tempBytes[_position + 2] << 0x08 | _tempBytes[_position + 3]); _position += 4; return ret; } public float PopSingle() { //溢出 if (_position + 4 > _length) { return 0.0f; } float result = BitConverter.ToSingle(_tempBytes, _position); _position += 4; return result; } public int PopInt() { return (int)PopUInt(); } public ulong PopULong() { if (_position + 8 > _length) return 0; ulong ret = (ulong)((uint)(_tempBytes[_position] << 0x18 | _tempBytes[_position + 1] << 0x10 | _tempBytes[_position + 2] << 0x08 | _tempBytes[_position + 3]) * 0x100000000) + (uint)(_tempBytes[_position + 4] << 0x18 | _tempBytes[_position + 5] << 0x10 | _tempBytes[_position + 6] << 0x08 | _tempBytes[_position + 7]); _position += 8; return ret; } public long PopLong() { return (long)PopULong(); } public string PopUTF() { short len = PopShort(); return System.Text.Encoding.UTF8.GetString(PopByteArray(len)); } public byte[] PopByteArray(int Length) { //溢出 if (_position + Length > _length) { return new byte[0]; } byte[] ret = new byte[Length]; _BlockCopy(_tempBytes, _position, ret, 0, Length); //提升位置 _position += Length; return ret; } public byte Get(int index) { return _tempBytes[index]; } public void Set(int index, byte value) { _tempBytes[index] = value; } public byte[] GetByteArray(int index, int length) { //溢出 if (index + length > _length) { return new byte[0]; } byte[] ret = new byte[length]; _BlockCopy(_tempBytes, index, ret, 0, length); return ret; } protected void _PushByteArray(byte[] sourceBytes, int sourceIndex, int len) { _CheckBuffer(len); _BlockCopy(sourceBytes, sourceIndex, _tempBytes, _length, len); _length += len; } protected void _BlockCopy(byte[] src, int srcOffset, byte[] dst, int dstOffset, int len) { if (len > 8) { Buffer.BlockCopy(src, srcOffset, dst, dstOffset, len); } else { for (int i = 0; i < len; i++) { dst[dstOffset + i] = src[srcOffset + i]; } } } private void _CheckBuffer(int len) { if (_length + len > _tempBytes.Length) { int size = Math.Max(_tempBytes.Length + MIN_BUFF_LENGTH, _length + len); byte[] bytes = new byte[size]; _BlockCopy(_tempBytes, 0, bytes, 0, _length); _tempBytes = bytes; } } } }
06-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值