I had an issue recently where I needed to write some bits into a byte array in network order. In my case, I was writing an executable which could read a text file with some instructions on what to write into a network packet, then send that packet over the network.

raw

// Here is an explanation:
//
// Say we have our buffer, which is 1 byte long:
//           ^
// buffer -> 00000000
//
// Now we want to write a 2 bit long value into it, which has the value 3 (binary 11).
//                 ^
// Now buffer -> 11000000
// Now lets write another value, this time 3 bits, with the value zero.
//                    ^
// Now buffer -> 11000000
// Now lets write another 2 bit value, but this time with the value 1. (binary 01).
//                      ^
// Now buffer -> 11000010
//
// He is an example for an ethernet frame
//
// An ethernet frame is laid out as follows:
//
// Destination MAC Address: 48 bits
// Source MAC Address:      48 bits
// Ethernet Type:           16 bits
//
// I wanted to be able to create a file which had some sort
//   of details like this, very generalized.
//
// For example:
// 0x00155E112233 int48
// 0x00155E445566 int48
// 0x0800         int16
//
// This would write an ethernet frame into a byte[] buffer. There are some more
//   complex cases in the IPv4 header, which need to write values that are 2 or 4 bits long.
//

That's where this bit of code was born. It takes a list of values and their bit length as an input, and writes them into a bit buffer.

raw

/// <summary>
/// Simple struct for definiting a value and its bit length;
/// </summary>
public struct BitDefinition
{
    public BitDefinition(ulong value, byte bitLength)
    {
        this.Value = value;
        this.BitLength = bitLength;
    }

    public ulong Value;
    public byte BitLength;
}

public static byte[] WriteBits(IList<BitDefinition> bits)
{
    // Figure out the number of bytes we need in our array.
    var requiredBitLength = bits.Sum(b => b.BitLength);
    var requiredByteLength = requiredBitLength / 8 + (requiredBitLength % 8 == 0 ? 0 : 1);

    var buffer = new byte[requiredByteLength];
    unsafe
    {
        fixed (byte* bufferConstPtr = buffer)
        {
            // Initial offset withing the first byte is always zero.
            var bitOffset = 0;

            // We need a pointer we can actually change.
            var bufferPtr = bufferConstPtr;

            foreach(var bitDefinition in bits)
            {
                WriteBits(ref bufferPtr, ref bitOffset, bitDefinition.Value, bitDefinition.BitLength);
            }
        }
    }

    return buffer;
}

private static unsafe void WriteBits(ref byte * buffer, ref int bitOffset, ulong value, byte length)
{
    var bitsWritten = 0;

    if (length > 64)
    {
        throw new ArgumentOutOfRangeException("Length must be less than the size of a ulong");
    }

    while(bitsWritten < length)
    {
        // The number of bits left to write in total.
        var bitsRemaining = length - bitsWritten;

        // Size of the shift we need to do to get the target bits into the current byte.
        var shiftAmount = Math.Max(bitsRemaining - 8, 0);

        // The current byte value to write into the buffer.
        var currentVal = (byte)((value >> shiftAmount) & 0xFF);

        // The number of bits left to write.
        var currentByteBitsRemaining = Math.Min(8, bitsRemaining);

        // Move the value into the right offset and or it with the buffer.
        *buffer |= (byte)((currentVal << (8 - currentByteBitsRemaining)) >> bitOffset);

        // The number of written bits was either 
        var written = Math.Min(currentByteBitsRemaining, (8 - bitOffset));
        bitOffset += written;

        // If we have filled up the byte, then roll over to the next one.
        if(bitOffset == 8)
        {
            bitOffset = 0;
            buffer++;
        }

        // Add to the number of total bits written.
        bitsWritten += written;
    }
}

Any C# where you can use "unsafe" and pointers is great C#. This code also handles longer values which straddle the byte boundaries. And also values which are not byte aligned. Here is a test case.

raw

static unsafe void Main(string[] args)
{
    var definitions = Enumerable.Range(0, 16).Select(i => new BitDefinition((ulong)i % 2, 1)).ToList();
    var bytes = WriteBits(definitions);

    for(var i = 0; i < bytes.Length; i++)
    {
        Console.WriteLine("{0} {1:}", i, Convert.ToString(bytes[i], 2).PadLeft(8, '0'));
    }
}

// Output:
// 0 01010101
// 1 01010101

There you have it! Some not overly complicated C# which can write bits to a buffer in order, in big-endian(network order) format.