Add string support to ReadOnlyMemory<char>
authorStephen Toub <stoub@microsoft.com>
Tue, 7 Nov 2017 22:00:08 +0000 (17:00 -0500)
committerStephen Toub <stoub@microsoft.com>
Wed, 8 Nov 2017 14:40:46 +0000 (09:40 -0500)
src/mscorlib/shared/System/Buffers/OwnedMemory.cs
src/mscorlib/shared/System/Memory.cs
src/mscorlib/shared/System/MemoryDebugView.cs
src/mscorlib/shared/System/ReadOnlyMemory.cs
src/mscorlib/shared/System/Span.NonGeneric.cs
src/mscorlib/src/System/ThrowHelper.cs

index f75a29a..1167670 100644 (file)
@@ -21,7 +21,7 @@ namespace System.Buffers
                 {
                     ThrowHelper.ThrowObjectDisposedException(nameof(OwnedMemory<T>), ExceptionResource.Memory_ThrowIfDisposed);
                 }
-                return new Memory<T>(this, 0, Length);
+                return new Memory<T>(owner: this, 0, Length);
             }
         }
 
@@ -50,4 +50,4 @@ namespace System.Buffers
         public abstract bool Release();
 
     }
-}
\ No newline at end of file
+}
index ecb33e8..37d6b5f 100644 (file)
@@ -3,12 +3,11 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
-using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
-using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
 using System.Diagnostics;
-using System.Runtime;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
+using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
 
 namespace System
 {
@@ -19,10 +18,10 @@ namespace System
         // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
         // as code uses Unsafe.As to cast between them.
 
-        // The highest order bit of _index is used to discern whether _arrayOrOwnedMemory is an array or an owned memory
-        // if (_index >> 31) == 1, object _arrayOrOwnedMemory is an OwnedMemory<T>
-        // else, object _arrayOrOwnedMemory is a T[]
-        private readonly object _arrayOrOwnedMemory;
+        // The highest order bit of _index is used to discern whether _object is an array or an owned memory
+        // if (_index >> 31) == 1, object _object is an OwnedMemory<T>
+        // else, object _object is a T[].
+        private readonly object _object;
         private readonly int _index;
         private readonly int _length;
 
@@ -43,7 +42,7 @@ namespace System
             if (default(T) == null && array.GetType() != typeof(T[]))
                 ThrowHelper.ThrowArrayTypeMismatchException();
 
-            _arrayOrOwnedMemory = array;
+            _object = array;
             _index = 0;
             _length = array.Length;
         }
@@ -71,7 +70,7 @@ namespace System
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
 
-            _arrayOrOwnedMemory = array;
+            _object = array;
             _index = start;
             _length = length;
         }
@@ -85,11 +84,20 @@ namespace System
             if (index < 0 || length < 0)
                 ThrowHelper.ThrowArgumentOutOfRangeException();
 
-            _arrayOrOwnedMemory = owner;
+            _object = owner;
             _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with RemoveOwnedFlagBitMask
             _length = length;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private Memory(object obj, int index, int length)
+        {
+            // No validation performed; caller must provide any necessary validation.
+            _object = obj;
+            _index = index;
+            _length = length;
+        }
+
         /// <summary>
         /// Defines an implicit conversion of an array to a <see cref="Memory{T}"/>
         /// </summary>
@@ -135,11 +143,11 @@ namespace System
         public Memory<T> Slice(int start)
         {
             if ((uint)start > (uint)_length)
-                ThrowHelper.ThrowArgumentOutOfRangeException();
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+            }
 
-            if (_index < 0)
-                return new Memory<T>((OwnedMemory<T>)_arrayOrOwnedMemory, (_index & RemoveOwnedFlagBitMask) + start, _length - start);
-            return new Memory<T>((T[])_arrayOrOwnedMemory, _index + start, _length - start);
+            return new Memory<T>(_object, _index + start, _length - start);
         }
 
         /// <summary>
@@ -154,11 +162,11 @@ namespace System
         public Memory<T> Slice(int start, int length)
         {
             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
+            {
                 ThrowHelper.ThrowArgumentOutOfRangeException();
-                
-            if (_index < 0)
-                return new Memory<T>((OwnedMemory<T>)_arrayOrOwnedMemory, (_index & RemoveOwnedFlagBitMask) + start, length);
-            return new Memory<T>((T[])_arrayOrOwnedMemory, _index + start, length);
+            }
+            
+            return new Memory<T>(_object, _index + start, length);
         }
 
         /// <summary>
@@ -170,8 +178,8 @@ namespace System
             get
             {
                 if (_index < 0)
-                    return ((OwnedMemory<T>)_arrayOrOwnedMemory).Span.Slice(_index & RemoveOwnedFlagBitMask, _length);
-                return new Span<T>((T[])_arrayOrOwnedMemory, _index, _length);
+                    return ((OwnedMemory<T>)_object).Span.Slice(_index & RemoveOwnedFlagBitMask, _length);
+                return new Span<T>((T[])_object, _index, _length);
             }
         }
 
@@ -182,12 +190,12 @@ namespace System
             {
                 if (_index < 0)
                 {
-                    memoryHandle = ((OwnedMemory<T>)_arrayOrOwnedMemory).Pin();
+                    memoryHandle = ((OwnedMemory<T>)_object).Pin();
                     memoryHandle.AddOffset((_index & RemoveOwnedFlagBitMask) * Unsafe.SizeOf<T>());
                 }
                 else
                 {
-                    var array = (T[])_arrayOrOwnedMemory;
+                    var array = (T[])_object;
                     var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
                     void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
                     memoryHandle = new MemoryHandle(null, pointer, handle);
@@ -197,8 +205,8 @@ namespace System
             {
                 if (_index < 0)
                 {
-                    ((OwnedMemory<T>)_arrayOrOwnedMemory).Retain();
-                    memoryHandle = new MemoryHandle((OwnedMemory<T>)_arrayOrOwnedMemory);
+                    ((OwnedMemory<T>)_object).Retain();
+                    memoryHandle = new MemoryHandle((OwnedMemory<T>)_object);
                 }
                 else
                 {
@@ -216,7 +224,7 @@ namespace System
         {
             if (_index < 0)
             {
-                if (((OwnedMemory<T>)_arrayOrOwnedMemory).TryGetArray(out var segment))
+                if (((OwnedMemory<T>)_object).TryGetArray(out var segment))
                 {
                     arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (_index & RemoveOwnedFlagBitMask), _length);
                     return true;
@@ -224,7 +232,7 @@ namespace System
             }
             else
             {
-                arraySegment = new ArraySegment<T>((T[])_arrayOrOwnedMemory, _index, _length);
+                arraySegment = new ArraySegment<T>((T[])_object, _index, _length);
                 return true;
             }
 
@@ -263,7 +271,7 @@ namespace System
         public bool Equals(Memory<T> other)
         {
             return
-                _arrayOrOwnedMemory == other._arrayOrOwnedMemory &&
+                _object == other._object &&
                 _index == other._index &&
                 _length == other._length;
         }
@@ -271,7 +279,7 @@ namespace System
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override int GetHashCode()
         {
-            return CombineHashCodes(_arrayOrOwnedMemory.GetHashCode(), (_index & RemoveOwnedFlagBitMask).GetHashCode(), _length.GetHashCode());
+            return CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode());
         }
 
         private static int CombineHashCodes(int left, int right)
index 761d558..2155691 100644 (file)
@@ -33,8 +33,15 @@ namespace System
                     Array.Copy(segment.Array, segment.Offset, array, 0, array.Length);
                     return array;
                 }
+
+                if (typeof(T) == typeof(char) &&
+                    ((ReadOnlyMemory<char>)(object)_memory).TryGetString(out string text, out int start, out int length))
+                {
+                    return (T[])(object)text.Substring(start, length).ToCharArray();
+                }
+
                 return Array.Empty<T>();
             }
         }
     }
-}
\ No newline at end of file
+}
index f28a958..5240a37 100644 (file)
@@ -3,15 +3,18 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Buffers;
-using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
-using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
 using System.Diagnostics;
-using System.Runtime;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute;
+using EditorBrowsableState = System.ComponentModel.EditorBrowsableState;
 
 namespace System
 {
+    /// <summary>
+    /// Represents a contiguous region of memory, similar to <see cref="ReadOnlySpan{T}"/>.
+    /// Unlike <see cref="ReadOnlySpan{T}"/>, it is not a byref-like type.
+    /// </summary>
     [DebuggerDisplay("{DebuggerDisplay,nq}")]
     [DebuggerTypeProxy(typeof(MemoryDebugView<>))]
     public readonly struct ReadOnlyMemory<T>
@@ -19,10 +22,10 @@ namespace System
         // NOTE: With the current implementation, Memory<T> and ReadOnlyMemory<T> must have the same layout,
         // as code uses Unsafe.As to cast between them.
 
-        // The highest order bit of _index is used to discern whether _arrayOrOwnedMemory is an array or an owned memory
-        // if (_index >> 31) == 1, object _arrayOrOwnedMemory is an OwnedMemory<T>
-        // else, object _arrayOrOwnedMemory is a T[]
-        private readonly object _arrayOrOwnedMemory;
+        // The highest order bit of _index is used to discern whether _object is an array/string or an owned memory
+        // if (_index >> 31) == 1, _object is an OwnedMemory<T>
+        // else, _object is a T[] or string
+        private readonly object _object;
         private readonly int _index;
         private readonly int _length;
 
@@ -41,7 +44,7 @@ namespace System
             if (array == null)
                 ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
 
-            _arrayOrOwnedMemory = array;
+            _object = array;
             _index = 0;
             _length = array.Length;
         }
@@ -67,22 +70,21 @@ namespace System
             if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
                 ThrowHelper.ThrowArgumentOutOfRangeException();
 
-            _arrayOrOwnedMemory = array;
+            _object = array;
             _index = start;
             _length = length;
         }
-        
-        // Constructor for internal use only.
+
+        /// <summary>Creates a new memory over the existing object, start, and length.  No validation is performed.</summary>
+        /// <param name="obj">The target object.</param>
+        /// <param name="start">The index at which to begin the memory.</param>
+        /// <param name="length">The number of items in the memory.</param>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal ReadOnlyMemory(OwnedMemory<T> owner, int index, int length)
+        internal ReadOnlyMemory(object obj, int start, int length)
         {
-            if (owner == null)
-                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.ownedMemory);
-            if (index < 0 || length < 0)
-                ThrowHelper.ThrowArgumentOutOfRangeException();
-
-            _arrayOrOwnedMemory = owner;
-            _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with RemoveOwnedFlagBitMask
+            // No validation performed; caller must provide any necessary validation.
+            _object = obj;
+            _index = start;
             _length = length;
         }
 
@@ -125,11 +127,11 @@ namespace System
         public ReadOnlyMemory<T> Slice(int start)
         {
             if ((uint)start > (uint)_length)
-                ThrowHelper.ThrowArgumentOutOfRangeException();
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+            }
 
-            if (_index < 0)
-                return new ReadOnlyMemory<T>((OwnedMemory<T>)_arrayOrOwnedMemory, (_index & RemoveOwnedFlagBitMask) + start, _length - start);
-            return new ReadOnlyMemory<T>((T[])_arrayOrOwnedMemory, _index + start, _length - start);
+            return new ReadOnlyMemory<T>(_object, _index + start, _length - start);
         }
 
         /// <summary>
@@ -144,11 +146,11 @@ namespace System
         public ReadOnlyMemory<T> Slice(int start, int length)
         {
             if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
-                ThrowHelper.ThrowArgumentOutOfRangeException();
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+            }
 
-            if (_index < 0)
-                return new ReadOnlyMemory<T>((OwnedMemory<T>)_arrayOrOwnedMemory, (_index & RemoveOwnedFlagBitMask) + start, length);
-            return new ReadOnlyMemory<T>((T[])_arrayOrOwnedMemory, _index + start, length);
+            return new ReadOnlyMemory<T>(_object, _index + start, length);
         }
 
         /// <summary>
@@ -160,11 +162,25 @@ namespace System
             get
             {
                 if (_index < 0)
-                    return ((OwnedMemory<T>)_arrayOrOwnedMemory).Span.Slice(_index & RemoveOwnedFlagBitMask, _length);
-                return new ReadOnlySpan<T>((T[])_arrayOrOwnedMemory, _index, _length);
+                {
+                    return ((OwnedMemory<T>)_object).Span.Slice(_index & RemoveOwnedFlagBitMask, _length);
+                }
+                else if (typeof(T) == typeof(char) && _object is string s)
+                {
+                    return new ReadOnlySpan<T>(ref Unsafe.As<char, T>(ref s.GetRawStringData()), s.Length).Slice(_index, _length);
+                }
+                else
+                {
+                    return new ReadOnlySpan<T>((T[])_object, _index, _length);
+                }
             }
         }
 
+        /// <summary>Creates a handle for the memory.</summary>
+        /// <param name="pin">
+        /// If pin is true, the GC will not move the array until the returned <see cref="MemoryHandle"/>
+        /// is disposed, enabling the memory's address can be taken and used.
+        /// </param>
         public unsafe MemoryHandle Retain(bool pin = false)
         {
             MemoryHandle memoryHandle;
@@ -172,12 +188,18 @@ namespace System
             {
                 if (_index < 0)
                 {
-                    memoryHandle = ((OwnedMemory<T>)_arrayOrOwnedMemory).Pin();
+                    memoryHandle = ((OwnedMemory<T>)_object).Pin();
                     memoryHandle.AddOffset((_index & RemoveOwnedFlagBitMask) * Unsafe.SizeOf<T>());
                 }
+                else if (typeof(T) == typeof(char) && _object is string s)
+                {
+                    GCHandle handle = GCHandle.Alloc(s, GCHandleType.Pinned);
+                    void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref s.GetRawStringData()), _index);
+                    memoryHandle = new MemoryHandle(null, pointer, handle);
+                }
                 else
                 {
-                    var array = (T[])_arrayOrOwnedMemory;
+                    var array = (T[])_object;
                     var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
                     void* pointer = Unsafe.Add<T>(Unsafe.AsPointer(ref array.GetRawSzArrayData()), _index);
                     memoryHandle = new MemoryHandle(null, pointer, handle);
@@ -187,8 +209,8 @@ namespace System
             {
                 if (_index < 0)
                 {
-                    ((OwnedMemory<T>)_arrayOrOwnedMemory).Retain();
-                    memoryHandle = new MemoryHandle((OwnedMemory<T>)_arrayOrOwnedMemory);
+                    ((OwnedMemory<T>)_object).Retain();
+                    memoryHandle = new MemoryHandle((OwnedMemory<T>)_object);
                 }
                 else
                 {
@@ -207,7 +229,7 @@ namespace System
         {
             if (_index < 0)
             {
-                if (((OwnedMemory<T>)_arrayOrOwnedMemory).TryGetArray(out var segment))
+                if (((OwnedMemory<T>)_object).TryGetArray(out var segment))
                 {
                     arraySegment = new ArraySegment<T>(segment.Array, segment.Offset + (_index & RemoveOwnedFlagBitMask), _length);
                     return true;
@@ -215,11 +237,15 @@ namespace System
             }
             else
             {
-                arraySegment = new ArraySegment<T>((T[])_arrayOrOwnedMemory, _index, _length);
-                return true;
+                T[] arr = _object as T[];
+                if (typeof(T) != typeof(char) || arr != null)
+                {
+                    arraySegment = new ArraySegment<T>(arr, _index, _length);
+                    return true;
+                }
             }
 
-            arraySegment = default(ArraySegment<T>);
+            arraySegment = default;
             return false;
         }
 
@@ -230,6 +256,7 @@ namespace System
         /// </summary>
         public T[] ToArray() => Span.ToArray();
 
+        /// <summary>Determines whether the specified object is equal to the current object.</summary>
         [EditorBrowsable(EditorBrowsableState.Never)]
         public override bool Equals(object obj)
         {
@@ -254,15 +281,16 @@ namespace System
         public bool Equals(ReadOnlyMemory<T> other)
         {
             return
-                _arrayOrOwnedMemory == other._arrayOrOwnedMemory &&
+                _object == other._object &&
                 _index == other._index &&
                 _length == other._length;
         }
 
-        [EditorBrowsable( EditorBrowsableState.Never)]
+        /// <summary>Returns the hash code for this <see cref="ReadOnlyMemory{T}"/></summary>
+        [EditorBrowsable(EditorBrowsableState.Never)]
         public override int GetHashCode()
         {
-            return CombineHashCodes(_arrayOrOwnedMemory.GetHashCode(), (_index & RemoveOwnedFlagBitMask).GetHashCode(), _length.GetHashCode());
+            return CombineHashCodes(_object.GetHashCode(), _index.GetHashCode(), _length.GetHashCode());
         }
         
         private static int CombineHashCodes(int left, int right)
@@ -275,5 +303,16 @@ namespace System
             return CombineHashCodes(CombineHashCodes(h1, h2), h3);
         }
 
+        /// <summary>Gets the state of the memory as individual fields.</summary>
+        /// <param name="start">The offset.</param>
+        /// <param name="length">The count.</param>
+        /// <returns>The object.</returns>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal object GetObjectStartLength(out int start, out int length)
+        {
+            start = _index;
+            length = _length;
+            return _object;
+        }
     }
 }
index 0e8bfaa..da995c2 100644 (file)
@@ -15,10 +15,47 @@ using nuint = System.UInt32;
 namespace System
 {
     /// <summary>
-    /// Extension methods and non-generic helpers for Span and ReadOnlySpan
+    /// Extension methods and non-generic helpers for Span, ReadOnlySpan, Memory, and ReadOnlyMemory.
     /// </summary>
     public static class Span
     {
+        /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary>
+        /// <param name="text">The target string.</param>
+        /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is a null reference (Nothing in Visual Basic).</exception>
+        public static ReadOnlyMemory<char> AsReadOnlyMemory(this string text)
+        {
+            if (text == null)
+            {
+                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text);
+            }
+
+            return new ReadOnlyMemory<char>(text, 0, text.Length);
+        }
+
+        /// <summary>Attempts to get the underlying <see cref="string"/> from a <see cref="ReadOnlyMemory{T}"/>.</summary>
+        /// <param name="readOnlyMemory">The memory that may be wrapping a <see cref="string"/> object.</param>
+        /// <param name="text">The string.</param>
+        /// <param name="start">The starting location in <paramref name="text"/>.</param>
+        /// <param name="length">The number of items in <paramref name="text"/>.</param>
+        /// <returns></returns>
+        public static bool TryGetString(this ReadOnlyMemory<char> readOnlyMemory, out string text, out int start, out int length)
+        {
+            if (readOnlyMemory.GetObjectStartLength(out int offset, out int count) is string s)
+            {
+                text = s;
+                start = offset;
+                length = count;
+                return true;
+            }
+            else
+            {
+                text = null;
+                start = 0;
+                length = 0;
+                return false;
+            }
+        }
+
         /// <summary>
         /// Casts a Span of one primitive type <typeparamref name="T"/> to Span of bytes.
         /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety.
index d3356ea..ef4955c 100644 (file)
@@ -432,7 +432,8 @@ namespace System
         keyValuePair,
         input,
         ownedMemory,
-        pointer
+        pointer,
+        start
     }
 
     //