views:

141

answers:

3

I am parsing an arbitrary length byte array that is going to be passed around to a few different layers of parsing. Each parser creates a Header and a Packet payload just like any ordinary encapsulation. And my problem lies in how the encapsulation holds its packet byte array payload. Say i have a 100 byte array, and it has 3 levels of encapsulation. 3 packet objects will be created and i want to set the payload of these packets to the corresponding position in the byte array of the packet. For example lets say the payload size is 20 for all levels, then imagine it has a public byte[] Payload on each object. However the problem is that this byte[] Payload is a copy of the original 100 bytes. So i'm going to end up with 160 bytes in memory instead of 100.

If it were in c++ i could just easily use a pointer however i'm writing this in c#. So i created the following class:

public class PayloadSegment<T> : IEnumerable<T>
{
    public readonly T[] Array;
    public readonly int Offset;
    public readonly int Count;

    public PayloadSegment(T[] array, int offset, int count)
    {
        this.Array = array;
        this.Offset = offset;
        this.Count = count;
    }

    public T this[int index]
    {
        get
        {
            if (index < 0 || index >= this.Count)
                throw new IndexOutOfRangeException();
            else
                return Array[Offset + index];
        }
        set
        {
            if (index < 0 || index >= this.Count)
                throw new IndexOutOfRangeException();
            else
                Array[Offset + index] = value;
        }
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = Offset; i < Offset + Count; i++)
            yield return Array[i];
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        IEnumerator<T> enumerator = this.GetEnumerator();
        while (enumerator.MoveNext())
        {
            yield return enumerator.Current;
        }
    }
}

This way i can simply reference a position inside the original byte array but use positional indexing.

However if i do something like:

 PayloadSegment<byte> something = new PayloadSegment<byte>(someArray, 5, 10);
 byte[] somethingArray = something.ToArray();

Will the somethingArray be a copy of the bytes, or a reference to the original PayloadSegment which in turn is a reference to the original byte array?

Sorry it was hard to word this lol >_<

EDIT: Actually after rethinking this can't i simply use a new MemoryStream(array, offset, length) ?

A: 

Because byte is a value type, the array will hold copies of the values, not pointers to them.
If you need the same behavior as an reference type, it is best to create a class that holds the byte has a property, and may group other data and functionality.

Kobi
A: 

It's a copy. It would be very unintuitive if I passed something.ToArray() to some method, and the method changed the value of something by changing the array!

BlueRaja - Danny Pflughoeft
+2  A: 

The documentation for the Enumerable.ToArray extension method doesn't specifically mention what it does when it's passed a sequence that happens to already be an array. But a simple check with .NET Reflector reveals that it does indeed create a copy of the array.

It is worth noting however that when given a sequence that implements ICollection<T> (which Array does) the copy can be done much faster because the number of elements is known up front so it does not have to do dynamic resizing of the buffer such as List<T> does.

Josh Einstein
Ok i guess i'll have to use a foreach loop on the IEnumerator instead. Thanks
Daniel
Yeah my intent is to hide the portion of the byte[] of data. But i'd still like to be able to treat it as a normal array i.e have indexing, i think i'll just pass the PayloadSegment<byte> to the actual parser and have it use it. Or is there already interface that i can use?
Daniel
Actually after rethinking this can't i simply use a new MemoryStream(array, offset, length) ?
Daniel
MemoryStream won't help, it will actually be worse. It has its own buffer that will be at least as large as you put in. Your original enumerator will work fine without any additional copy of the array. I would avoid calling ToArray() unless you truly need an array, but understand it will be a copy. There's no way to treat a segment of an array as an array in .NET. Even ArraySegment<T> which the framework already has is basically useless.
Josh Einstein
You should also implement IList<T>, return true for IsReadOnly, throw exceptions on the methods that would attempt to add/remove/clear/etc. Implementing IList<T> will allow your class to be used by more API's than if you just implemented IEnumerable<T>.
Josh Einstein
Thanks very much for your help :) I'll pass the PayloadSegment<byte> object to the parsers so i don't have to worry about IEnumerables
Daniel