Updated on 2021-04-23
BinaryReader needs a better way to read strings and types. Here's a quick and dirty fix
Perhaps just to prove I can trade you less than 5 minutes of your time and in return give you something useful, here you go.
BinaryReader can't read fixed length nor ASCII null terminated strings out of streams, which is unfortunate because a whole lot of files store strings that way, for better or worse. Furthermore, there's no easy way to read data into an entire structure without reading each individual field.
I recently reimplemented this feature once again, because it seems every time I do, the code gets buried somewhere to the point where it's easier to rewrite it than to hunt it down. This tip therefore, is as much for me as it is for you, gentle reader.
This code is so short, I'm simply pasting it here for you to use.
using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
/// <summary>
/// Provides some conspicuously absent string and type functionality to
/// <seealso cref="BinaryReader"/>
/// </summary>
static class BinaryReaderExtensions
{
/// <summary>
/// Reads a class or a struct from the reader
/// </summary>
/// <typeparam name="T">The type to read</typeparam>
/// <param name="reader">The reader</param>
/// <returns>An instance of <typeparamref name="T"/> as read from the stream</returns>
public static T ReadType<T>(this BinaryReader reader)
{
byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T)));
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T result = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return result;
}
/// <summary>
/// Reads a C style null terminated ASCII string
/// </summary>
/// <param name="reader">The binary reader</param>
/// <returns>A string as read from the stream</returns>
public static string ReadSZString(this BinaryReader reader)
{
var result = new StringBuilder();
while (true)
{
byte b = reader.ReadByte();
if (0 == b)
break;
result.Append((char)b);
}
return result.ToString();
}
/// <summary>
/// Reads a fixed size ASCII string
/// </summary>
/// <param name="reader">The binary reader</param>
/// <param name="count">The number of characters</param>
/// <returns>A string as read from the stream</returns>
public static string ReadFixedString(this BinaryReader reader,int count)
{
return Encoding.ASCII.GetString(reader.ReadBytes(count));
}
}
After pasting the above into a file in your code, simply use the BinaryReader as you normally would, except that now you have the above methods. Keep in mind any types you want to read from a file must be able to be marshalled. This may mean actually adding marshalling attributes to your types if you want to use ReadType<>() with them.
Reading these ASCII strings is often useful when you're dealing with older files that commonly use fixed length or null terminated ASCII to store their strings. When you encounter such a string in your stream, you can simply use the appropriate method to read it. The doc comments should make everything clear.
And that's all there is to it. Hopefully it helps you out, if not now, then somewhere down the road.