Introduction
What the article/code snippet does, why it's useful, the problem it solves etc.
Background
(Optional) Is there any background to this article that may be useful
such as an introduction to the basic ideas presented?
Using the code
A brief description of how to use the article or code. The
class names, the methods and properties, any tricks or tips.
Blocks of code should be set as style "Formatted"
like this:
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Class: String
**
**
** Purpose: Contains headers for the String class. Actual implementations
** are in String.cpp
**
**
===========================================================*/
namespace System {
using System.Text;
using System;
using System.Runtime.ConstrainedExecution;
using System.Globalization;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Win32;
using System.Runtime.InteropServices;
using va_list = System.ArgIterator;
//
// For Information on these methods, please see COMString.cpp
//
// 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. All comparison methods are
// implemented as a part of String. As with arrays, character positions
// (indices) are zero-based.
//
// When passing a null string into a constructor in VJ and VC, the null should be
// explicitly type cast to a String.
// For Example:
// String s = new String((String)null);
// Text.Out.WriteLine(s);
//
[System.Runtime.InteropServices.ComVisible(true)]
[Serializable] public sealed class String : IComparable, ICloneable, IConvertible, IEnumerable
#if GENERICS_WORK
, IComparable<string>, IEnumerable<char>, IEquatable<string>
#endif
{
//
//NOTE NOTE NOTE NOTE
//These fields map directly onto the fields in an EE StringObject. See object.h for the layout.
//
[NonSerialized]private int m_arrayLength;
[NonSerialized]private int m_stringLength;
[NonSerialized]private char m_firstChar;
//private static readonly char FmtMsgMarkerChar= //private static readonly char FmtMsgFmtCodeChar= //These are defined in Com99/src/vm/COMStringCommon.h and must be kept in [....].
private const int TrimHead = 0;
private const int TrimTail = 1;
private const int TrimBoth = 2;
// The Empty constant holds the empty string value.
//We need to call the String constructor so that the compiler doesn //Marking this as a literal would mean that it doesn //from native.
public static readonly String Empty = "";
//
//Native Static Methods
//
// Joins an array of strings together as one string with a separator between each original string.
//
public static String Join (String separator, String[] value) {
if (value==null) {
throw new ArgumentNullException("value");
}
return Join(separator, value, 0, value.Length);
}
#if WIN64
private const int charPtrAlignConst = 3;
private const int alignConst = 7;
#else
private const int charPtrAlignConst = 1;
private const int alignConst = 3;
#endif
internal char FirstChar { get { return m_firstChar; } }
// Joins an array of strings together as one string with a separator between each original string.
//
public unsafe static String Join(String separator, String[] value, int startIndex, int count) {
//Treat null as empty string.
if (separator == null) {
separator = String.Empty;
}
//Range check the array
if (value == null) {
throw new ArgumentNullException("value");
}
if (startIndex < 0) {
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
}
if (count < 0) {
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
}
if (startIndex > value.Length - count) {
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
}
//If count is 0, that skews a whole bunch of the calculations below, so just special case that.
if (count == 0) {
return String.Empty;
}
int jointLength = 0;
//Figure out the total length of the strings in value
int endIndex = startIndex + count - 1;
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
if (value[stringToJoinIndex] != null) {
jointLength += value[stringToJoinIndex].Length;
}
}
//Add enough room for the separator.
jointLength += (count - 1) * separator.Length;
// Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times
// and landed back in the positive range.) The input array might be modifed from other threads,
// so we have to do an overflow check before each append below anyway. Those overflows will get caught down there.
if ((jointLength < 0) || ((jointLength + 1) < 0) ) {
throw new OutOfMemoryException();
}
//If this is an empty string, just return.
if (jointLength == 0) {
return String.Empty;
}
string jointString = FastAllocateString( jointLength );
fixed (char * pointerToJointString = &jointString.m_firstChar) {
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength);
// Append the first string first and then append each following string prefixed by the separator.
charBuffer.AppendString( value[startIndex] );
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
charBuffer.AppendString( separator );
charBuffer.AppendString( value[stringToJoinIndex] );
}
BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == }
return jointString;
}
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern int nativeCompareOrdinal(String strA, String strB, bool bIgnoreCase);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern int nativeCompareOrdinalEx(String strA, int indexA, String strB, int indexB, int count);
//This will not work in case-insensitive mode for any character greater than 0x80.
//We [MethodImplAttribute(MethodImplOptions.InternalCall)]
unsafe internal static extern int nativeCompareOrdinalWC(String strA, char *strBChars, bool bIgnoreCase, out bool success);
//
// This is a helper method for the security team. They need to uppercase some strings (guaranteed to be less
// than 0x80) before security is fully initialized. Without security initialized, we can // from the assembly. This provides a workaround for that problem and should NOT be used anywhere else.
//
internal unsafe static string SmallCharToUpper(string strIn) {
BCLDebug.Assert(strIn != null, "strIn");
//
// Get the length and pointers to each of the buffers. Walk the length
// of the string and copy the characters from the inBuffer to the outBuffer,
// capitalizing it if necessary. We assert that all of our characters are
// less than 0x80.
//
int length = strIn.Length;
String strOut = FastAllocateString(length);
fixed (char * inBuff = &strIn.m_firstChar, outBuff = &strOut.m_firstChar) {
char c;
int upMask = ~0x20;
for(int i = 0; i < length; i++) {
c = inBuff[i];
BCLDebug.Assert((int)c < 0x80, "(int)c < 0x80");
//
// 0x20 is the difference between upper and lower characters in the lower
// 128 ASCII characters. And this bit off to make the chars uppercase.
//
if (c >= c = (char)((int)c & upMask);
}
outBuff[i] = c;
}
BCLDebug.Assert(outBuff[length]== }
return strOut;
}
//
//
// NATIVE INSTANCE METHODS
//
//
//
// Search/Query methods
//
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private unsafe static bool EqualsHelper(String strA, String strB)
{
int length = strA.Length;
if (length != strB.Length) return false;
fixed(char* ap = strA) fixed(char* bp = strB)
{
char* a = ap;
char* b = bp;
// unroll the loop
#if AMD64
// for AMD64 bit platform we unroll by 12 and
// check 3 qword at a time. This is less code
// than the 32 bit case and is shorter
// pathlength
while (length >= 12)
{
if (*(long*)a != *(long*)b) break;
if (*(long*)(a+4) != *(long*)(b+4)) break;
if (*(long*)(a+8) != *(long*)(b+8)) break;
a += 12; b += 12; length -= 12;
}
#else
while (length >= 10)
{
if (*(int*)a != *(int*)b) break;
if (*(int*)(a+2) != *(int*)(b+2)) break;
if (*(int*)(a+4) != *(int*)(b+4)) break;
if (*(int*)(a+6) != *(int*)(b+6)) break;
if (*(int*)(a+8) != *(int*)(b+8)) break;
a += 10; b += 10; length -= 10;
}
#endif
// This depends on the fact that the String objects are
// always zero terminated and that the terminating zero is not included
// in the length. For odd string sizes, the last compare will include
// the zero terminator.
while (length > 0)
{
if (*(int*)a != *(int*)b) break;
a += 2; b += 2; length -= 2;
}
return (length <= 0);
}
}
private unsafe static int CompareOrdinalHelper(String strA, String strB)
{
BCLDebug.Assert(strA != null && strB != null, "strings cannot be null!");
int length = Math.Min(strA.Length, strB.Length);
int diffOffset = -1;
fixed(char* ap = strA) fixed(char* bp = strB)
{
char* a = ap;
char* b = bp;
// unroll the loop
while (length >= 10)
{
if (*(int*)a != *(int*)b) {
diffOffset = 0;
break;
}
if (*(int*)(a+2) != *(int*)(b+2)) {
diffOffset = 2;
break;
}
if (*(int*)(a+4) != *(int*)(b+4)) {
diffOffset = 4;
break;
}
if (*(int*)(a+6) != *(int*)(b+6)) {
diffOffset = 6;
break;
}
if (*(int*)(a+8) != *(int*)(b+8)) {
diffOffset = 8;
break;
}
a += 10;
b += 10;
length -= 10;
}
if( diffOffset != -1) {
// we already see a difference in the unrolled loop above
a += diffOffset;
b += diffOffset;
int order;
if ( (order = (int)*a - (int)*b) != 0) {
return order;
}
BCLDebug.Assert( *(a+1) != *(b+1), "This byte must be different if we reach here!");
return ((int)*(a+1) - (int)*(b+1));
}
// now go back to slower code path and do comparison on 4 bytes one time.
// Following code also take advantage of the fact strings will
// use even numbers of characters (runtime will have a extra zero at the end.)
// so even if length is 1 here, we can still do the comparsion.
while (length > 0) {
if (*(int*)a != *(int*)b) {
break;
}
a += 2;
b += 2;
length -= 2;
}
if( length > 0) {
int c;
// found a different int on above loop
if ( (c = (int)*a - (int)*b) != 0) {
return c;
}
BCLDebug.Assert( *(a+1) != *(b+1), "This byte must be different if we reach here!");
return ((int)*(a+1) - (int)*(b+1));
}
// At this point, we have compared all the characters in at least one string.
// The longer string will be larger.
return strA.Length - strB.Length;
}
}
// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(Object obj) {
String str = obj as String;
if (str == null)
{
// exception will be thrown later for null this
if (this != null) return false;
}
return EqualsHelper(this, str);
}
// Determines whether two strings match.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public bool Equals(String value) {
if (value == null)
{
// exception will be thrown later for null this
if (this != null) return false;
}
return EqualsHelper(this, value);
}
public bool Equals(String value, StringComparison comparisonType) {
if( comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) {
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
if( (Object)this == (Object)value) {
return true;
}
if( (Object)value == null) {
return false;
}
switch (comparisonType) {
case StringComparison.CurrentCulture:
return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
case StringComparison.CurrentCultureIgnoreCase:
return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
case StringComparison.InvariantCulture:
return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
case StringComparison.InvariantCultureIgnoreCase:
return (CultureInfo.InvariantCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
case StringComparison.Ordinal:
return this.Equals(value);
case StringComparison.OrdinalIgnoreCase:
if( this.Length != value.Length)
return false;
else {
// If both strings are ASCII strings, we can take the fast path.
if (this.IsAscii() && value.IsAscii()) {
return (String.nativeCompareOrdinal(this, value, true) == 0);
}
// Take the slow path.
return (TextInfo.CompareOrdinalIgnoreCase(this, value) == 0);
}
default:
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
}
// Determines whether two Strings match.
public static bool Equals(String a, String b) {
if ((Object)a==(Object)b) {
return true;
}
if ((Object)a==null || (Object)b==null) {
return false;
}
return EqualsHelper(a, b);
}
public static bool Equals(String a, String b, StringComparison comparisonType) {
if( comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase) {
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
if ((Object)a==(Object)b) {
return true;
}
if ((Object)a==null || (Object)b==null) {
return false;
}
switch (comparisonType) {
case StringComparison.CurrentCulture:
return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
case StringComparison.CurrentCultureIgnoreCase:
return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
case StringComparison.InvariantCulture:
return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
case StringComparison.InvariantCultureIgnoreCase:
return (CultureInfo.InvariantCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
case StringComparison.Ordinal:
return EqualsHelper(a, b);
case StringComparison.OrdinalIgnoreCase:
if( a.Length != b.Length)
return false;
else {
// If both strings are ASCII strings, we can take the fast path.
if (a.IsAscii() && b.IsAscii()) {
return (String.nativeCompareOrdinal(a, b, true) == 0);
}
// Take the slow path.
return (TextInfo.CompareOrdinalIgnoreCase(a, b) == 0);
}
default:
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
}
public static bool operator == (String a, String b) {
return String.Equals(a, b);
}
public static bool operator != (String a, String b) {
return !String.Equals(a, b);
}
// Gets the character at a specified position.
//
[System.Runtime.CompilerServices.IndexerName("Chars")]
public extern char this[int index] {
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
// Converts a substring of this string to an array of characters. Copies the
// characters of this string beginning at position startIndex and ending at
// startIndex + length - 1 to the character array buffer, beginning
// at bufferStartIndex.
//
unsafe public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
if (destination == null)
throw new ArgumentNullException("destination");
if (count < 0)
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
if (sourceIndex < 0)
throw new ArgumentOutOfRangeException("sourceIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
if (count > Length - sourceIndex)
throw new ArgumentOutOfRangeException("sourceIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCount"));
if (destinationIndex > destination.Length-count || destinationIndex < 0)
throw new ArgumentOutOfRangeException("destinationIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCount"));
// Note: fixed does not like empty arrays
if (count > 0)
{
fixed (char* src = &this.m_firstChar)
fixed (char* dest = destination)
wstrcpy(dest + destinationIndex, src + sourceIndex, count);
}
}
// Returns the entire string as an array of characters.
unsafe public char[] ToCharArray() {
// <strip> huge performance improvement for short strings by doing this </strip>
int length = Length;
char[] chars = new char[length];
if (length > 0)
{
fixed (char* src = &this.m_firstChar)
fixed (char* dest = chars)
wstrcpyPtrAligned(dest, src, length);
}
return chars;
}
// Returns a substring of this string as an array of characters.
//
unsafe public char[] ToCharArray(int startIndex, int length)
{
// Range check everything.
if (startIndex < 0 || startIndex > Length || startIndex >
Remember to set the Language of your code snippet using the
Language dropdown.
Use the "var" button to to wrap Variable or class names in
<code> tags like this.
Points of Interest
Did you learn anything interesting/fun/annoying while writing
the code? Did you do anything particularly clever or wild or zany?
History
Keep a running update of any changes or improvements you've
made here.