File: src\Microsoft.DotNet.Wpf\src\Shared\MS\Internal\CharacterBuffer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
//---------------------------------------------------------------------------
//
 
// 
//
// Description: Definition of readonly character memory buffer
// 
//
//  
//
//
//
//---------------------------------------------------------------------------
 
using System.Collections;
using System.Text;
using System.Runtime.InteropServices;
 
namespace MS.Internal
{
    /// <summary>
    /// Abstraction of a readonly character buffer
    /// </summary>
    internal abstract class CharacterBuffer : IList<char>
    {
        /// <summary>
        /// Get fixed address of the character buffer
        /// </summary>
        public abstract unsafe char* GetCharacterPointer();
 
        /// <summary>
        /// Get fixed address of the character buffer and Pin if necessary.
        /// Note: This call should only be used when we know that we are not pinning 
        /// memory for a long time so as not to fragment the heap.
        /// </summary>
        public abstract IntPtr PinAndGetCharacterPointer(int offset, out GCHandle gcHandle);
 
        /// <summary>
        /// This is a matching call for PinAndGetCharacterPointer to unpin the memory.
        /// </summary>
        public abstract void UnpinCharacterPointer(GCHandle gcHandle);
 
 
        /// <summary>
        /// Add character buffer's content to a StringBuilder
        /// </summary>
        /// <param name="stringBuilder">string builder to add content to</param>
        /// <param name="characterOffset">character offset to first character to append</param>
        /// <param name="length">number of character appending</param>
        /// <returns>string builder</returns>
        public abstract void AppendToStringBuilder(
            StringBuilder   stringBuilder,
            int             characterOffset,
            int             length
            );
 
        #region IList<char> Members
 
        public int IndexOf(char item)
        {
            for (int i = 0; i < Count; ++i)
            {
                if (item == this[i])
                    return i;
            }
            return -1;
        }
 
        public void Insert(int index, char item)
        {
            // CharacterBuffer is read only.
            throw new NotSupportedException();
        }
 
        public abstract char this[int index]
        {
            get;
            set;
        }
 
        public void RemoveAt(int index)
        {
            // CharacterBuffer is read only.
            throw new NotSupportedException();
        }
 
        #endregion
 
        #region ICollection<char> Members
 
        public void Add(char item)
        {
            // CharacterBuffer is read only.
            throw new NotSupportedException();
        }
 
        public void Clear()
        {
            // CharacterBuffer is read only.
            throw new NotSupportedException();
        }
 
        public bool Contains(char item)
        {
            return IndexOf(item) != -1;
        }
 
        public void CopyTo(char[] array, int arrayIndex)
        {
            for (int i = 0; i < Count; ++i)
            {
                array[arrayIndex + i] = this[i];
            }
        }
 
        public abstract int Count
        {
            get;
        }
 
        public bool IsReadOnly
        {
            get { return true; }
        }
 
        public bool Remove(char item)
        {
            // CharacterBuffer is read only.
            throw new NotSupportedException();
        }
 
        #endregion
 
        #region IEnumerable<char> Members
 
        IEnumerator<char> IEnumerable<char>.GetEnumerator()
        {
            for (int i = 0; i < Count; ++i)
                yield return this[i];
        }
 
        #endregion
 
        #region IEnumerable Members
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable<char>)this).GetEnumerator();
        }
 
        #endregion
    }
 
 
 
    /// <summary>
    /// Character memory buffer implemented by managed character array
    /// </summary>
    internal sealed class CharArrayCharacterBuffer : CharacterBuffer
    {
        private char[]      _characterArray;
 
 
        /// <summary>
        /// Creating a character memory buffer from character array
        /// </summary>
        /// <param name="characterArray">character array</param>
        public CharArrayCharacterBuffer(
            char[]  characterArray
            )
        {
            ArgumentNullException.ThrowIfNull(characterArray);
 
            _characterArray = characterArray;
        }
 
 
        /// <summary>
        /// Read a character from buffer at the specified character index
        /// </summary>
        public override char this[int characterOffset]
        {
            get { return _characterArray[characterOffset]; }
            set { throw new NotSupportedException(); }
        }
 
 
        /// <summary>
        /// Buffer character length
        /// </summary>
        public override int Count
        {
            get { return _characterArray.Length; }
        }
 
 
        /// <summary>
        /// Get fixed address of the character buffer
        /// </summary>
        public override unsafe char* GetCharacterPointer()
        {
            // Even though we could allocate GCHandle for this purpose, we would need
            // to manage how to release them appropriately. It is even worse if we
            // consider performance implication of doing so. In typical UI scenario,
            // there are so many string objects running around in GC heap. Getting 
            // GCHandle for every one of them is very expensive and demote GC's ability
            // to compact its heap. 
            return null;
        }
 
        /// <summary>
        /// Get fixed address of the character buffer and Pin if necessary.
        /// Note: This call should only be used when we know that we are not pinning 
        /// memory for a long time so as not to fragment the heap.
        public override unsafe IntPtr PinAndGetCharacterPointer(int offset, out GCHandle gcHandle)
        {
            gcHandle = GCHandle.Alloc(_characterArray, GCHandleType.Pinned);
            return new IntPtr(((char*)gcHandle.AddrOfPinnedObject().ToPointer()) + offset);
        }
        
        /// <summary>
        /// This is a matching call for PinAndGetCharacterPointer to unpin the memory.
        /// </summary>
        public override void UnpinCharacterPointer(GCHandle gcHandle)
        {
            gcHandle.Free();
        }
 
        /// <summary>
        /// Add character buffer's content to a StringBuilder
        /// </summary>
        /// <param name="stringBuilder">string builder to add content to</param>
        /// <param name="characterOffset">index to first character in the buffer to append</param>
        /// <param name="characterLength">number of character appending</param>
        public override void AppendToStringBuilder(
            StringBuilder   stringBuilder,
            int             characterOffset,
            int             characterLength
            )
        {
            Debug.Assert(characterOffset >= 0 && characterOffset < _characterArray.Length, "Invalid character index");
 
            if (    characterLength < 0 
                ||  characterOffset + characterLength > _characterArray.Length)
            {
                characterLength = _characterArray.Length - characterOffset;
            }
 
            stringBuilder.Append(_characterArray, characterOffset, characterLength);
        }
    }
 
 
 
    /// <summary>
    /// Character buffer implemented by string
    /// </summary>
    internal sealed class StringCharacterBuffer : CharacterBuffer
    {
        private string      _string;
 
 
        /// <summary>
        /// Creating a character buffer from string
        /// </summary>
        /// <param name="characterString">character string</param>
        public StringCharacterBuffer(
            string  characterString
            )
        {
            ArgumentNullException.ThrowIfNull(characterString);
 
            _string = characterString;
        }
 
 
        /// <summary>
        /// Read a character from buffer at the specified character index
        /// </summary>
        public override char this[int characterOffset]
        {
            get { return _string[characterOffset]; }
            set { throw new NotSupportedException(); }
        }
 
 
        /// <summary>
        /// Buffer character length
        /// </summary>
        public override int Count
        {
            get { return _string.Length; }
        }
 
 
        /// <summary>
        /// Get fixed address of the character buffer
        /// </summary>
        public override unsafe char* GetCharacterPointer()
        {
            // Even though we could allocate GCHandle for this purpose, we would need
            // to manage how to release them appropriately. It is even worse if we
            // consider performance implication of doing so. In typical UI scenario,
            // there are so many string objects running around in GC heap. Getting 
            // GCHandle for every one of them is very expensive and demote GC's ability
            // to compact its heap. 
            return null;
        }
 
        /// <summary>
        /// Get fixed address of the character buffer and Pin if necessary.
        /// Note: This call should only be used when we know that we are not pinning 
        /// memory for a long time so as not to fragment the heap.
        public override unsafe IntPtr PinAndGetCharacterPointer(int offset, out GCHandle gcHandle)
        {
            gcHandle = GCHandle.Alloc(_string, GCHandleType.Pinned);
            return new IntPtr(((char*)gcHandle.AddrOfPinnedObject().ToPointer()) + offset);
        }
 
        /// <summary>
        /// This is a matching call for PinAndGetCharacterPointer to unpin the memory.
        /// </summary>
        public override void UnpinCharacterPointer(GCHandle gcHandle)
        {
            gcHandle.Free();
        }
 
 
        /// <summary>
        /// Add character buffer's content to a StringBuilder
        /// </summary>
        /// <param name="stringBuilder">string builder to add content to</param>
        /// <param name="characterOffset">index to first character in the buffer to append</param>
        /// <param name="characterLength">number of character appending</param>
        public override void AppendToStringBuilder(
            StringBuilder   stringBuilder,
            int             characterOffset,
            int             characterLength
            )
        {
            Debug.Assert(characterOffset >= 0 && characterOffset < _string.Length, "Invalid character index");
 
            if (    characterLength < 0 
                ||  characterOffset + characterLength > _string.Length)
            {
                characterLength = _string.Length - characterOffset;
            }
 
            stringBuilder.Append(_string, characterOffset, characterLength);
        }
    }
 
 
 
    /// <summary>
    /// Character buffer implemented as unsafe pointer to character string
    /// </summary>
    internal sealed unsafe class UnsafeStringCharacterBuffer : CharacterBuffer
    {
        private char*   _unsafeString;
 
        private int     _length;
 
 
        /// <summary>
        /// Creating a character buffer from an unsafe pointer to character string
        /// </summary>
        /// <param name="characterString">unsafe pointer to character string</param>
        /// <param name="length">number of valid characters referenced by the unsafe pointer</param>
        public UnsafeStringCharacterBuffer(
            char*   characterString,
            int     length
            )
        {
            if (characterString == null)
            {
                throw new ArgumentNullException(nameof(characterString));
            }
 
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length);
 
            _unsafeString = characterString;
            _length = length;
        }
 
 
        /// <summary>
        /// Read a character from buffer at the specified character index
        /// </summary>
        public override char this[int characterOffset]
        {
            get {
                ArgumentOutOfRangeException.ThrowIfNegative(characterOffset);
                ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(characterOffset, _length);
 
                return _unsafeString[characterOffset];
            }
            set { throw new NotSupportedException(); }
        }
 
 
        /// <summary>
        /// Buffer character length
        /// </summary>
        public override int Count
        {
            get { return _length; }
        }
 
 
        /// <summary>
        /// Get fixed address of the character buffer
        /// </summary>
        public override unsafe char* GetCharacterPointer()
        {
            return _unsafeString;
        }
 
        /// <summary>
        /// Get fixed address of the character buffer and Pin if necessary.
        /// Note: This call should only be used when we know that we are not pinning 
        /// memory for a long time so as not to fragment the heap.
        public override IntPtr PinAndGetCharacterPointer(int offset, out GCHandle gcHandle)
        {
            gcHandle = new GCHandle();
            return new IntPtr(_unsafeString + offset);
        }
 
        /// <summary>
        /// This is a matching call for PinAndGetCharacterPointer to unpin the memory.
        /// </summary>
        public override void UnpinCharacterPointer(GCHandle gcHandle)
        {
        }
 
 
        /// <summary>
        /// Add character buffer's content to a StringBuilder
        /// </summary>
        /// <param name="stringBuilder">string builder to add content to</param>
        /// <param name="characterOffset">index to first character in the buffer to append</param>
        /// <param name="characterLength">number of character appending</param>
        public override  void AppendToStringBuilder(
            StringBuilder   stringBuilder,
            int             characterOffset,
            int             characterLength
            )
        {
 
            ArgumentOutOfRangeException.ThrowIfNegative(characterOffset);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(characterOffset, _length);
 
            ArgumentOutOfRangeException.ThrowIfNegative(characterLength);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(characterLength, _length - characterOffset);
 
            stringBuilder.Append(new string(_unsafeString, characterOffset, characterLength));
        }
    }
}