Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / string

A Few Missing Methods on BinaryReader

5.00/5 (12 votes)
23 Apr 2021CPOL1 min read 9.1K  
BinaryReader needs a better way to read strings and types. Here's a quick and dirty fix
BinaryReader often can't read strings the way they're stored in files not created with BinaryWriter, and can't easily read types an entire struct/class at a time. These quick extension methods remedy that.

ReadFixedString

Introduction

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.

Coding this Mess

This code is so short, I'm simply pasting it here for you to use.

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

Using this Mess

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.

History

  • 23rd April, 2021 - Initial submission

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)