将 ReadOnlyCollection<byte> 写入流
Posted
技术标签:
【中文标题】将 ReadOnlyCollection<byte> 写入流【英文标题】:Writing ReadOnlyCollection<byte> to stream 【发布时间】:2016-04-16 03:12:11 【问题描述】:我使用包含多个魔术字节序列的二进制格式。我想将它们作为不可变的静态成员保存在静态类中。
public static class HuffmanConsts
// output format: Header, serialized tree (prefix), DataDelimiter, coded data (logical blocks are 8 byte large, Little Endian)
public const string Extension = ".huff";
public static readonly IReadOnlyList<byte> Header = Array.AsReadOnly(new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66); // string hu|mff
public static readonly IReadOnlyList<byte> DataDelimiter = Array.AsReadOnly(BitConverter.GetBytes(0L)); // eight binary zeroes, regardless of endianness
ReadOnlyCollection<byte>
(从Array.AsReadOnly()
返回)防止外部代码更改值,这与byte[]
不同。
但是现在,我无法通过stream.Write()
输出Header
,因为它需要byte[]
:
stream.Write(HuffmanConsts.Header, 0, HuffmanConsts.Header.Count)
有没有一种优雅的方式来编写Header
?还是我必须编写一个循环并将字节一个一个地输入到流中?
【问题讨论】:
在Stack Overflow、MSDN 上搜索并尝试了stream.write readonlycollection byte
之类的查询,但没有得到相关结果。
Stream
需要byte[]
。点。您需要牺牲一些 OOP 概念或性能。选择权在你。
我会封装序列化/反序列化本身。考虑一个具有 [静态] 方法的类 void WriteHeader(stream), WriteDelimiter(stream), ReadHeader(stream), ...
【参考方案1】:
只是让输出数组不可变
你可以考虑这样的事情:
public static class HuffmanConsts
// output format: Header, serialized tree (prefix), DataDelimiter,
// coded data (logical blocks are 8 byte large, Little Endian)
public const string Extension = ".huff";
private static readonly IReadOnlyList<byte> _header =
// string hu|mff
Array.AsReadOnly(new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66);
private static readonly IReadOnlyList<byte> _dataDelimiter =
// eight binary zeroes, regardless of endianness
Array.AsReadOnly(BitConverter.GetBytes(0L));
public static byte[] Header get return _header.ToArray();
public static byte[] DataDelimiter get return _dataDelimiter.ToArray();
处理 ToArray 的任何性能影响
但是,每次访问这些属性时都会产生ToArray()
的开销。为了减轻潜在的性能损失(注意:测试是为了看看是否真的有一个!),你可以使用System.Buffer.BlockCopy
:
private static readonly byte[] _header =
// string hu|mff
new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66;
private static int BYTE_SIZE = 1;
private static byte[] GetHeaderClone()
byte[] clone = new byte[_header.Length];
Buffer.BlockCopy(_header, 0, clone, 0, _header.Length * BYTE_SIZE);
return clone;
更好的解决方案:封装写入流
您还可以创建扩展方法,让您的消费者不再纠结于自己编写这些流组件的细节,例如,WriteHeader
方法可能如下所示:
public static class StreamExtensions
// include BlockCopy code from above
public static void WriteHuffmanHeader(this Stream stream)
var header = GetHeaderClone();
stream.Write(header, 0, header.Length);
这不会使数组不可变,但私有化不是问题。
可能更好的解决方案:封装 Huffman 流对象
您还可以选择实现自己的HuffmanStream
,它会为您处理标题和其他方面的细节!我实际上认为这是理想的,因为它将 Huffman 流的所有关注点封装成一段可测试的代码,不会在你需要使用的每个地方都重复。
public class HuffmanStream : Stream
private Stream _stream = new MemoryStream();
private static byte[] _header = ... ;
public HuffmanStream( ... )
...
_stream.Write(_header, 0, _header.Length)
// the stream already has the header written at instantiation time
注意:将byte[]
实例传递给Stream.Write()
时,可能会在方法返回后对其进行修改,因为该方法可以直接访问数组。行为良好的 Stream
实现不会这样做,但为了防止自定义流,您必须将 Stream
实例视为敌对,因此永远不要向它们传递对不应该的数组的引用不能改变。例如,任何时候你想将_header
字节数组传递给possiblyHostileStream.Write()
,你需要传递_header.Clone()
。我的HuffmanStream
不需要这个,因为它使用了可以信任的MemoryStream
。
【讨论】:
编辑看起来很棒。我用不同的方法编写了自己的答案,但是围绕实际流编写 HuffmanStream 包装器可能是最终的解决方案。 Huffman coding 需要写入不能均匀划分为字节的位序列,因此 HuffmanStream 也可以实现 Write() 来表示这些位序列。 实际上,ReadOnlyCollection
、@usr 始终需要善意假设。通过强制转换,您可以从IReadOnlyList
接口获取它并使用它的Items
property,然后您就进入了只读外观。我相信将底层数组传递给Stream
的Write
方法是可以的,因为流没有理由改变数组。当我读到its documentation 时,这实际上是违反合同的。如果我错了,请纠正我。
@Palec 始终使用someList.AsReadOnly()
来填充IReadOnlyCollection
或IReadOnlyList
。你不能修改它,不需要善意。另外,Stream
是抽象的,所以class MaliciousStream : Stream
不安全。 Stream
的任何 MS 编写的子类,例如 FileStream
或 MemoryStream
都是安全的。
啊啊,Items
property is protected!这就是我错过的。
@Palec 它可能会违反合同,真的。这里的相关场景是生活在同一个 .NET 进程中的多个不合作方。这就是为什么 BCL 如此认真地不允许这样做。如果一个库运行起来会导致其他组件出现可怕的错误。如果您控制所有这些代码,我不会打扰任何这些,只需公开数组。或者可以使用此问题中找到的 99% 保护方法之一。【参考方案2】:
您可以保持原样并将Header
转换为byte[]
用于流:
stream.Write(HuffmanConsts.Header.ToArray(), 0, HuffmanConsts.Header.Count)
这个IEnumerable.ToArray()
扩展方法来自System.Linq
。
或者,您可以直接存储字节数组并使用属性返回其克隆。这是first approach described by ErikE 的一个更简单的变体。不再需要ReadOnlyCollection
。
public static class HuffmanConsts
// output format: Header, serialized tree (prefix), DataDelimiter, coded data (logical blocks are 8 byte large, Little Endian)
public const string Extension = ".huff";
private static byte[] _header = new byte[] 0x7B, 0x68, 0x75, 0x7C, 0x6D, 0x7D, 0x66, 0x66; // string hu|mff
private static byte[] _dataDelimiter = BitConverter.GetBytes(0L); // eight binary zeroes, regardless of endianity
public byte[] Header get return (byte[])_header.Clone();
public byte[] DataDelimiter get return (byte[])_dataDelimiter.Clone();
我不赞成这个解决方案,因为这些属性做了大量的工作(分配;尽管仍然是 O(1))。根据Framework Design Guidelines,将它们转换为Get*
方法可以传达这个想法,并且是发布不可变数组时要走的路。
正如 Ivan Stoev 在问题下评论的那样:
Stream
需要byte[]
。点。您需要牺牲一些 OOP 概念或性能。选择权在你。
原因是(我猜)字节数组直接传递给底层系统调用,而其他集合具有不兼容的内部结构。因此,我相信,如果您想保留HuffmanConsts
的当前实现,就不可能避免每次调用新数组分配带来的开销。
【讨论】:
每天学习新东西。我在我的 C# 中处于中间状态,只是没有在我的雷达上看到Clone
,所以谢谢你!它比Buffer.BlockCopy
好(它仍然可以将部分目标复制到部分目的地,Clone
显然不会这样做)。您是否介意我更新我的答案以使用Clone
(并将BlockCopy
的原始推荐留在cmets 中,这样您的答案仍然有意义)?
是的,我没问题。以上是关于将 ReadOnlyCollection<byte> 写入流的主要内容,如果未能解决你的问题,请参考以下文章
将 Dictionary<TKey, List<TValue>> 转换为 ReadOnlyDictionary<TKey, ReadOnlyCollection<T
根据 FxCop,为啥 ReadOnlyCollection<ReadOnlyCollection<T>> 不好,以及在生成不可变二维对象时有啥替代方法? [复制]
ReadOnlyCollection 或 IEnumerable 用于公开成员集合?
更新 GridView 中的 ReadOnlyCollection
如何在 C# 中使用带有 HashSet<ReadOnlyCollection<string>> 类型的 exceptWith?