很久没有搞比较底层一点的东西了,最近又开始搞,于是乎又发现了一些很鸡毛蒜皮的事情。也许有人已经发现过了,那就请原谅我就再来挖掘一遍。
byte[]、sbyte[]、int[]等数组,是一种特殊的类型,他们都继承自Array。不过这个继承还不是一般的继承关系,编译器和CLI都做了一些特殊的工作。我们先看普通的继承关系:
{
}
class Man : Human
{
}
class Woman : Human
{
}
如果我们试图写下列的代码:
Woman woman = (Woman)human;
那么结果是什么,你懂的。
好吧,你不懂。那我给说一下,Man和Woman都是从Human这个类继承的,因此Man和Woman是兄妹关系,兄妹之间实际上是不能互相转换的。虽然上面的代码是可以编译通过的,但是运行起来就会抛异常,告诉你Man类型的对象是不能转换成Woman的。
不过换到byte[]、sbyte[]等数组里面,就不是这么简单了。比如说:
sbyte[] target = (sbyte[])(Array)source;
foreach(var item in target)
{
Console.WriteLine(item);
}
你猜怎么着?哎?为啥居然能跑呢?按道理byte[]和sbyte[]都是从Array派生的,那么他们之间自然也是兄妹关系了。看来CLI为我们做了一些事情。好了,那么当我们有这么一个函数:
{
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[] another = new sbyte[3];
Copy(target, another);
在Copy函数内会出现除了ArgumentNullException之外的其它异常吗?这个怎么看也是不能吧?错了,可能会出现ArrayTypeMismatchException异常,假如GetTarget的代码是:
{
return (sbyte[])(Array)new byte[] {1, 2, 3};
}
这就奇怪了,为啥咧?因为Array.Copy会判断要复制的数组,其元素类型之间是什么关系。这个还不是简单的相等关系,说这个之前,我们再看另一个古怪的例子:
int[] intArray = (int[])(Array)source;
这个照样还是能够编译的,不过运行时会抛出来InvalidCastException。为什么sbyte[]和byte[]之间可以互相转换,同样是兄妹关系的byte[]和int[]就不能互相转换呢?其实,这个问题不需要深究都会知道,sbyte和byte[]的元素所占字节数都一样,所以理论上是可以直接转换来访问的。而int[]和byte[]之间,如果因为其元素一个是4字节另一个是1字节,而导致不允许直接进行转换,那也很正常。不过,既然sbyte[]和byte[]之间都可以强制转换,而且转换之后都可以访问元素,为啥就不能通过Array.Copy来复制呢?嗯,不用想,Array.Copy对于源数组和目标数组的元素类型进行了判断,至于咋判断的Reflector看不到,我们就先猜测吧:Array.Copy只要发现源和目标数组的元素类型不一样,那就不能够复制。真是这样吗?我们再看另一个例子:
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不是同一个人开发的时候。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器