在 C# 中强制转换数组而不制作副本

Posted

技术标签:

【中文标题】在 C# 中强制转换数组而不制作副本【英文标题】:In C# cast array without making a copy 【发布时间】:2017-04-12 06:01:06 【问题描述】:

我有一系列短裤:

short[] data;

我有一个将字节写入文件的函数:

void Write(byte[] data);

我不控制这个功能,也不能改变它。有没有办法在不先制作冗余副本将其转换为字节的情况下编写我的短裤数组?

类似的东西:Write((byte[])data);

我不关心字节顺序。我想要以任何机器表示形式写入文件的短裤的内存表示。我知道这种类型的转换不适用于任何包含引用的非 POD 类型,但短裤应该是完全可转换的。强制转换应生成两倍大小的字节数组,指向相同的内存。

如果这在 C# 中是不可能的,那么 CLR 中是否有任何东西使这不可能,或者仅仅是 C# 的限制?

【问题讨论】:

你的代码以后依赖的东西没有任何“冗余”。出于好奇而制作副本有什么问题? 一个short是两个字节,一个字节是......一个字节。你打算如何转换它? Array.ConvertAll(array, item => (byte)item) 是您将获得的最佳选择。这确保了数组只迭代一次。让编译器处理性能影响。如果你关心这种低级的东西,你就不会用 C# 编写。 那么使用 C# 对性能的影响是不可接受的。这就是它的工作原理。不,当然,副本不会与您所拥有的完全相同。数组是 CLR 中的一等类型,它们是类型感知的。对于初学者来说,它会有不同的长度。这不是 C。您不只是将指针传递给第一个元素。如果您想编写您所描述的那种低级代码(我不怪您;我也是这样认为的),那么您不应该选择 C#。类型系统“紧身衣”为您提供安全:这一切都是设计使然。你只是不喜欢这个设计。 您正试图将 C# 变成一种不同于它试图成为的语言。数组不是 POD 类型。它们是对象,隐式继承自 System.Array。它们是 CLR 中的一等类型,C# 中的任何内容都不会改变这一点。 【参考方案1】:

我不关心字节顺序。我想要以任何机器表示形式写入文件的短裤的内存表示。

这是第一个不可能的事情——字节序会改变内存表示,因此从数组中第一个短字节地址开始的连续字节地址读取将根据机器字节序产生不同的字节模式。

第二个不可能的事情是 CLR 中的数组具有与数据一起编码的类型和长度信息。您不能更改此标头信息,否则您会破坏垃圾收集器。所以给定一个short[] 数组,你不能将它转换为byte[] 数组。您可能会使用 C++ clr 或不安全代码获得 byte 指针,但您仍然无法获得 CLR 数组。

如果您真的无法控制采用字节数组的代码,您可以更改操作短裤的代码。在字节数组上使用MemoryStream 将允许您对其进行读取和写入数据,您可以将数组包装为IList<short>,或者您可以创建访问器扩展函数以将数据作为short 获取。

public sealed class ShortList :IList<short>

    private readonly byte[] _array;

    public short this[int index]
    
        get  return (short)_array[index/2]<<8 | _array[index/2+1] ; 
    

    public int Count
    
        get  return _array.Length/2; 
    

    ... many more methods in IList

【讨论】:

将此标记为答案。我没有意识到数组长度和类型与 CLR 中的数据一起存储。如果是这样,在 C# 或任何基于 CLR 的语言中不可能有 2 个不同长度和类型的 Array 对象指向相同的数据。 “我不关心字节顺序”意味着我不关心字节的写入顺序。数据只会被写入它的同一设备读取。另外,我实际上知道我所有的平台都是小端的。 @kaalus it's impossible in C# or any CLR-based language to have 2 Array objects of different lengths and types pointing to the same data. 实际上不是。 .NET 支持数组协方差,只是不支持值类型,只支持引用类型。好吧,它们的长度相同;将一个引用类型数组视为另一种引用类型永远不会导致它具有不同的长度,但(编译时)类型会不同。【参考方案2】:

怎么样

Write(data.SelectMany(x => BitConverter.GetBytes(x)).ToArray());

【讨论】:

这会复制数组,OP 表示他不想这样做。 您的答案会生成两个(如果不是三个)数据副本。该数组为数百兆字节。代码在手机上运行。 @kaalus 它只复制一份数据,不再复制。如果您使用不需要所有数据都在物化数组中的写入操作,而是使用带流或IEnumerable 的写入操作,则可以使用 O(1) 额外内存执行类似的操作. copy 1 - BitConverter.GetBytes - 将数据从每个 short 复制到它自己的 short-lived 数组中;复制 2 个半 - IEnumerable 上的 ToArray() 不受 ICollection 支持(这是 SelectMany 使用 yield 的情况)将 Buffer 中的数组从 4 增长到所需的大小增加 2 的幂,因此将复制大约一半的数据平均两次。

以上是关于在 C# 中强制转换数组而不制作副本的主要内容,如果未能解决你的问题,请参考以下文章

C指针和数组:[警告]赋值使指针从整数而不进行强制转换[关闭]

strcpy 从整数生成指针而不进行强制转换

在 c++ 逻辑层上强制转换 int

在C#中如何将int类型强制转换为double类型

在Scala中的测试中访问私有变量而不进行强制转换

在 C# 中强制转换基数组的派生成员