2

Question

Is there a way create a FileStream in C# with an offset? For example, if I open SomeFile.bin at offset 100, Stream.Position would equal 0, but reads & writes would be offset by 100.

Background

I'm working on a hybrid file format for my company that combines machine-readable binary data with a modern PC-readable OPC file (essentially a ZIP file created using System.IO.Packaging). For performance reasons, the binary data must appear at the top of the file. I understand that ZIP files will allow this (e.g. self-extracting archives), but unfortunately the internal ZipIOCentralDirectoryBlock class is hard-coded to reject ZIP files where the first file header doesn't appear at offset 0. In the interest of avoiding temporary files (since the files can be as large as 3.99GB), I would like to fool ZipPackage into thinking it's dealing with the start of a file, when in reality it's reading & writing at an offset.

Neil
  • 249
  • 3
  • 13
  • 2
    It wouldn't be hard to create a wrapper class to do this. – willaien Jun 26 '15 at 17:26
  • What's the easiest way to create the wrapper? Should I inherit from FileStream, or inherit from Stream and own a FileStream instance? – Neil Jun 26 '15 at 17:48
  • Inheriting from FileStream would be natural, but due to the silly amount of constructors, I would avoid it. – leppie Jun 26 '15 at 18:23

1 Answers1

3

Sure. This is a perfect case for the Decorator Pattern:

Basically you create a class that

  • inherits from Stream (the abstract class you are decorating)
  • has a constructor that accepts a single instance of that base class

Then you override all the methods and properties, passing the call through to the decorated instance. If the method or property has knowledge of the stream's position or length, you apply the appropriate adjustment as needed.

Edited to note: It looks like you need to decorate the abstract stream as shown below (no way to create a filestream instance without actually opening a file).

Here's a [truncated] example of the decorator itself:

class OffsetStreamDecorator : Stream
{
  private readonly Stream instance ;
  private readonly long       offset   ;

  public static Stream Decorate( Stream instance )
  {
    if ( instance == null ) throw new ArgumentNullException("instance") ;

    FileStream decorator= new OffsetStreamDecorator( instance ) ;
    return decorator;
  }

  private OffsetStreamDecorator( FileStream instance )
  {
    this.instance = instance ;
    this.offset   = instance.Position ;
  }

  #region override methods and properties pertaining to the file position/length to transform the file positon using the instance's offset

  public override long Length
  {
    get { return instance.Length - offset ; }
  }

  public override void SetLength( long value )
  {
    instance.SetLength( value + offset );
  }

  public override long Position
  {
    get { return instance.Position - this.offset         ; }
    set {        instance.Position = value + this.offset ; }
  }

  // etc.

  #endregion

  #region override all other methods and properties as simple pass-through calls to the decorated instance.

  public override IAsyncResult BeginRead( byte[] array , int offset , int numBytes , AsyncCallback userCallback , object stateObject )
  {
    return instance.BeginRead( array , offset , numBytes , userCallback , stateObject );
  }

  public override IAsyncResult BeginWrite( byte[] array , int offset , int numBytes , AsyncCallback userCallback , object stateObject )
  {
    return instance.BeginWrite( array , offset , numBytes , userCallback , stateObject );
  }

  // etc.

  #endregion

}

Usage is pretty straightforward, something along these lines:

using ( Stream baseStream = new FileStream( @"c:\foo\bar\somefile.dat" , FileMode.Open , FileAccess.Read , FileShare.Read ) )
{

  // establish your offset
  baseStream.Seek( 100 , SeekOrigin.Begin ) ;

  using ( Stream decoratedStream = OffsetStreamDecorator.Decorate( baseStream ) )
  {
    // now you have a stream that reports a false position and length
    // which you should be able to use anywhere a Stream is accepted.

    PassDecoratedStreamToSomethingExpectingAVanillaStream( decoratedStream ) ;

  }

}

Easy! (except for all the boilerplate involved)

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • Perhaps you missed the bit about it being a ZIP file. The is no friggen way in hell you can guess offsets if compression is involved... – leppie Jun 26 '15 at 18:01
  • 1
    @leppie: A zip file is an *archive* format containing one or more individually compressed files. The only offsets involved here are WRT the zip file itself. The zip file directory is found at the end of the zip file. Each entry in the directory tells you the location and length of that compressed entry within the zip file. You seek to it, read the octets and decompress them. All we're doing here is establishing a different "zero point" for the stream. – Nicholas Carey Jun 26 '15 at 18:07
  • Sorry, I misunderstood the question. But my brain compiler tells me there are quite a few compiler errors in your code :) (upvoting for now, but please fix your type errors) – leppie Jun 26 '15 at 18:21
  • Thanks, this looks like it will do what I want. Quick question though, should I still override Close() and Dispose() if I'm going to use this in an inner using block like your example? Seems like that would lead to the FileStream being closed and disposed twice, or worse being accidentally used after being disposed. – Neil Jun 26 '15 at 19:31
  • Never mind, [this answered my question](http://stackoverflow.com/questions/17943870/decorators-and-idisposable). I will not be overriding Close() and Dispose(). – Neil Jun 26 '15 at 19:38
  • You need to override Seek, and if origin is SeekOrigin.Begin, add this.offset to the offset passed to the method when calling instance.Seek. – robertburke Feb 17 '23 at 04:35