Search code examples
c#filestreamimmutabilitybinaryfilesreadonly-collection

Writing ReadOnlyCollection<byte> to stream


I work with a binary format that contains several magic byte sequences. I want to keep them in a static class as immutable static members.

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|m}ff
    public static readonly IReadOnlyList<byte> DataDelimiter = Array.AsReadOnly(BitConverter.GetBytes(0L)); // eight binary zeroes, regardless of endianness
}

ReadOnlyCollection<byte> (returned from Array.AsReadOnly()) prevents outside code from changing the values, unlike byte[].

But now, I cannot output Header via stream.Write(), because it requires byte[]:

stream.Write(HuffmanConsts.Header, 0, HuffmanConsts.Header.Count)

Is there an elegant way to write the Header? Or do I have to write a loop and feed the bytes into the stream one by one?


Solution

  • Just Making the Output Array Immutable

    You could consider something like this:

    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|m}ff
          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(); } }
    }
    

    Dealing With Any Performance Implications of ToArray

    The overhead of ToArray() would be incurred each time you access these properties, however. To alleviate that potential performance penalty (note: testing is in order to see if there actually is one!), you can use System.Buffer.BlockCopy:

    private static readonly byte[] _header =
       // string {hu|m}ff
       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;
    }
    

    A Better Solution: Encapsulate Writing to the Stream

    You could also create extension methods that let your consumers stop messing about with the details of writing these stream components themselves, for example, the WriteHeader method might look like this:

    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);
       }
    }
    

    This would not make the array immutable, but being private that is not an issue.

    A Possibly Even Better Solution: Encapsulate a Huffman Stream Object

    You also have the option of implementing your own HuffmanStream which takes care of the details of the header and other aspects for you! I actually think this is ideal as it would encapsulate all the concerns of Huffman streams into a testable bit of code that is not duplicated every place you need to work with one.

    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
       }
    }
    

    Note: when passing a byte[] instance to Stream.Write(), it may be modified after the method returns as the method gets direct access to the array. Well-behaved Stream implementations don't do that, but to be safe against custom streams, you must treat Stream instances as hostile and therefore never pass them a reference to an array that shouldn't be changed. For example, any time you want to pass your _header byte array to possiblyHostileStream.Write(), you need to pass _header.Clone() instead. My HuffmanStream does not need this because it uses MemoryStream, which can be trusted.