How to expose a sub section of my stream to a user
Asked Answered
M

5

16

I have a stream that contains many pieces of data. I want to expose just a piece of that data in another stream. The piece of data I want to extract can often be over 100mb. Since I already have stream with the data in it it seems like a waste to copy that data to another stream and return that. What im looking for is a way to reference the data in the first stream while controlling how much of it the second stream can reference. Is this possible

Mossman answered 4/8, 2011 at 22:35 Comment(0)
K
18

There is a good implementation of this by Mark Gravell detailed here. The code posted there is:

using System.IO;
using System;
static class Program
{

 // shows that we can read a subset of an existing stream...
    static void Main()
    {
        byte[] buffer = new byte[255];
        for (byte i = 0; i < 255; i++)
        {
            buffer[i] = i;
        }
        using(MemoryStream ms = new MemoryStream(buffer))
        using (SubStream ss = new SubStream(ms, 10, 200))
        {
            const int BUFFER_SIZE = 17; // why not...
            byte[] working = new byte[BUFFER_SIZE];
            int read;
            while ((read = ss.Read(working, 0, BUFFER_SIZE)) > 0)
            {
                for (int i = 0; i < read; i++)
                {
                    Console.WriteLine(working[i]);
                }
            }
        }
    }
}

class SubStream : Stream
{
    private Stream baseStream;
    private readonly long length;
    private long position;
    public SubStream(Stream baseStream, long offset, long length)
    {
        if (baseStream == null) throw new ArgumentNullException("baseStream");
        if (!baseStream.CanRead) throw new ArgumentException("can't read base stream");
        if (offset < 0) throw new ArgumentOutOfRangeException("offset");

        this.baseStream = baseStream;
        this.length = length;

        if (baseStream.CanSeek)
        {
            baseStream.Seek(offset, SeekOrigin.Current);
        }
        else
        { // read it manually...
            const int BUFFER_SIZE = 512;
            byte[] buffer = new byte[BUFFER_SIZE];
            while (offset > 0)
            {
                int read = baseStream.Read(buffer, 0, offset < BUFFER_SIZE ? (int) offset : BUFFER_SIZE);
                offset -= read;
            }
        }
    }
    public override int Read(byte[] buffer, int offset, int count)
    {
        CheckDisposed();
        long remaining = length - position;
        if (remaining <= 0) return 0;
        if (remaining < count) count = (int) remaining;
        int read = baseStream.Read(buffer, offset, count);
        position += read;
        return read;
    }
    private void CheckDisposed()
    {
        if (baseStream == null) throw new ObjectDisposedException(GetType().Name);
    }
    public override long Length
    {
        get { CheckDisposed(); return length; }
    }
    public override bool CanRead
    {
        get { CheckDisposed(); return true; }
    }
    public override bool CanWrite
    {
        get { CheckDisposed(); return false; }
    }
    public override bool CanSeek
    {
        get { CheckDisposed(); return false; }
    }
    public override long Position
    {
        get {
            CheckDisposed();
            return position;
        }
        set { throw new NotSupportedException(); }
    }
    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }
    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }
    public override void Flush()
    {
        CheckDisposed(); baseStream.Flush();
    }
    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (disposing)
        {
            if (baseStream != null)
            {
                try { baseStream.Dispose(); }
                catch { }
                baseStream = null;
            }
        }
    }
    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }
}
Kesha answered 9/11, 2016 at 22:59 Comment(0)
H
4

You need to make your own Stream class that validates its position and returns the desired subset.

I'm not aware of any built-in classes that do this.

Hewie answered 4/8, 2011 at 22:40 Comment(0)
M
2

Looks like StreamMuxer project was created with similar purpose in mind.

Mcinnis answered 11/1, 2014 at 10:39 Comment(1)
This looks good. I wish that Code Project worked with Git Hub. Likely a slight conflict of interest.Mossman
B
1

I also needed a substream so that I could work on a ZIP archive inside of another file, the other answer didn't implement seeking though so here's one with seeking; be aware it will not dispose the original stream when disposed:

public class SubStream : Stream
{
    Stream Vector;
    long Offset, _Length, _Position = 0;
    public SubStream(Stream vector, long offset, long length)
    {
        if (length < 1) throw new ArgumentException("Length must be greater than zero.");

        this.Vector = vector;
        this.Offset = offset;
        this._Length = length;

        vector.Seek(offset, SeekOrigin.Begin);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        CheckDisposed();
        long remaining = _Length - _Position;
        if (remaining <= 0) return 0;
        if (remaining < count) count = (int)remaining;
        int read = Vector.Read(buffer, offset, count);
        _Position += read;
        return read;
    }

    private void CheckDisposed()
    {
        if (Vector == null) throw new ObjectDisposedException(GetType().Name);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        long pos = _Position;

        if (origin == SeekOrigin.Begin)
            pos = offset;
        else if (origin == SeekOrigin.End)
            pos = _Length + offset;
        else if (origin == SeekOrigin.Current)
            pos += offset;

        if (pos < 0) pos = 0;
        else if (pos >= _Length) pos = _Length - 1;

        _Position = Vector.Seek(this.Offset + pos, SeekOrigin.Begin) - this.Offset;

        return pos;
    }

    public override bool CanRead => true;

    public override bool CanSeek => true;

    public override bool CanWrite => false;

    public override long Length => _Length;

    public override long Position { get => _Position; set { _Position = this.Seek(value, SeekOrigin.Begin); } }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }
}
Britannia answered 19/8, 2020 at 4:54 Comment(0)
I
-2

What exactly are you scared of duplicating? I doubt you have anything super performance critical, parse your Stream on the fly and use a MemoryStream till you find you need something else.

Inconsonant answered 5/8, 2011 at 0:50 Comment(1)
I would be copying hundreds of mesgs worth of data, and don't want to copy that to another place in memory. Specifically if the person is going to request multiple hundred meg sections. Besides it not a fear of duplication, is a practice of programming to reuse other classes if they exist.Mossman

© 2022 - 2025 — McMap. All rights reserved.