Search code examples
c#.net-corememory-pool

Convert string to IMemoryOwner / Copy string into a rented buffer


I have a custom memory sequence OwnedMemorySequence. I want to put a string inside. The issue is that I cannot get it to compile. Usually something like Encoding.UTF8.GetBytes and then copy that buffer into the rented buffer should work but in my case there are compile time errors.

var message = "Hello";

var buffer = MemoryPool<byte>.Shared.Rent(Encoding.UTF8.GetByteCount(message));
Encoding.UTF8.Convert(message, buffer.Memory, true, out _, out _, out _); // no such overload

var seq = new OwnedMemorySequence<byte>();
seq.Append(buffer);

Code

public sealed class OwnedMemorySequence<T> : IDisposable
{
    private readonly CollectionDisposable _disposable = new();
    private readonly MemorySequence<T> _sequence = new();

    public ReadOnlySequence<T> ReadOnlySequence => _sequence.ReadOnlySequence;

    public void Dispose()
    {
        _disposable.Dispose();
    }

    public OwnedMemorySequence<T> Append(IMemoryOwner<T> memoryOwner)
    {
        _disposable.Add(memoryOwner);
        _sequence.Append(memoryOwner.Memory);
        return this;
    }

    public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex)
    {
        return _sequence.CreateReadOnlySequence(firstBufferStartIndex, lastBufferEndIndex);
    }
}

public sealed class MemorySequence<T>
{
    private MemorySegment? _head;
    private MemorySegment? _tail;

    public ReadOnlySequence<T> ReadOnlySequence => CreateReadOnlySequence(0, _tail?.Memory.Length ?? 0);

    public MemorySequence<T> Append(ReadOnlyMemory<T> buffer)
    {
        if (_tail == null)
            _head = _tail = new MemorySegment(buffer, 0);
        else
            _tail = _tail.Append(buffer);
        return this;
    }

    public ReadOnlySequence<T> CreateReadOnlySequence(int firstBufferStartIndex, int lastBufferEndIndex)
    {
        return _tail == null ? new ReadOnlySequence<T>(Array.Empty<T>()) : new ReadOnlySequence<T>(_head!, firstBufferStartIndex, _tail, lastBufferEndIndex);
    }

    private sealed class MemorySegment : ReadOnlySequenceSegment<T>
    {
        public MemorySegment(ReadOnlyMemory<T> memory, long runningIndex)
        {
            Memory = memory;
            RunningIndex = runningIndex;
        }

        public MemorySegment Append(ReadOnlyMemory<T> nextMemory)
        {
            var next = new MemorySegment(nextMemory, RunningIndex + Memory.Length);
            Next = next;
            return next;
        }
    }
}

public static class MemoryOwnerExtensions
{
    /// <summary>
    ///     Rent a buffer from a pool with an exact length.
    /// </summary>
    /// <param name="pool">The <see cref="MemoryPool{T}" /> instance.</param>
    /// <param name="exactBufferSize">The exact size of the buffer.</param>
    public static IMemoryOwner<T> RentExact<T>(this MemoryPool<T> pool, int exactBufferSize)
    {
        if (pool == null) throw new ArgumentNullException(nameof(pool));

        var rented = pool.Rent(exactBufferSize);

        if (exactBufferSize == rented.Memory.Length)
            return rented;

        return new SliceOwner<T>(rented, 0, exactBufferSize);
    }

    /// <summary>
    ///     Wrap an existing <see cref="IMemoryOwner{T}" /> instance in a lightweight manner, but allow
    ///     the <see cref="IMemoryOwner{T}.Memory" /> member to have a different length.
    /// </summary>
    /// <param name="owner">The original instance.</param>
    /// <param name="start">The starting offset of the slice.</param>
    /// <param name="length">The length of the slice.</param>
    public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start, int length)
    {
        if (owner == null) throw new ArgumentNullException(nameof(owner));

        if (start == 0 && length == owner.Memory.Length)
            return owner;

        if ((uint)start >= (uint)owner.Memory.Length) throw new ArgumentOutOfRangeException(nameof(start));
        if ((uint)length > (uint)(owner.Memory.Length - start)) throw new ArgumentOutOfRangeException(nameof(length));

        return new SliceOwner<T>(owner, start, length);
    }

    /// <summary>
    ///     Wrap an existing <see cref="IMemoryOwner{T}" /> instance in a lightweight manner, but allow
    ///     the <see cref="IMemoryOwner{T}.Memory" /> member to have a different length.
    /// </summary>
    /// <param name="owner">The original instance.</param>
    /// <param name="start">The starting offset of the slice.</param>
    public static IMemoryOwner<T> Slice<T>(this IMemoryOwner<T> owner, int start)
    {
        if (owner == null) throw new ArgumentNullException(nameof(owner));

        if (start == 0)
            return owner;

        if ((uint)start >= (uint)owner.Memory.Length) throw new ArgumentOutOfRangeException(nameof(start));

        return new SliceOwner<T>(owner, start);
    }

    private sealed class SliceOwner<T> : IMemoryOwner<T>
    {
        private IMemoryOwner<T> _owner;

        public SliceOwner(IMemoryOwner<T> owner, int start, int length)
        {
            _owner = owner;
            Memory = _owner.Memory.Slice(start, length);
        }

        public SliceOwner(IMemoryOwner<T> owner, int start)
        {
            _owner = owner;
            Memory = _owner.Memory.Slice(start);
        }

        public Memory<T> Memory { get; private set; }

        public void Dispose()
        {
            if (_owner != null)
            {
                _owner.Dispose();
                _owner = null;
            }

            Memory = default;
        }
    }
}

Solution

  • Indeed, no such overload exists. Where did you look in the documentation to set that argument?

    Encoding.Convert Method

    Encoder have a similar method.

    Encoder.Convert Method

    EncodingExtensions.Convert Method

    using System;
    using System.Buffers;
    using System.Text;
    
    public class Program
    {
        public static void Main()
        {
            var message = "Hello";
    
            var buffer = MemoryPool<byte>.Shared.Rent(Encoding.UTF8.GetByteCount(message));
            Encoding.UTF8.GetEncoder().Convert(message, buffer.Memory.Span, true, out _, out _, out _);
    
            Console.WriteLine(Encoding.UTF8.GetString(buffer.Memory.Span));
        }
    }