Equivalent of StringBuilder for byte arrays
Asked Answered
D

6

44

This is a simple one, and one that I thought would have been answered. I did try to find an answer on here, but didn't come up with anything - so apologies if there is something I have missed.

Anyway, is there an equivalent of StringBuilder but for byte arrays?

I'm not bothered about all the different overloads of Append() - but I'd like to see Append(byte) and Append(byte[]).

Is there anything around or is it roll-your-own time?

Decoteau answered 25/10, 2010 at 14:36 Comment(0)
S
41

Would MemoryStream work for you? The interface might not be quite as convenient, but it offers a simple way to append bytes, and when you are done you can get the contents as a byte[] by calling ToArray().

A more StringBuilder-like interface could probably be achieved by making an extension method.

Update
Extension methods could look like this:

public static class MemoryStreamExtensions
{
    public static void Append(this MemoryStream stream, byte value)
    {
        stream.Append(new[] { value });
    }

    public static void Append(this MemoryStream stream, byte[] values)
    {
        stream.Write(values, 0, values.Length);
    }
}

Usage:

MemoryStream stream = new MemoryStream();
stream.Append(67);
stream.Append(new byte[] { 68, 69 });
byte[] data = stream.ToArray();  // gets an array with bytes 67, 68 and 69
Show answered 25/10, 2010 at 14:39 Comment(9)
Yes it would - I like that approach... ThanksDecoteau
Can I just ask - I haven't actually seen the syntax new[] { value } before - is that inferring the type of the array?Decoteau
@Matt: yes, that is correct. In the extension method, value is of type byte, so byte[] is inferred. In the last sample I need to specify new byte[] since the compiler would infer the type int otherwise.Neuman
@Frederik Mörk - Awesome - thanks. Accepting for the extra 'I've learned something new today' goodness.Decoteau
@Fredrik Mörk: Is there any way to munch data from the start of a MemoryStream, so it works like a queue, or is there any other CLR class which can be used as an intra-process pipe?Handpick
@supercat: as far as I know, there is no functionality in MemoryStream for that. I don't know any other class than Queue<byte> from the top of my head.Neuman
To make this even more like a StringBuilder, you could have the extension methods return the stream, itself. While this may sacrifice a small amount of efficiency, it's not terribly uncommon in builder patterns to return the builder with each method, so that they can be called in one, long statement.Elytron
It'd be more efficient to use stream.WriteByte(value) for the Append overload that takes a single byte as it would avoid the intermediate byte[] array allocation.Miyasawa
Don't you need to position MemoryStream to the end before appending?Nomi
J
22

The MemoryStream approach is good, but if you want to have StringBuilder-like behavior add a BinaryWriter. BinaryWriter provides all the Write overrides you could want.

MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
writer.Write((byte)67);
writer.Write(new byte[] { 68, 69 });
Jessalyn answered 25/10, 2010 at 14:51 Comment(3)
You can also derive a new class from BinaryWriter if you need special writers. I made one that handled hton conversions.Actinon
And when done, get the array with stream.ToArray();Wrap
Please note, that the Write(String) method of the BinaryWriter writes a length prefix to the stream!Bundle
G
12

Probably List<byte>:

var byteList = new List<byte>();
byteList.Add(42);
byteList.AddRange(new byte[] { 1, 2, 3 });
Guttate answered 25/10, 2010 at 14:38 Comment(8)
...and then Append(byte) becomes Add(byte) and Append(byte[]) becomes AddRange(byte[]).Tyrrell
Note that as with StringBuilder, adding elements to a List is amortized O(1).Rest
Ok, cool - just one question... Would that be as efficient? I know List<> does some efficient copies for AddRange under the hood, but I didn't know if that works out as the most efficient way to do it?Decoteau
@Matt : As with StringBuilder, C# List uses a backing capacity greater than the size of the data structure in order to enable constant time insertions. This capacity is doubled as needed. This strategy is the same strategy StringBuilder uses to avoid excessive memory allocations.Rest
@Rest - Actually, what I was referring to was the way that List<> checks to see if the IEnumerable it is adding in AddRange is an ICollection too, and if it is it adds the elements in a different way... I can't remember exactly how, because I'm not at a machine where I have the reference code downloaded...Decoteau
@Matt: Yes, if you pass an ICollection<T> then the AddRange method uses the collection's Count property in order pre-allocate the list to the right size (if necessary). If it's not an ICollection<T> then the list will need to be resized on-the-fly as the elements are added.Guttate
How does your answer compare to Fredrik Mörk's 'MemoryStream' answer in terms of versatility, efficiency and closeness to StringBuilder?Bangalore
One notable feature of StringBuilder that MemoryStream is missing is StringBuilder.Remove(). List<byte> appears to provide an exact equivalent with List<T>.RemoveRange(). The inflexibility of MemoryStream’s API makes me suspect that it can be more efficient on operations both it and List<T> support, but I haven’t tested this.Lazes
V
3

List<byte> Then when you want it as an array you can call ToArray()

Virgilio answered 25/10, 2010 at 14:39 Comment(0)
K
0
using System;
using System.IO;

public static class MemoryStreams
{
    public static MemoryStream Append(
        this MemoryStream stream
        , byte value
        , out bool done)
    {
        try
        {
            stream.WriteByte(value);
            done = true;
        }
        catch { done = false; }
        return stream;
    }

    public static MemoryStream Append(
        this MemoryStream stream
        , byte[] value
        , out bool done
        , uint offset = 0
        , Nullable<uint> count = null)
    {
        try
        {
            var rLenth = (uint)((value == null) ? 0 : value.Length);

            var rOffset = unchecked((int)(offset & 0x7FFFFFFF));

            var rCount = unchecked((int)((count ?? rLenth) & 0x7FFFFFFF));

            stream.Write(value, rOffset, rCount);
            done = true;
        }
        catch { done = false; }
        return stream;
    }
}
Kansu answered 25/10, 2010 at 14:36 Comment(0)
A
0

Look at this project at codeproject. It's just one class that implements the solution with a MemoryStream as suggested in other answers.

Applejack answered 17/2, 2022 at 7:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.