diff options
Diffstat (limited to 'src/mscorlib/corefx/System/Runtime')
4 files changed, 73 insertions, 489 deletions
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs deleted file mode 100644 index 875009aee2..0000000000 --- a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Runtime.CompilerServices; - -namespace System.Runtime.InteropServices -{ - /// <summary> - /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements. - /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero. - /// - /// NativeBuffer utilizes a cache of heap buffers. - /// </summary> - /// <remarks> - /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle. - /// - /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke. - /// (Notably AddRef and ReleaseRef will be called by the interop layer.) - /// - /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap - /// handles or worse. - /// </remarks> - internal class NativeBuffer : IDisposable - { - private readonly static SafeHeapHandleCache s_handleCache = new SafeHeapHandleCache(); - private readonly static SafeHandle s_emptyHandle = new EmptySafeHandle(); - private SafeHeapHandle _handle; - private ulong _capacity; - - /// <summary> - /// Create a buffer with at least the specified initial capacity in bytes. - /// </summary> - public NativeBuffer(ulong initialMinCapacity = 0) - { - EnsureByteCapacity(initialMinCapacity); - } - - protected unsafe void* VoidPointer - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _handle == null ? null : _handle.DangerousGetHandle().ToPointer(); - } - } - - protected unsafe byte* BytePointer - { - get - { - return (byte*)VoidPointer; - } - } - - /// <summary> - /// Get the handle for the buffer. - /// </summary> - public SafeHandle GetHandle() - { - // Marshalling code will throw on null for SafeHandle - return _handle ?? s_emptyHandle; - } - - /// <summary> - /// The capacity of the buffer in bytes. - /// </summary> - public ulong ByteCapacity - { - get { return _capacity; } - } - - /// <summary> - /// Ensure capacity in bytes is at least the given minimum. - /// </summary> - /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> - /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set <paramref name="nameof(minCapacity)"/> to a value that is larger than the maximum addressable memory.</exception> - public void EnsureByteCapacity(ulong minCapacity) - { - if (_capacity < minCapacity) - { - Resize(minCapacity); - _capacity = minCapacity; - } - } - - public unsafe byte this[ulong index] - { - get - { - if (index >= _capacity) throw new ArgumentOutOfRangeException(); - return BytePointer[index]; - } - set - { - if (index >= _capacity) throw new ArgumentOutOfRangeException(); - BytePointer[index] = value; - } - } - - private unsafe void Resize(ulong byteLength) - { - if (byteLength == 0) - { - ReleaseHandle(); - return; - } - - if (_handle == null) - { - _handle = s_handleCache.Acquire(byteLength); - } - else - { - _handle.Resize(byteLength); - } - } - - private void ReleaseHandle() - { - if (_handle != null) - { - s_handleCache.Release(_handle); - _capacity = 0; - _handle = null; - } - } - - /// <summary> - /// Release the backing buffer - /// </summary> - public virtual void Free() - { - ReleaseHandle(); - } - - public void Dispose() - { - Free(); - } - - private sealed class EmptySafeHandle : SafeHandle - { - public EmptySafeHandle() : base(IntPtr.Zero, true) { } - - public override bool IsInvalid - { - get { return true; } - } - - protected override bool ReleaseHandle() - { - return true; - } - } - } -} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs deleted file mode 100644 index 92b3d980db..0000000000 --- a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Runtime.InteropServices -{ - /// <summary> - /// Handle for heap memory that allows tracking of capacity and reallocating. - /// </summary> - internal sealed class SafeHeapHandle : SafeBuffer - { - /// <summary> - /// Allocate a buffer of the given size if requested. - /// </summary> - /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> - /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> - /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> - public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true) - { - Resize(byteLength); - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - /// <summary> - /// Resize the buffer to the given size if requested. - /// </summary> - /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param> - /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception> - /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception> - public void Resize(ulong byteLength) - { - if (IsClosed) throw new ObjectDisposedException(nameof(SafeHeapHandle)); - - ulong originalLength = 0; - if (handle == IntPtr.Zero) - { - handle = Marshal.AllocHGlobal((IntPtr)byteLength); - } - else - { - originalLength = ByteLength; - - // This may or may not be the same handle, may realloc in place. If the - // handle changes Windows will deal with the old handle, trying to free it will - // cause an error. - handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength); - } - - if (handle == IntPtr.Zero) - { - // Only real plausible answer - throw new OutOfMemoryException(); - } - - if (byteLength > originalLength) - { - // Add pressure - ulong addedBytes = byteLength - originalLength; - if (addedBytes > long.MaxValue) - { - GC.AddMemoryPressure(long.MaxValue); - GC.AddMemoryPressure((long)(addedBytes - long.MaxValue)); - } - else - { - GC.AddMemoryPressure((long)addedBytes); - } - } - else - { - // Shrank or did nothing, release pressure if needed - RemoveMemoryPressure(originalLength - byteLength); - } - - Initialize(byteLength); - } - - private void RemoveMemoryPressure(ulong removedBytes) - { - if (removedBytes == 0) return; - - if (removedBytes > long.MaxValue) - { - GC.RemoveMemoryPressure(long.MaxValue); - GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue)); - } - else - { - GC.RemoveMemoryPressure((long)removedBytes); - } - } - - protected override bool ReleaseHandle() - { - if (handle != IntPtr.Zero) - { - RemoveMemoryPressure(ByteLength); - Marshal.FreeHGlobal(handle); - } - - handle = IntPtr.Zero; - return true; - } - } -} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs deleted file mode 100644 index 725076ed66..0000000000 --- a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs +++ /dev/null @@ -1,97 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading; - -namespace System.Runtime.InteropServices -{ - /// <summary> - /// Allows limited thread safe reuse of heap buffers to limit memory pressure. - /// - /// This cache does not ensure that multiple copies of handles are not released back into the cache. - /// </summary> - internal sealed class SafeHeapHandleCache : IDisposable - { - private readonly ulong _minSize; - private readonly ulong _maxSize; - - // internal for testing - internal readonly SafeHeapHandle[] _handleCache; - - /// <param name="minSize">Smallest buffer size to allocate in bytes.</param> - /// <param name="maxSize">The largest buffer size to cache in bytes.</param> - /// <param name="maxHandles">The maximum number of handles to cache.</param> - public SafeHeapHandleCache(ulong minSize = 64, ulong maxSize = 1024 * 2, int maxHandles = 0) - { - _minSize = minSize; - _maxSize = maxSize; - _handleCache = new SafeHeapHandle[maxHandles > 0 ? maxHandles : Environment.ProcessorCount * 4]; - } - - /// <summary> - /// Get a HeapHandle - /// </summary> - public SafeHeapHandle Acquire(ulong minSize = 0) - { - if (minSize < _minSize) minSize = _minSize; - - SafeHeapHandle handle = null; - - for (int i = 0; i < _handleCache.Length; i++) - { - handle = Interlocked.Exchange(ref _handleCache[i], null); - if (handle != null) break; - } - - if (handle != null) - { - // One possible future consideration is to attempt cycling through to - // find one that might already have sufficient capacity - if (handle.ByteLength < minSize) - handle.Resize(minSize); - } - else - { - handle = new SafeHeapHandle(minSize); - } - - return handle; - } - - /// <summary> - /// Give a HeapHandle back for potential reuse - /// </summary> - public void Release(SafeHeapHandle handle) - { - if (handle.ByteLength <= _maxSize) - { - for (int i = 0; i < _handleCache.Length; i++) - { - // Push the handles down, walking the last one off the end to keep - // the top of the "stack" fresh - handle = Interlocked.Exchange(ref _handleCache[i], handle); - if (handle == null) return; - } - } - - handle.Dispose(); - } - - public void Dispose() - { - Dispose(disposing: true); - } - - private void Dispose(bool disposing) - { - if (disposing && _handleCache != null) - { - foreach (SafeHeapHandle handle in _handleCache) - { - if (handle != null) handle.Dispose(); - } - } - } - } -} diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs index 29cef08b6c..fdd0b95590 100644 --- a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs +++ b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs @@ -2,69 +2,81 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Buffers; +using System.Runtime.CompilerServices; + namespace System.Runtime.InteropServices { /// <summary> - /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger - /// than a maximum size string to enable working with very large string arrays. Always makes ordinal - /// comparisons. + /// Buffer that deals in char size increments. Dispose to free memory. Always makes ordinal + /// comparisons. Not thread safe. /// /// A more performant replacement for StringBuilder when performing native interop. + /// + /// "No copy" valuetype. Has to be passed as "ref". + /// /// </summary> /// <remarks> /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle(). /// </remarks> - internal class StringBuffer : NativeBuffer + internal struct StringBuffer { - private uint _length; + private char[] _buffer; + private int _length; /// <summary> /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity /// includes the trailing null character. /// </summary> - public StringBuffer(uint initialCapacity = 0) - : base(initialCapacity * (ulong)sizeof(char)) + public StringBuffer(int initialCapacity) { + _buffer = ArrayPool<char>.Shared.Rent(initialCapacity); + _length = 0; } /// <summary> /// Get/set the character at the given index. /// </summary> /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception> - public unsafe char this[uint index] + public char this[int index] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); - return CharPointer[index]; + return _buffer[index]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index)); - CharPointer[index] = value; + _buffer[index] = value; } } /// <summary> + /// Underlying storage of the buffer. Used for interop. + /// </summary> + public char[] UnderlyingArray => _buffer; + + /// <summary> /// Character capacity of the buffer. Includes the count for the trailing null character. /// </summary> - public uint CharCapacity - { - get - { - ulong byteCapacity = ByteCapacity; - ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char); - return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity; - } - } + public int Capacity => _buffer.Length; /// <summary> /// Ensure capacity in characters is at least the given minimum. /// </summary> /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> - public void EnsureCharCapacity(uint minCapacity) + public void EnsureCapacity(int minCapacity) { - EnsureByteCapacity(minCapacity * (ulong)sizeof(char)); + if (minCapacity > Capacity) + { + char[] oldBuffer = _buffer; + _buffer = ArrayPool<char>.Shared.Rent(minCapacity); + Array.Copy(oldBuffer, 0, _buffer, 0, oldBuffer.Length); + ArrayPool<char>.Shared.Return(oldBuffer); + } } /// <summary> @@ -72,59 +84,32 @@ namespace System.Runtime.InteropServices /// This is where the usable data ends. /// </summary> /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception> - /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null).</exception> - public unsafe uint Length + /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is int.MaxValue (as space is implicitly reserved for the trailing null).</exception> + public int Length { get { return _length; } set { - if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length)); - // Null terminate - EnsureCharCapacity(value + 1); - CharPointer[value] = '\0'; + EnsureCapacity(checked(value + 1)); + _buffer[value] = '\0'; _length = value; } } /// <summary> - /// For use when the native api null terminates but doesn't return a length. - /// If no null is found, the length will not be changed. - /// </summary> - public unsafe void SetLengthToFirstNull() - { - char* buffer = CharPointer; - uint capacity = CharCapacity; - for (uint i = 0; i < capacity; i++) - { - if (buffer[i] == '\0') - { - _length = i; - break; - } - } - } - - internal unsafe char* CharPointer - { - get - { - return (char*)VoidPointer; - } - } - - /// <summary> /// True if the buffer contains the given character. /// </summary> public unsafe bool Contains(char value) { - char* start = CharPointer; - uint length = _length; - - for (uint i = 0; i < length; i++) + fixed (char* start = _buffer) { - if (*start++ == value) return true; + int length = _length; + for (int i = 0; i < length; i++) + { + if (start[i] == value) return true; + } } return false; @@ -136,7 +121,7 @@ namespace System.Runtime.InteropServices public bool StartsWith(string value) { if (value == null) throw new ArgumentNullException(nameof(value)); - if (_length < (uint)value.Length) return false; + if (_length < value.Length) return false; return SubstringEquals(value, startIndex: 0, count: value.Length); } @@ -150,27 +135,28 @@ namespace System.Runtime.InteropServices /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range /// of the buffer's length. /// </exception> - public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1) + public unsafe bool SubstringEquals(string value, int startIndex = 0, int count = -1) { if (value == null) return false; if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex)); - uint realCount = count == -1 ? _length - startIndex : (uint)count; + int realCount = count == -1 ? _length - startIndex : (int)count; if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); int length = value.Length; // Check the substring length against the input length - if (realCount != (uint)length) return false; + if (realCount != length) return false; fixed (char* valueStart = value) + fixed (char* bufferStart = _buffer) { - char* bufferStart = CharPointer + startIndex; + char* subStringStart = bufferStart + startIndex; + for (int i = 0; i < length; i++) { - // Note that indexing in this case generates faster code than trying to copy the pointer and increment it - if (*bufferStart++ != valueStart[i]) return false; + if (subStringStart[i] != valueStart[i]) return false; } } @@ -178,26 +164,6 @@ namespace System.Runtime.InteropServices } /// <summary> - /// Append the given string. - /// </summary> - /// <param name="value">The string to append.</param> - /// <param name="startIndex">The index in the input string to start appending from.</param> - /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param> - /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception> - /// <exception cref="ArgumentOutOfRangeException"> - /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range - /// of <paramref name="value"/> characters. - /// </exception> - public void Append(string value, int startIndex = 0, int count = -1) - { - CopyFrom( - bufferIndex: _length, - source: value, - sourceIndex: startIndex, - count: count); - } - - /// <summary> /// Append the given buffer. /// </summary> /// <param name="value">The buffer to append.</param> @@ -208,14 +174,13 @@ namespace System.Runtime.InteropServices /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range /// of <paramref name="value"/> characters. /// </exception> - public void Append(StringBuffer value, uint startIndex = 0) + public void Append(ref StringBuffer value, int startIndex = 0) { - if (value == null) throw new ArgumentNullException(nameof(value)); if (value.Length == 0) return; value.CopyTo( bufferIndex: startIndex, - destination: this, + destination: ref this, destinationIndex: _length, count: value.Length); } @@ -231,14 +196,13 @@ namespace System.Runtime.InteropServices /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range /// of <paramref name="value"/> characters. /// </exception> - public void Append(StringBuffer value, uint startIndex, uint count) + public void Append(ref StringBuffer value, int startIndex, int count) { - if (value == null) throw new ArgumentNullException(nameof(value)); if (count == 0) return; value.CopyTo( bufferIndex: startIndex, - destination: this, + destination: ref this, destinationIndex: _length, count: count); } @@ -252,29 +216,24 @@ namespace System.Runtime.InteropServices /// of <paramref name="value"/> characters. /// </exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception> - public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count) + public void CopyTo(int bufferIndex, ref StringBuffer destination, int destinationIndex, int count) { - if (destination == null) throw new ArgumentNullException(nameof(destination)); if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex)); if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count)); if (count == 0) return; - uint lastIndex = checked(destinationIndex + count); - if (destination._length < lastIndex) destination.Length = lastIndex; - - Buffer.MemoryCopy( - source: CharPointer + bufferIndex, - destination: destination.CharPointer + destinationIndex, - destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))), - sourceBytesToCopy: checked((long)count * sizeof(char))); + int lastIndex = checked(destinationIndex + count); + if (destination.Length < lastIndex) destination.Length = lastIndex; + + Array.Copy(UnderlyingArray, bufferIndex, destination.UnderlyingArray, destinationIndex, count); } /// <summary> /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of /// the buffer, will grow as necessary. /// </summary> - public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1) + public void CopyFrom(int bufferIndex, string source, int sourceIndex = 0, int count = -1) { if (source == null) throw new ArgumentNullException(nameof(source)); if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex)); @@ -283,32 +242,22 @@ namespace System.Runtime.InteropServices if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count)); if (count == 0) return; - uint lastIndex = bufferIndex + (uint)count; + int lastIndex = bufferIndex + (int)count; if (_length < lastIndex) Length = lastIndex; - fixed (char* content = source) - { - Buffer.MemoryCopy( - source: content + sourceIndex, - destination: CharPointer + bufferIndex, - destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))), - sourceBytesToCopy: (long)count * sizeof(char)); - } + source.CopyTo(sourceIndex, UnderlyingArray, bufferIndex, count); } /// <summary> /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed. /// </summary> - public unsafe void TrimEnd(char[] values) + public void TrimEnd(char[] values) { if (values == null || values.Length == 0 || _length == 0) return; - char* end = CharPointer + _length - 1; - - while (_length > 0 && Array.IndexOf(values, *end) >= 0) + while (_length > 0 && Array.IndexOf(values, _buffer[_length - 1]) >= 0) { Length = _length - 1; - end--; } } @@ -316,11 +265,9 @@ namespace System.Runtime.InteropServices /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw. /// </summary> /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception> - public unsafe override string ToString() + public override string ToString() { - if (_length == 0) return string.Empty; - if (_length > int.MaxValue) throw new InvalidOperationException(); - return new string(CharPointer, startIndex: 0, length: (int)_length); + return new string(_buffer, startIndex: 0, length: _length); } /// <summary> @@ -331,23 +278,23 @@ namespace System.Runtime.InteropServices /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length /// or count is greater than the maximum string size (int.MaxValue). /// </exception> - public unsafe string Substring(uint startIndex, int count = -1) + public string Substring(int startIndex, int count = -1) { if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex)); if (count < -1) throw new ArgumentOutOfRangeException(nameof(count)); - uint realCount = count == -1 ? _length - startIndex : (uint)count; + int realCount = count == -1 ? _length - startIndex : (int)count; if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count)); - if (realCount == 0) return string.Empty; // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting // index might be bigger than int we need to index ourselves. - return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount); + return new string(_buffer, startIndex: startIndex, length: realCount); } - public override void Free() + public void Free() { - base.Free(); + ArrayPool<char>.Shared.Return(_buffer); + _buffer = null; _length = 0; } } |