| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786 |
- // 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.Buffers;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Globalization;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
- using System.Text;
- using Internal.Runtime.CompilerServices;
- namespace System
- {
- // The String class represents a static string of characters. Many of
- // the string methods perform some type of transformation on the current
- // instance and return the result as a new string. As with arrays, character
- // positions (indices) are zero-based.
- [Serializable]
- [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
- public sealed partial class String : IComparable, IEnumerable, IConvertible, IEnumerable<char>, IComparable<string>, IEquatable<string>, ICloneable
- {
- // String constructors
- // These are special. The implementation methods for these have a different signature from the
- // declared constructors.
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern String(char[] value);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private string Ctor(char[] value)
- {
- if (value == null || value.Length == 0)
- return Empty;
- string result = FastAllocateString(value.Length);
- unsafe
- {
- fixed (char* dest = &result._firstChar, source = value)
- wstrcpy(dest, source, value.Length);
- }
- return result;
- }
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern String(char[] value, int startIndex, int length);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private string Ctor(char[] value, int startIndex, int length)
- {
- if (value == null)
- throw new ArgumentNullException(nameof(value));
- if (startIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
- if (startIndex > value.Length - length)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- if (length == 0)
- return Empty;
- string result = FastAllocateString(length);
- unsafe
- {
- fixed (char* dest = &result._firstChar, source = value)
- wstrcpy(dest, source + startIndex, length);
- }
- return result;
- }
- [CLSCompliant(false)]
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern unsafe String(char* value);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(char* ptr)
- {
- if (ptr == null)
- return Empty;
- int count = wcslen(ptr);
- if (count == 0)
- return Empty;
- string result = FastAllocateString(count);
- fixed (char* dest = &result._firstChar)
- wstrcpy(dest, ptr, count);
- return result;
- }
- [CLSCompliant(false)]
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern unsafe String(char* value, int startIndex, int length);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(char* ptr, int startIndex, int length)
- {
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
- if (startIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
- char* pStart = ptr + startIndex;
- // overflow check
- if (pStart < ptr)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_PartialWCHAR);
- if (length == 0)
- return Empty;
- if (ptr == null)
- throw new ArgumentOutOfRangeException(nameof(ptr), SR.ArgumentOutOfRange_PartialWCHAR);
- string result = FastAllocateString(length);
- fixed (char* dest = &result._firstChar)
- wstrcpy(dest, pStart, length);
- return result;
- }
- [CLSCompliant(false)]
- [MethodImpl(MethodImplOptions.InternalCall)]
- public extern unsafe String(sbyte* value);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(sbyte* value)
- {
- byte* pb = (byte*)value;
- if (pb == null)
- return Empty;
- int numBytes = new ReadOnlySpan<byte>((byte*)value, int.MaxValue).IndexOf<byte>(0);
- // Check for overflow
- if (numBytes < 0)
- throw new ArgumentException(SR.Arg_MustBeNullTerminatedString);
- return CreateStringForSByteConstructor(pb, numBytes);
- }
- [CLSCompliant(false)]
- [MethodImpl(MethodImplOptions.InternalCall)]
- public extern unsafe String(sbyte* value, int startIndex, int length);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(sbyte* value, int startIndex, int length)
- {
- if (startIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
- if (value == null)
- {
- if (length == 0)
- return Empty;
- throw new ArgumentNullException(nameof(value));
- }
- byte* pStart = (byte*)(value + startIndex);
- // overflow check
- if (pStart < value)
- throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_PartialWCHAR);
- return CreateStringForSByteConstructor(pStart, length);
- }
- // Encoder for String..ctor(sbyte*) and String..ctor(sbyte*, int, int)
- private static unsafe string CreateStringForSByteConstructor(byte *pb, int numBytes)
- {
- Debug.Assert(numBytes >= 0);
- Debug.Assert(pb <= (pb + numBytes));
- if (numBytes == 0)
- return Empty;
- #if PLATFORM_WINDOWS
- int numCharsRequired = Interop.Kernel32.MultiByteToWideChar(Interop.Kernel32.CP_ACP, Interop.Kernel32.MB_PRECOMPOSED, pb, numBytes, (char*)null, 0);
- if (numCharsRequired == 0)
- throw new ArgumentException(SR.Arg_InvalidANSIString);
- string newString = FastAllocateString(numCharsRequired);
- fixed (char *pFirstChar = &newString._firstChar)
- {
- numCharsRequired = Interop.Kernel32.MultiByteToWideChar(Interop.Kernel32.CP_ACP, Interop.Kernel32.MB_PRECOMPOSED, pb, numBytes, pFirstChar, numCharsRequired);
- }
- if (numCharsRequired == 0)
- throw new ArgumentException(SR.Arg_InvalidANSIString);
- return newString;
- #else
- return Encoding.UTF8.GetString(pb, numBytes);
- #endif
- }
- [CLSCompliant(false)]
- [MethodImpl(MethodImplOptions.InternalCall)]
- public extern unsafe String(sbyte* value, int startIndex, int length, Encoding enc);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(sbyte* value, int startIndex, int length, Encoding enc)
- {
- if (enc == null)
- return new string(value, startIndex, length);
- if (length < 0)
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
- if (startIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_StartIndex);
- if (value == null)
- {
- if (length == 0)
- return Empty;
- throw new ArgumentNullException(nameof(value));
- }
- byte* pStart = (byte*)(value + startIndex);
- // overflow check
- if (pStart < value)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_PartialWCHAR);
- return enc.GetString(new ReadOnlySpan<byte>(pStart, length));
- }
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern String(char c, int count);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private string Ctor(char c, int count)
- {
- if (count <= 0)
- {
- if (count == 0)
- return Empty;
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
- }
- string result = FastAllocateString(count);
- if (c != '\0') // Fast path null char string
- {
- unsafe
- {
- fixed (char* dest = &result._firstChar)
- {
- uint cc = (uint)((c << 16) | c);
- uint* dmem = (uint*)dest;
- if (count >= 4)
- {
- count -= 4;
- do
- {
- dmem[0] = cc;
- dmem[1] = cc;
- dmem += 2;
- count -= 4;
- } while (count >= 0);
- }
- if ((count & 2) != 0)
- {
- *dmem = cc;
- dmem++;
- }
- if ((count & 1) != 0)
- ((char*)dmem)[0] = c;
- }
- }
- }
- return result;
- }
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- public extern String(ReadOnlySpan<char> value);
- #if PROJECTN
- [DependencyReductionRoot]
- #endif
- #if !CORECLR
- static
- #endif
- private unsafe string Ctor(ReadOnlySpan<char> value)
- {
- if (value.Length == 0)
- return Empty;
- string result = FastAllocateString(value.Length);
- fixed (char* dest = &result._firstChar, src = &MemoryMarshal.GetReference(value))
- wstrcpy(dest, src, value.Length);
- return result;
- }
- public static string Create<TState>(int length, TState state, SpanAction<char, TState> action)
- {
- if (action == null)
- throw new ArgumentNullException(nameof(action));
- if (length <= 0)
- {
- if (length == 0)
- return Empty;
- throw new ArgumentOutOfRangeException(nameof(length));
- }
- string result = FastAllocateString(length);
- action(new Span<char>(ref result.GetRawStringData(), length), state);
- return result;
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator ReadOnlySpan<char>(string value) =>
- value != null ? new ReadOnlySpan<char>(ref value.GetRawStringData(), value.Length) : default;
- public object Clone()
- {
- return this;
- }
- public static unsafe string Copy(string str)
- {
- if (str == null)
- throw new ArgumentNullException(nameof(str));
- string result = FastAllocateString(str.Length);
- fixed (char* dest = &result._firstChar, src = &str._firstChar)
- wstrcpy(dest, src, str.Length);
- return result;
- }
- // Converts a substring of this string to an array of characters. Copies the
- // characters of this string beginning at position sourceIndex and ending at
- // sourceIndex + count - 1 to the character array buffer, beginning
- // at destinationIndex.
- //
- public unsafe void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
- {
- if (destination == null)
- throw new ArgumentNullException(nameof(destination));
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NegativeCount);
- if (sourceIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index);
- if (count > Length - sourceIndex)
- throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_IndexCount);
- if (destinationIndex > destination.Length - count || destinationIndex < 0)
- throw new ArgumentOutOfRangeException(nameof(destinationIndex), SR.ArgumentOutOfRange_IndexCount);
- fixed (char* src = &_firstChar, dest = destination)
- wstrcpy(dest + destinationIndex, src + sourceIndex, count);
- }
- // Returns the entire string as an array of characters.
- public unsafe char[] ToCharArray()
- {
- if (Length == 0)
- return Array.Empty<char>();
- char[] chars = new char[Length];
- fixed (char* src = &_firstChar, dest = &chars[0])
- wstrcpy(dest, src, Length);
- return chars;
- }
- // Returns a substring of this string as an array of characters.
- //
- public unsafe char[] ToCharArray(int startIndex, int length)
- {
- // Range check everything.
- if (startIndex < 0 || startIndex > Length || startIndex > Length - length)
- throw new ArgumentOutOfRangeException(nameof(startIndex), SR.ArgumentOutOfRange_Index);
- if (length <= 0)
- {
- if (length == 0)
- return Array.Empty<char>();
- throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_Index);
- }
- char[] chars = new char[length];
- fixed (char* src = &_firstChar, dest = &chars[0])
- wstrcpy(dest, src + startIndex, length);
- return chars;
- }
- [NonVersionable]
- public static bool IsNullOrEmpty(string value)
- {
- // Using 0u >= (uint)value.Length rather than
- // value.Length == 0 as it will elide the bounds check to
- // the first char: value[0] if that is performed following the test
- // for the same test cost.
- // Ternary operator returning true/false prevents redundant asm generation:
- // https://github.com/dotnet/coreclr/issues/914
- return (value == null || 0u >= (uint)value.Length) ? true : false;
- }
- public static bool IsNullOrWhiteSpace(string value)
- {
- if (value == null) return true;
- for (int i = 0; i < value.Length; i++)
- {
- if (!char.IsWhiteSpace(value[i])) return false;
- }
- return true;
- }
- internal ref char GetRawStringData() => ref _firstChar;
- // Helper for encodings so they can talk to our buffer directly
- // stringLength must be the exact size we'll expect
- internal static unsafe string CreateStringFromEncoding(
- byte* bytes, int byteLength, Encoding encoding)
- {
- Debug.Assert(bytes != null);
- Debug.Assert(byteLength >= 0);
- // Get our string length
- int stringLength = encoding.GetCharCount(bytes, byteLength, null);
- Debug.Assert(stringLength >= 0, "stringLength >= 0");
- // They gave us an empty string if they needed one
- // 0 bytelength might be possible if there's something in an encoder
- if (stringLength == 0)
- return Empty;
- string s = FastAllocateString(stringLength);
- fixed (char* pTempChars = &s._firstChar)
- {
- int doubleCheck = encoding.GetChars(bytes, byteLength, pTempChars, stringLength, null);
- Debug.Assert(stringLength == doubleCheck,
- "Expected encoding.GetChars to return same length as encoding.GetCharCount");
- }
- return s;
- }
- // This is only intended to be used by char.ToString.
- // It is necessary to put the code in this class instead of Char, since _firstChar is a private member.
- // Making _firstChar internal would be dangerous since it would make it much easier to break String's immutability.
- internal static string CreateFromChar(char c)
- {
- string result = FastAllocateString(1);
- result._firstChar = c;
- return result;
- }
- internal static string CreateFromChar(char c1, char c2)
- {
- string result = FastAllocateString(2);
- result._firstChar = c1;
- Unsafe.Add(ref result._firstChar, 1) = c2;
- return result;
- }
- internal static unsafe void wstrcpy(char* dmem, char* smem, int charCount)
- {
- Buffer.Memmove((byte*)dmem, (byte*)smem, ((uint)charCount) * 2);
- }
- // Returns this string.
- public override string ToString()
- {
- return this;
- }
- // Returns this string.
- public string ToString(IFormatProvider provider)
- {
- return this;
- }
- public CharEnumerator GetEnumerator()
- {
- return new CharEnumerator(this);
- }
- IEnumerator<char> IEnumerable<char>.GetEnumerator()
- {
- return new CharEnumerator(this);
- }
- IEnumerator IEnumerable.GetEnumerator()
- {
- return new CharEnumerator(this);
- }
- /// <summary>
- /// Returns an enumeration of <see cref="Rune"/> from this string.
- /// </summary>
- /// <remarks>
- /// Invalid sequences will be represented in the enumeration by <see cref="Rune.ReplacementChar"/>.
- /// </remarks>
- public StringRuneEnumerator EnumerateRunes()
- {
- return new StringRuneEnumerator(this);
- }
- internal static unsafe int wcslen(char* ptr)
- {
- char* end = ptr;
- // First make sure our pointer is aligned on a word boundary
- int alignment = IntPtr.Size - 1;
- // If ptr is at an odd address (e.g. 0x5), this loop will simply iterate all the way
- while (((uint)end & (uint)alignment) != 0)
- {
- if (*end == 0) goto FoundZero;
- end++;
- }
- #if !BIT64
- // The following code is (somewhat surprisingly!) significantly faster than a naive loop,
- // at least on x86 and the current jit.
- // The loop condition below works because if "end[0] & end[1]" is non-zero, that means
- // neither operand can have been zero. If is zero, we have to look at the operands individually,
- // but we hope this going to fairly rare.
- // In general, it would be incorrect to access end[1] if we haven't made sure
- // end[0] is non-zero. However, we know the ptr has been aligned by the loop above
- // so end[0] and end[1] must be in the same word (and therefore page), so they're either both accessible, or both not.
- while ((end[0] & end[1]) != 0 || (end[0] != 0 && end[1] != 0))
- {
- end += 2;
- }
- Debug.Assert(end[0] == 0 || end[1] == 0);
- if (end[0] != 0) end++;
- #else // !BIT64
- // Based on https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
- // 64-bit implementation: process 1 ulong (word) at a time
- // What we do here is add 0x7fff from each of the
- // 4 individual chars within the ulong, using MagicMask.
- // If the char > 0 and < 0x8001, it will have its high bit set.
- // We then OR with MagicMask, to set all the other bits.
- // This will result in all bits set (ulong.MaxValue) for any
- // char that fits the above criteria, and something else otherwise.
- // Note that for any char > 0x8000, this will be a false
- // positive and we will fallback to the slow path and
- // check each char individually. This is OK though, since
- // we optimize for the common case (ASCII chars, which are < 0x80).
- // NOTE: We can access a ulong a time since the ptr is aligned,
- // and therefore we're only accessing the same word/page. (See notes
- // for the 32-bit version above.)
- const ulong MagicMask = 0x7fff7fff7fff7fff;
- while (true)
- {
- ulong word = *(ulong*)end;
- word += MagicMask; // cause high bit to be set if not zero, and <= 0x8000
- word |= MagicMask; // set everything besides the high bits
- if (word == ulong.MaxValue) // 0xffff...
- {
- // all of the chars have their bits set (and therefore none can be 0)
- end += 4;
- continue;
- }
- // at least one of them didn't have their high bit set!
- // go through each char and check for 0.
- if (end[0] == 0) goto EndAt0;
- if (end[1] == 0) goto EndAt1;
- if (end[2] == 0) goto EndAt2;
- if (end[3] == 0) goto EndAt3;
- // if we reached here, it was a false positive-- just continue
- end += 4;
- }
- EndAt3: end++;
- EndAt2: end++;
- EndAt1: end++;
- EndAt0:
- #endif // !BIT64
- FoundZero:
- Debug.Assert(*end == 0);
- int count = (int)(end - ptr);
- #if BIT64
- // Check for overflow
- if (ptr + count != end)
- throw new ArgumentException(SR.Arg_MustBeNullTerminatedString);
- #else
- Debug.Assert(ptr + count == end);
- #endif
- return count;
- }
- //
- // IConvertible implementation
- //
- public TypeCode GetTypeCode()
- {
- return TypeCode.String;
- }
- bool IConvertible.ToBoolean(IFormatProvider provider)
- {
- return Convert.ToBoolean(this, provider);
- }
- char IConvertible.ToChar(IFormatProvider provider)
- {
- return Convert.ToChar(this, provider);
- }
- sbyte IConvertible.ToSByte(IFormatProvider provider)
- {
- return Convert.ToSByte(this, provider);
- }
- byte IConvertible.ToByte(IFormatProvider provider)
- {
- return Convert.ToByte(this, provider);
- }
- short IConvertible.ToInt16(IFormatProvider provider)
- {
- return Convert.ToInt16(this, provider);
- }
- ushort IConvertible.ToUInt16(IFormatProvider provider)
- {
- return Convert.ToUInt16(this, provider);
- }
- int IConvertible.ToInt32(IFormatProvider provider)
- {
- return Convert.ToInt32(this, provider);
- }
- uint IConvertible.ToUInt32(IFormatProvider provider)
- {
- return Convert.ToUInt32(this, provider);
- }
- long IConvertible.ToInt64(IFormatProvider provider)
- {
- return Convert.ToInt64(this, provider);
- }
- ulong IConvertible.ToUInt64(IFormatProvider provider)
- {
- return Convert.ToUInt64(this, provider);
- }
- float IConvertible.ToSingle(IFormatProvider provider)
- {
- return Convert.ToSingle(this, provider);
- }
- double IConvertible.ToDouble(IFormatProvider provider)
- {
- return Convert.ToDouble(this, provider);
- }
- decimal IConvertible.ToDecimal(IFormatProvider provider)
- {
- return Convert.ToDecimal(this, provider);
- }
- DateTime IConvertible.ToDateTime(IFormatProvider provider)
- {
- return Convert.ToDateTime(this, provider);
- }
- object IConvertible.ToType(Type type, IFormatProvider provider)
- {
- return Convert.DefaultToType((IConvertible)this, type, provider);
- }
- // Normalization Methods
- // These just wrap calls to Normalization class
- public bool IsNormalized()
- {
- return IsNormalized(NormalizationForm.FormC);
- }
- public bool IsNormalized(NormalizationForm normalizationForm)
- {
- #if CORECLR
- if (this.IsFastSort())
- {
- // If its FastSort && one of the 4 main forms, then its already normalized
- if (normalizationForm == NormalizationForm.FormC ||
- normalizationForm == NormalizationForm.FormKC ||
- normalizationForm == NormalizationForm.FormD ||
- normalizationForm == NormalizationForm.FormKD)
- return true;
- }
- #endif
- return Normalization.IsNormalized(this, normalizationForm);
- }
- public string Normalize()
- {
- return Normalize(NormalizationForm.FormC);
- }
- public string Normalize(NormalizationForm normalizationForm)
- {
- #if CORECLR
- if (this.IsAscii())
- {
- // If its FastSort && one of the 4 main forms, then its already normalized
- if (normalizationForm == NormalizationForm.FormC ||
- normalizationForm == NormalizationForm.FormKC ||
- normalizationForm == NormalizationForm.FormD ||
- normalizationForm == NormalizationForm.FormKD)
- return this;
- }
- #endif
- return Normalization.Normalize(this, normalizationForm);
- }
- }
- }
|