サロゲートペアを考慮してSubstringできるやつ

using System;
using System.Collections.Generic;
using System.Text;

namespace Liberfy
{
	public sealed unsafe class TwStringInfo : IDisposable
	{
		#region fields

		private int* surrogateIndices;
		private int surrogateIndicesLength;

		#endregion

		#region properties

		public string String { get; private set; }

		public int Length { get; private set; }

		#endregion

		public TwStringInfo(string text)
		{
			if (text == null) return;

			var surrogateIndicesList = new LinkedList<int>();

			String = text.Normalize(NormalizationForm.FormC);

			char c;
			int Length = String.Length;

			int surrogateIndex;
			for (surrogateIndex = 0; surrogateIndex < Length; surrogateIndex++)
			{
				if ('\uD800' <= (c = String[surrogateIndex]) && c <= '\uDBFF')
				{
					surrogateIndicesList.AddLast(surrogateIndex);
					surrogateIndex++;
				}
			}

			surrogateIndicesLength = surrogateIndicesList.Count;

			if (surrogateIndicesLength > 0)
			{
				int* indices = stackalloc int[surrogateIndicesLength];

				LinkedListNode<int> node = surrogateIndicesList.First;

				for (int i = 0; i < surrogateIndicesLength; i++)
				{
					indices[i] = node.Value;
					node = node.Next;
				}

				surrogateIndices = indices;
				indices = null;

				surrogateIndicesList.Clear();
			}

			surrogateIndicesList = null;
		}

		public string Substring(int startIndex)
		{
			int substringStartIndex = startIndex;

			for (int i = 0; i < surrogateIndicesLength; i++)
			{
				if (surrogateIndices[i] < startIndex)
				{
					substringStartIndex++;
				}
				else
				{
					break;
				}
			}

			return String.Substring(substringStartIndex);
		}

		public string Substring(int startIndex, int length)
		{
			int i = 0;
			int substringStartIndex = startIndex;
			int substringLength = length;
			int totalLength = startIndex + length;

			for (; i < surrogateIndicesLength; i++)
			{
				if (surrogateIndices[i] < startIndex)
				{
					substringStartIndex++;
				}
				else
				{
					break;
				}
			}

			for (; i < surrogateIndicesLength; i++)
			{
				if (surrogateIndices[i] < totalLength)
				{
					substringLength++;
				}
				else
				{
					break;
				}
			}

			return String.Substring(substringStartIndex, substringLength);
		}

		#region IDisposable Support
		private bool disposedValue = false;

		private void Dispose(bool disposing)
		{
			if (!disposedValue)
			{
				if (disposing)
				{
					Length = 0;
					String = null;
				}
				else
				{
					surrogateIndices = null;
					surrogateIndicesLength = 0;
				}

				disposedValue = true;
			}
		}

		~TwStringInfo()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion
	}
}