--- /dev/null
+//
+// System.String.cs
+//
+// Authors:
+// Patrik Torstensson
+// Jeffrey Stedfast (fejj@ximian.com)
+// Dan Lewis (dihlewis@yahoo.co.uk)
+// Sebastien Pouliot <sebastien@ximian.com>
+// Marek Safar (marek.safar@seznam.cz)
+// Andreas Nahr (Classdevelopment@A-SoftTech.com)
+//
+// (C) 2001 Ximian, Inc. http://www.ximian.com
+// Copyright (C) 2004-2005 Novell (http://www.novell.com)
+// Copyright (c) 2012 Xamarin, Inc (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+//
+
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System
+{
+ partial class String
+ {
+ public int Length {
+ get {
+ return m_stringLength;
+ }
+ }
+
+ internal static unsafe int CompareOrdinalUnchecked (String strA, int indexA, int lenA, String strB, int indexB, int lenB)
+ {
+ if (strA == null) {
+ return strB == null ? 0 : -1;
+ }
+ if (strB == null) {
+ return 1;
+ }
+ int lengthA = Math.Min (lenA, strA.m_stringLength - indexA);
+ int lengthB = Math.Min (lenB, strB.m_stringLength - indexB);
+
+ if (lengthA == lengthB && indexA == indexB && Object.ReferenceEquals (strA, strB))
+ return 0;
+
+ fixed (char* aptr = strA, bptr = strB) {
+ char* ap = aptr + indexA;
+ char* end = ap + Math.Min (lengthA, lengthB);
+ char* bp = bptr + indexB;
+ while (ap < end) {
+ if (*ap != *bp)
+ return *ap - *bp;
+ ap++;
+ bp++;
+ }
+ return lengthA - lengthB;
+ }
+ }
+
+ public int IndexOf (char value, int startIndex, int count)
+ {
+ if (startIndex < 0 || startIndex > this.m_stringLength)
+ throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative and must be< 0");
+ if (count < 0)
+ throw new ArgumentOutOfRangeException ("count", "< 0");
+ if (startIndex > this.m_stringLength - count)
+ throw new ArgumentOutOfRangeException ("count", "startIndex + count > this.m_stringLength");
+
+ if ((startIndex == 0 && this.m_stringLength == 0) || (startIndex == this.m_stringLength) || (count == 0))
+ return -1;
+
+ return IndexOfUnchecked (value, startIndex, count);
+ }
+
+ internal unsafe int IndexOfUnchecked (char value, int startIndex, int count)
+ {
+ // It helps JIT compiler to optimize comparison
+ int value_32 = (int)value;
+
+ fixed (char* start = &m_firstChar) {
+ char* ptr = start + startIndex;
+ char* end_ptr = ptr + (count >> 3 << 3);
+
+ while (ptr != end_ptr) {
+ if (*ptr == value_32)
+ return (int)(ptr - start);
+ if (ptr[1] == value_32)
+ return (int)(ptr - start + 1);
+ if (ptr[2] == value_32)
+ return (int)(ptr - start + 2);
+ if (ptr[3] == value_32)
+ return (int)(ptr - start + 3);
+ if (ptr[4] == value_32)
+ return (int)(ptr - start + 4);
+ if (ptr[5] == value_32)
+ return (int)(ptr - start + 5);
+ if (ptr[6] == value_32)
+ return (int)(ptr - start + 6);
+ if (ptr[7] == value_32)
+ return (int)(ptr - start + 7);
+
+ ptr += 8;
+ }
+
+ end_ptr += count & 0x07;
+ while (ptr != end_ptr) {
+ if (*ptr == value_32)
+ return (int)(ptr - start);
+
+ ptr++;
+ }
+ return -1;
+ }
+ }
+
+ internal unsafe int IndexOfUnchecked (string value, int startIndex, int count)
+ {
+ int valueLen = value.Length;
+ if (count < valueLen)
+ return -1;
+
+ if (valueLen <= 1) {
+ if (valueLen == 1)
+ return IndexOfUnchecked (value[0], startIndex, count);
+ return startIndex;
+ }
+
+ fixed (char* thisptr = &m_firstChar, valueptr = value) {
+ char* ap = thisptr + startIndex;
+ char* thisEnd = ap + count - valueLen + 1;
+ while (ap != thisEnd) {
+ if (*ap == *valueptr) {
+ for (int i = 1; i < valueLen; i++) {
+ if (ap[i] != valueptr[i])
+ goto NextVal;
+ }
+ return (int)(ap - thisptr);
+ }
+ NextVal:
+ ap++;
+ }
+ }
+ return -1;
+ }
+
+ public int IndexOfAny (char [] anyOf, int startIndex, int count)
+ {
+ if (anyOf == null)
+ throw new ArgumentNullException ();
+ if (startIndex < 0 || startIndex > this.m_stringLength)
+ throw new ArgumentOutOfRangeException ();
+ if (count < 0 || startIndex > this.m_stringLength - count)
+ throw new ArgumentOutOfRangeException ("count", "Count cannot be negative, and startIndex + count must be less than m_stringLength of the string.");
+
+ return IndexOfAnyUnchecked (anyOf, startIndex, count);
+ }
+
+ unsafe int IndexOfAnyUnchecked (char[] anyOf, int startIndex, int count)
+ {
+ if (anyOf.Length == 0)
+ return -1;
+
+ if (anyOf.Length == 1)
+ return IndexOfUnchecked (anyOf[0], startIndex, count);
+
+ fixed (char* any = anyOf) {
+ int highest = *any;
+ int lowest = *any;
+
+ char* end_any_ptr = any + anyOf.Length;
+ char* any_ptr = any;
+ while (++any_ptr != end_any_ptr) {
+ if (*any_ptr > highest) {
+ highest = *any_ptr;
+ continue;
+ }
+
+ if (*any_ptr < lowest)
+ lowest = *any_ptr;
+ }
+
+ fixed (char* start = &m_firstChar) {
+ char* ptr = start + startIndex;
+ char* end_ptr = ptr + count;
+
+ while (ptr != end_ptr) {
+ if (*ptr > highest || *ptr < lowest) {
+ ptr++;
+ continue;
+ }
+
+ if (*ptr == *any)
+ return (int)(ptr - start);
+
+ any_ptr = any;
+ while (++any_ptr != end_any_ptr) {
+ if (*ptr == *any_ptr)
+ return (int)(ptr - start);
+ }
+
+ ptr++;
+ }
+ }
+ }
+ return -1;
+ }
+
+ public int LastIndexOf (char value, int startIndex, int count)
+ {
+ if (this.m_stringLength == 0)
+ return -1;
+
+ // >= for char (> for string)
+ if ((startIndex < 0) || (startIndex >= this.Length))
+ throw new ArgumentOutOfRangeException ("startIndex", "< 0 || >= this.Length");
+ if ((count < 0) || (count > this.Length))
+ throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
+ if (startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
+
+ return LastIndexOfUnchecked (value, startIndex, count);
+ }
+
+ internal unsafe int LastIndexOfUnchecked (char value, int startIndex, int count)
+ {
+ // It helps JIT compiler to optimize comparison
+ int value_32 = (int)value;
+
+ fixed (char* start = &m_firstChar) {
+ char* ptr = start + startIndex;
+ char* end_ptr = ptr - (count >> 3 << 3);
+
+ while (ptr != end_ptr) {
+ if (*ptr == value_32)
+ return (int)(ptr - start);
+ if (ptr[-1] == value_32)
+ return (int)(ptr - start) - 1;
+ if (ptr[-2] == value_32)
+ return (int)(ptr - start) - 2;
+ if (ptr[-3] == value_32)
+ return (int)(ptr - start) - 3;
+ if (ptr[-4] == value_32)
+ return (int)(ptr - start) - 4;
+ if (ptr[-5] == value_32)
+ return (int)(ptr - start) - 5;
+ if (ptr[-6] == value_32)
+ return (int)(ptr - start) - 6;
+ if (ptr[-7] == value_32)
+ return (int)(ptr - start) - 7;
+
+ ptr -= 8;
+ }
+
+ end_ptr -= count & 0x07;
+ while (ptr != end_ptr) {
+ if (*ptr == value_32)
+ return (int)(ptr - start);
+
+ ptr--;
+ }
+ return -1;
+ }
+ }
+
+ public int LastIndexOfAny (char [] anyOf, int startIndex, int count)
+ {
+ if (anyOf == null)
+ throw new ArgumentNullException ();
+ if (this.m_stringLength == 0)
+ return -1;
+
+ if ((startIndex < 0) || (startIndex >= this.Length))
+ throw new ArgumentOutOfRangeException ("startIndex", "< 0 || > this.Length");
+ if ((count < 0) || (count > this.Length))
+ throw new ArgumentOutOfRangeException ("count", "< 0 || > this.Length");
+ if (startIndex - count + 1 < 0)
+ throw new ArgumentOutOfRangeException ("startIndex - count + 1 < 0");
+
+ if (this.m_stringLength == 0)
+ return -1;
+
+ return LastIndexOfAnyUnchecked (anyOf, startIndex, count);
+ }
+
+ private unsafe int LastIndexOfAnyUnchecked (char [] anyOf, int startIndex, int count)
+ {
+ if (anyOf.Length == 1)
+ return LastIndexOfUnchecked (anyOf[0], startIndex, count);
+
+ fixed (char* start = &m_firstChar, testStart = anyOf) {
+ char* ptr = start + startIndex;
+ char* ptrEnd = ptr - count;
+ char* test;
+ char* testEnd = testStart + anyOf.Length;
+
+ while (ptr != ptrEnd) {
+ test = testStart;
+ while (test != testEnd) {
+ if (*test == *ptr)
+ return (int)(ptr - start);
+ test++;
+ }
+ ptr--;
+ }
+ return -1;
+ }
+ }
+
+ internal static int nativeCompareOrdinalEx (String strA, int indexA, String strB, int indexB, int count)
+ {
+ return CompareOrdinalUnchecked (strA, indexA, count, strB, indexB, count);
+ }
+
+ unsafe String ReplaceInternal (char oldChar, char newChar)
+ {
+#if !BOOTSTRAP_BASIC
+ if (this.m_stringLength == 0 || oldChar == newChar)
+ return this;
+#endif
+ int start_pos = IndexOfUnchecked (oldChar, 0, this.m_stringLength);
+#if !BOOTSTRAP_BASIC
+ if (start_pos == -1)
+ return this;
+#endif
+ if (start_pos < 4)
+ start_pos = 0;
+
+ string tmp = FastAllocateString (m_stringLength);
+ fixed (char* dest = tmp, src = &m_firstChar) {
+ if (start_pos != 0)
+ CharCopy (dest, src, start_pos);
+
+ char* end_ptr = dest + m_stringLength;
+ char* dest_ptr = dest + start_pos;
+ char* src_ptr = src + start_pos;
+
+ while (dest_ptr != end_ptr) {
+ if (*src_ptr == oldChar)
+ *dest_ptr = newChar;
+ else
+ *dest_ptr = *src_ptr;
+
+ ++src_ptr;
+ ++dest_ptr;
+ }
+ }
+ return tmp;
+ }
+
+ public String ReplaceInternal (String oldValue, String newValue)
+ {
+ // LAMESPEC: According to MSDN the following method is culture-sensitive but this seems to be incorrect
+ // LAMESPEC: Result is undefined if result Length is longer than maximum string Length
+
+ if (oldValue == null)
+ throw new ArgumentNullException ("oldValue");
+
+ if (oldValue.Length == 0)
+ throw new ArgumentException ("oldValue is the empty string.");
+
+ if (this.Length == 0)
+#if BOOTSTRAP_BASIC
+ throw new NotImplementedException ("BOOTSTRAP_BASIC");
+#else
+ return this;
+#endif
+ if (newValue == null)
+ newValue = Empty;
+
+ return ReplaceUnchecked (oldValue, newValue);
+ }
+
+ private unsafe String ReplaceUnchecked (String oldValue, String newValue)
+ {
+ if (oldValue.m_stringLength > m_stringLength)
+#if BOOTSTRAP_BASIC
+ throw new NotImplementedException ("BOOTSTRAP_BASIC");
+#else
+ return this;
+#endif
+
+ if (oldValue.m_stringLength == 1 && newValue.m_stringLength == 1) {
+ return Replace (oldValue[0], newValue[0]);
+ // ENHANCE: It would be possible to special case oldValue.m_stringLength == newValue.m_stringLength
+ // because the m_stringLength of the result would be this.m_stringLength and m_stringLength calculation unneccesary
+ }
+
+ const int maxValue = 200; // Allocate 800 byte maximum
+ int* dat = stackalloc int[maxValue];
+ fixed (char* source = &m_firstChar, replace = newValue) {
+ int i = 0, count = 0;
+ while (i < m_stringLength) {
+ int found = IndexOfUnchecked (oldValue, i, m_stringLength - i);
+ if (found < 0)
+ break;
+ else {
+ if (count < maxValue)
+ dat[count++] = found;
+ else
+ return ReplaceFallback (oldValue, newValue, maxValue);
+ }
+ i = found + oldValue.m_stringLength;
+ }
+ if (count == 0)
+#if BOOTSTRAP_BASIC
+ throw new NotImplementedException ("BOOTSTRAP_BASIC");
+#else
+ return this;
+#endif
+ int nlen = 0;
+ checked {
+ try {
+ nlen = this.m_stringLength + ((newValue.m_stringLength - oldValue.m_stringLength) * count);
+ } catch (OverflowException) {
+ throw new OutOfMemoryException ();
+ }
+ }
+ String tmp = FastAllocateString (nlen);
+
+ int curPos = 0, lastReadPos = 0;
+ fixed (char* dest = tmp) {
+ for (int j = 0; j < count; j++) {
+ int precopy = dat[j] - lastReadPos;
+ CharCopy (dest + curPos, source + lastReadPos, precopy);
+ curPos += precopy;
+ lastReadPos = dat[j] + oldValue.m_stringLength;
+ CharCopy (dest + curPos, replace, newValue.m_stringLength);
+ curPos += newValue.m_stringLength;
+ }
+ CharCopy (dest + curPos, source + lastReadPos, m_stringLength - lastReadPos);
+ }
+ return tmp;
+ }
+ }
+
+ private String ReplaceFallback (String oldValue, String newValue, int testedCount)
+ {
+ int lengthEstimate = this.m_stringLength + ((newValue.m_stringLength - oldValue.m_stringLength) * testedCount);
+ StringBuilder sb = new StringBuilder (lengthEstimate);
+ for (int i = 0; i < m_stringLength;) {
+ int found = IndexOfUnchecked (oldValue, i, m_stringLength - i);
+ if (found < 0) {
+ sb.Append (InternalSubString (i, m_stringLength - i));
+ break;
+ }
+ sb.Append (InternalSubString (i, found - i));
+ sb.Append (newValue);
+ i = found + oldValue.m_stringLength;
+ }
+ return sb.ToString ();
+
+ }
+
+ unsafe String PadHelper (int totalWidth, char paddingChar, bool isRightPadded)
+ {
+ if (totalWidth < 0)
+ throw new ArgumentOutOfRangeException ("totalWidth", "Non-negative number required");
+ if (totalWidth <= m_stringLength)
+#if BOOTSTRAP_BASIC
+ throw new NotImplementedException ("BOOTSTRAP_BASIC");
+#else
+ return this;
+#endif
+ string result = FastAllocateString (totalWidth);
+
+ fixed (char *dest = result, src = &m_firstChar) {
+ if (isRightPadded) {
+ CharCopy (dest, src, m_stringLength);
+ char *end = dest + totalWidth;
+ char *p = dest + m_stringLength;
+ while (p < end) {
+ *p++ = paddingChar;
+ }
+ } else {
+ char *p = dest;
+ char *end = p + totalWidth - m_stringLength;
+ while (p < end) {
+ *p++ = paddingChar;
+ }
+ CharCopy (p, src, m_stringLength);
+ }
+ }
+
+ return result;
+ }
+
+ internal bool StartsWithOrdinalUnchecked (String value)
+ {
+#if BOOTSTRAP_BASIC
+ throw new NotImplementedException ("BOOTSTRAP_BASIC");
+#else
+ return m_stringLength >= value.m_stringLength && CompareOrdinalUnchecked (this, 0, value.m_stringLength, value, 0, value.m_stringLength) == 0;
+#endif
+ }
+
+ internal unsafe bool IsAscii ()
+ {
+ fixed (char* src = &m_firstChar) {
+ char* end_ptr = src + m_stringLength;
+ char* str_ptr = src;
+
+ while (str_ptr != end_ptr) {
+ if (*str_ptr >= 0x80)
+ return false;
+
+ ++str_ptr;
+ }
+ }
+
+ return true;
+ }
+
+ internal bool IsFastSort ()
+ {
+ return false;
+ }
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ private extern static string InternalIsInterned (string str);
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ private extern static string InternalIntern (string str);
+
+ internal static unsafe void CharCopy (char *dest, char *src, int count) {
+ // Same rules as for memcpy, but with the premise that
+ // chars can only be aligned to even addresses if their
+ // enclosing types are correctly aligned
+ if ((((int)(byte*)dest | (int)(byte*)src) & 3) != 0) {
+ if (((int)(byte*)dest & 2) != 0 && ((int)(byte*)src & 2) != 0 && count > 0) {
+ ((short*)dest) [0] = ((short*)src) [0];
+ dest++;
+ src++;
+ count--;
+ }
+ if ((((int)(byte*)dest | (int)(byte*)src) & 2) != 0) {
+ Buffer.memcpy2 ((byte*)dest, (byte*)src, count * 2);
+ return;
+ }
+ }
+ Buffer.memcpy4 ((byte*)dest, (byte*)src, count * 2);
+ }
+
+ #region Runtime method-to-ir dependencies
+
+ /* helpers used by the runtime as well as above or eslewhere in corlib */
+ static unsafe void memset (byte *dest, int val, int len)
+ {
+ if (len < 8) {
+ while (len != 0) {
+ *dest = (byte)val;
+ ++dest;
+ --len;
+ }
+ return;
+ }
+ if (val != 0) {
+ val = val | (val << 8);
+ val = val | (val << 16);
+ }
+ // align to 4
+ int rest = (int)dest & 3;
+ if (rest != 0) {
+ rest = 4 - rest;
+ len -= rest;
+ do {
+ *dest = (byte)val;
+ ++dest;
+ --rest;
+ } while (rest != 0);
+ }
+ while (len >= 16) {
+ ((int*)dest) [0] = val;
+ ((int*)dest) [1] = val;
+ ((int*)dest) [2] = val;
+ ((int*)dest) [3] = val;
+ dest += 16;
+ len -= 16;
+ }
+ while (len >= 4) {
+ ((int*)dest) [0] = val;
+ dest += 4;
+ len -= 4;
+ }
+ // tail bytes
+ while (len > 0) {
+ *dest = (byte)val;
+ dest++;
+ len--;
+ }
+ }
+
+ static unsafe void memcpy (byte *dest, byte *src, int size)
+ {
+ Buffer.Memcpy (dest, src, size);
+ }
+ #endregion
+
+ // Certain constructors are redirected to CreateString methods with
+ // matching argument list. The this pointer should not be used.
+
+ private unsafe String CreateString (sbyte* value)
+ {
+ if (value == null)
+ return Empty;
+
+ byte* bytes = (byte*) value;
+ int length = 0;
+
+ try {
+ while (bytes++ [0] != 0)
+ length++;
+ } catch (NullReferenceException) {
+ throw new ArgumentOutOfRangeException ("ptr", "Value does not refer to a valid string.");
+ }
+
+ return CreateString (value, 0, length, null);
+ }
+
+ unsafe String CreateString (sbyte* value, int startIndex, int length)
+ {
+ return CreateString (value, startIndex, length, null);
+ }
+
+ unsafe string CreateString (char *value)
+ {
+ if (value == null)
+ return Empty;
+ char *p = value;
+ int i = 0;
+ while (*p != 0) {
+ ++i;
+ ++p;
+ }
+ string result = FastAllocateString (i);
+
+ if (i != 0) {
+ fixed (char *dest = result) {
+ CharCopy (dest, value, i);
+ }
+ }
+ return result;
+ }
+
+ unsafe string CreateString (char *value, int startIndex, int length)
+ {
+ if (length == 0)
+ return Empty;
+ if (value == null)
+ throw new ArgumentNullException ("value");
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException ("startIndex");
+ if (length < 0)
+ throw new ArgumentOutOfRangeException ("length");
+
+ string result = FastAllocateString (length);
+
+ fixed (char *dest = result) {
+ CharCopy (dest, value + startIndex, length);
+ }
+ return result;
+ }
+
+ unsafe string CreateString (char [] val, int startIndex, int length)
+ {
+ if (val == null)
+ throw new ArgumentNullException ("value");
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative.");
+ if (length < 0)
+ throw new ArgumentOutOfRangeException ("length", "Cannot be negative.");
+ if (startIndex > val.Length - length)
+ throw new ArgumentOutOfRangeException ("startIndex", "Cannot be negative, and should be less than length of string.");
+ if (length == 0)
+ return Empty;
+
+ string result = FastAllocateString (length);
+
+ fixed (char *dest = result, src = val) {
+ CharCopy (dest, src + startIndex, length);
+ }
+ return result;
+ }
+
+ unsafe string CreateString (char [] val)
+ {
+ if (val == null || val.Length == 0)
+ return Empty;
+ string result = FastAllocateString (val.Length);
+
+ fixed (char *dest = result, src = val) {
+ CharCopy (dest, src, val.Length);
+ }
+ return result;
+ }
+
+ unsafe string CreateString (char c, int count)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException ("count");
+ if (count == 0)
+ return Empty;
+ string result = FastAllocateString (count);
+ fixed (char *dest = result) {
+ char *p = dest;
+ char *end = p + count;
+ while (p < end) {
+ *p = c;
+ p++;
+ }
+ }
+ return result;
+ }
+
+ private unsafe String CreateString (sbyte* value, int startIndex, int length, Encoding enc)
+ {
+ if (length < 0)
+ throw new ArgumentOutOfRangeException ("length", "Non-negative number required.");
+ if (startIndex < 0)
+ throw new ArgumentOutOfRangeException ("startIndex", "Non-negative number required.");
+ if (value + startIndex < value)
+ throw new ArgumentOutOfRangeException ("startIndex", "Value, startIndex and length do not refer to a valid string.");
+
+ if (enc == null) {
+ if (value == null)
+ throw new ArgumentNullException ("value");
+ if (length == 0)
+ return Empty;
+
+ enc = Encoding.Default;
+ }
+
+ byte [] bytes = new byte [length];
+
+ if (length != 0)
+ fixed (byte* bytePtr = bytes)
+ try {
+ if (value == null)
+ throw new ArgumentOutOfRangeException ("ptr", "Value, startIndex and length do not refer to a valid string.");
+ memcpy (bytePtr, (byte*) (value + startIndex), length);
+ } catch (NullReferenceException) {
+ throw new ArgumentOutOfRangeException ("ptr", "Value, startIndex and length do not refer to a valid string.");
+ }
+
+ // GetString () is called even when length == 0
+ return enc.GetString (bytes);
+ }
+ }
+}
\ No newline at end of file