anchor.diff:      member x.Length = x.IndexTo - x.IndexFrom + 1UL |> int
       override x.ToString() = sprintf "[%d,%d]" x.IndexFrom x.IndexTo
   
  -/// The internal structure to support `CharStreamSource.DynamicBlock`.
  -type private DynamicBlock =
  -    {
  -        /// The `TextReader` that powers the stream.
  -        Reader: TextReader
  -        /// A character array where the characters that get read, but are still needed are stored.
  -        mutable Buffer: char[]
  -        /// The index of the first element in the buffer.
  -        mutable BufferStartingIndex: uint64
  -        /// The index of the next character to be read.
  -        mutable NextReadIndex: uint64
  -    }
  -    member x.Item
  -        with get idx =
  -            if idx >= x.BufferStartingIndex && idx < x.NextReadIndex then
  -                let position = int <| idx - x.BufferStartingIndex
  -                x.Buffer.[position]
  -            else
  -                failwithf "Trying to get character at %d, while only characters from %d to %d have been loaded"
  -                    idx x.BufferStartingIndex <| x.NextReadIndex - 1UL
  -    /// The real length of the buffer, excluding the unpinned characters at the end.
  -    member x.BufferContentLength = x.NextReadIndex - x.BufferStartingIndex |> int
  +/// A type pointing to a character in a character stream.
  +type [<Struct>] CharStreamIndex = private {Index: uint64}
  +
  +/// A representation of a `CharStream`.
  +// Previously, this type was a discriminated union. I changed it to an abstract because
  +// pattern matches on it, which involve two type checks+casts seemed to be inferior to virtual calls.
  +// Besides, the CLR is more inclined to handle inheritance and polymorphism. 
  +// And regardless, this layout clearly separates source-specific code from the rest of it.
  +[<AbstractClass>]
  +type private CharStreamSource() =
  +    /// Gets a specified character by index; if it exists in memory, or raises an exception.
  +    abstract Item: uint64 -> char
  +    /// Returns the length of the input; or at least the
  +    /// length of the input that has ever crossed the memory.
  +    /// In dynamic block streams, it doesn't mean that all these characters are still in memory.
  +    abstract LengthSoFar: uint64
  +    /// Reads the character at the specified index, places it into the outref, and moves the
  +    /// index one position forward. Returns `false` when input ended. The first parameter
  +    /// is used for the dynamic block source.
  +    abstract ReadNextCharacter: uint64 * byref<CharStreamIndex> * outref<char> -> bool
  +    /// Returns a `ReadOnlySpan` containing the characters from the given index with the given length.
  +    /// The characters in the span are not bound to be released from memory after that call;
  +    /// this is the responsibility of the unpinning functions.
  +    abstract GetSpanForCharacters: uint64 * int -> ReadOnlySpan<char>
  +    /// Disposes unmanaged resources using a well-known pattern.
  +    /// To be overriden on sources that require it.
  +    abstract Dispose: bool -> unit
  +    default __.Dispose _ = ()
       interface IDisposable with
           member x.Dispose() =
  -            x.Reader.Dispose()
  +            x.Dispose(true)
               GC.SuppressFinalize(x)
  -    override x.Finalize() = (x :> IDisposable).Dispose()
  -
  -/// A representation of a `CharStream`.
  -type private CharStreamSource =
  -    /// A representation of a `CharStream` that stores
  -    /// the characters in one continuous area of memory.
  -    /// It is not recommended for large files.
  -    | StaticBlock of ReadOnlyMemory<char>
  -    /// A representation of a `CharStream` that lazily
  -    /// loads input from a `TextReader` when it is needed
  -    /// and unloads it when it is not.
  -    | DynamicBlock of DynamicBlock
  -    /// Gets a specified character by index; if it exists in memory, or raises an exception.
  -    member x.Item
  -        with get idx =
  -            match x with
  -            | StaticBlock sb -> sb.Span.[int idx]
  -            | DynamicBlock db -> db.[idx]
  -    /// Returns how many characters have been read so far.
  -    /// In dynamic block streams, it doesn't mean that all these characters are still on memory.
  -    member x.LengthSoFar =
  -        match x with
  -        | StaticBlock sb -> uint64 sb.Length
  -        | DynamicBlock db -> db.NextReadIndex
  -    interface IDisposable with
  -        member x.Dispose() =
  -            match x with
  -            | StaticBlock _ -> ()
  -            | DynamicBlock db -> (db :> IDisposable).Dispose()
  +
  +[<Sealed>]
  +/// A representation of a `CharStream` that stores
  +/// the characters in one continuous area of memory.
  +/// It is not recommended for large files.
  +type private StaticBlockSource(mem: ReadOnlyMemory<_>) =
  +    inherit CharStreamSource()
  +    override __.Item idx = mem.Span.[int idx]
  +    override __.LengthSoFar = uint64 mem.Length
  +    override __.ReadNextCharacter(_, idx, c) =
  +        if idx.Index < uint64 mem.Length then
  +            c <- mem.Span.[int idx.Index]
  +            idx <- {Index = idx.Index + 1UL}
  +            true
  +        else
  +            false
  +    override __.GetSpanForCharacters(startIndex, length) = mem.Span.Slice(int startIndex, length)
  +
  +[<Sealed>]
  +/// A representation of a `CharStream` that lazily
  +/// loads input from a `TextReader` when it is needed
  +/// and unloads it when it is not.
  +type private DynamicBlockSource(reader: TextReader, bufferSize) =
  +    inherit CharStreamSource()
  +    /// Whether the `Dispose` method has been called.
  +    /// Using this class is prohibited afterwards.
  +    let mutable disposed = false
  +    /// A character array where the characters that get read, but are still needed are stored.
  +    let mutable buffer = Array.zeroCreate bufferSize
  +    /// The index of the first element in the buffer.
  +    let mutable bufferFirstCharacterIndex = 0UL
  +    /// The index of the next character to be read.
  +    let mutable nextReadIndex = 0UL
  +    member __.CheckDisposed() =
  +        if disposed then
  +            raise <| ObjectDisposedException("Cannot use a dynamic block character stream after being disposed.")
  +    member private __.IsBufferFull = uint64 buffer.Length = nextReadIndex - bufferFirstCharacterIndex
  +    override db.Item idx =
  +        db.CheckDisposed()
  +        if idx >= bufferFirstCharacterIndex && idx < nextReadIndex then
  +            let position = int <| idx - bufferFirstCharacterIndex
  +            buffer.[position]
  +        elif nextReadIndex = 0UL then
  +            failwithf "Trying to get character at %d, while nothing has been read yet" idx
  +        else
  +            failwithf "Trying to get character at %d, while only characters from %d to %d have been loaded"
  +                idx bufferFirstCharacterIndex <| nextReadIndex - 1UL
  +    override db.LengthSoFar =
  +        db.CheckDisposed()
  +        nextReadIndex
  +    override db.ReadNextCharacter(startingIndex, idx, c) =
  +        db.CheckDisposed()
  +        // The character we want to read is already in memory. Easy stuff.
  +        if idx.Index < nextReadIndex then
  +            c <- db.[idx.Index]
  +            idx <- {Index = idx.Index + 1UL}
  +            true
  +        // The character we want to read is the next one to be read.
  +        elif idx.Index = nextReadIndex then
  +            // The buffer might be full however.
  +            if db.IsBufferFull then
  +                // If not all characters in the buffer are needed, we move those we need to the start.
  +                if bufferFirstCharacterIndex <> startingIndex then
  +                    let importantCharStart = int <| startingIndex - bufferFirstCharacterIndex
  +                    let importantCharLength = int <| nextReadIndex - startingIndex
  +                    Array.blit buffer importantCharStart buffer 0 importantCharLength
  +                    bufferFirstCharacterIndex <- startingIndex
  +                // Otherwise, we double the buffer's size. It is doubled to achieve amortized constant complexity.
  +                // But to reach the point of growing it, many times, we must have huge terminals. The default
  +                // buffer size is some hundreds of characters.
  +                else
  +                    Array.Resize(&buffer, buffer.Length * 2)
  +            // It's now time to read the next character.
  +            // We read each character at a time from the reader. It does not adversely
  +            // affect performance because the reader has its own buffer as well!
  +            let cRead = reader.Read()
  +            // There are still characters to read. We store this little
  +            // character in the buffer, and return it as well.
  +            if cRead <> -1 then
  +                nextReadIndex <- nextReadIndex + 1UL
  +                buffer.[int <| idx.Index - bufferFirstCharacterIndex] <- char cRead
  +                c <- char cRead
  +                idx <- {Index = nextReadIndex}
  +                true
  +            // We have reached the end.
  +            else
  +                false
  +        // We cannot read past the first character that has not been read.
  +        // This is how the CharStream works; you have to read one character at a time.
  +        else
  +            failwithf "Cannot read character at %d because the latest one was read at %d." idx.Index nextReadIndex
  +    override db.GetSpanForCharacters(startIndex, length) =
  +        db.CheckDisposed()
  +        let startIndex = startIndex - bufferFirstCharacterIndex |> int
  +        ReadOnlySpan(buffer).Slice(startIndex, length)
  +    override __.Dispose(disposing) =
  +        disposed <- true
  +        if disposing then
  +            buffer <- null
  +        reader.Dispose()
  +    override db.Finalize() = db.Dispose(false)
   
   /// A data structure that supports efficient and copy-free access to a read-only sequence of characters.
   /// It is not thread-safe.
  
positive.code:     /// The default buffer size is 256 characters. If the specified size is not positive, the default is used.
      /// Also, the character stream must be disposed afterwards.
      let ofTextReaderEx bufferSize textReader =
          let buffer =
              if bufferSize > 0 then
                  bufferSize
              else
                  defaultBufferSize
              |> Array.zeroCreate
          {
              Reader = textReader
              Buffer = buffer
              BufferStartingIndex = 0UL
              NextReadIndex = 0UL
          } |> DynamicBlock |> create
  
      /// Creates a `CharStream` from a `TextReader`.
      /// It must be disposed afterwards.
  
negative.code:         }
  
      /// Creates a `CharStream` from a `ReadOnlyMemory` of characters.
      let ofReadOnlyMemory mem =
          let source = new StaticBlockSource(mem)
          create source
  
      /// Creates a `CharStream` from a string.
      let ofString (x: string) = x.AsMemory() |> ofReadOnlyMemory
  