String.Trimのことを何も知らなかった話
思い込みは怖いという話
超今更なんだと思いますが、今日ふとC#.NETでプログラムを書いている時にString.Trimに引数が渡せることを気付きました。
これまで空白除去にしか使っていなかったので脳みそが勝手に空白除去するメソッドとして認識していました。
というわけで、何も知らないままというのもあれなので、Trimの実装を見てみたいと思います。
確認するバージョンは「.NET Framework 4.8」です。
実装を確認してみる
SystemのString.csを確認してTrimメソッドを探すと、オーバーロードされているTrimメソッドを発見しました。
今回は引数がある方の中身を見ていきます。見やすいように整形しています。
private const int TrimHead = 0;
private const int TrimTail = 1;
private const int TrimBoth = 2;
[Pure]
public String Trim(params char[] trimChars) {
if (null==trimChars || trimChars.Length == 0) {
return TrimHelper(TrimBoth);
}
return TrimHelper(trimChars,TrimBoth);
}
[System.Security.SecuritySafeCritical] // auto-generated
private String TrimHelper(char[] trimChars, int trimType) {
//end will point to the first non-trimmed character on the right
//start will point to the first non-trimmed character on the Left
int end = this.Length-1;
int start=0;
//Trim specified characters.
if (trimType !=TrimTail) {
for (start=0; start < this.Length; start++) {
int i = 0;
char ch = this[start];
for( i = 0; i < trimChars.Length; i++) {
if( trimChars[i] == ch) break;
}
if( i == trimChars.Length) { // the character is not white space
break;
}
}
}
if (trimType !=TrimHead) {
for (end= Length -1; end >= start; end–) {
int i = 0;
char ch = this[end];
for(i = 0; i < trimChars.Length; i++) {
if( trimChars[i] == ch) break;
}
if( i == trimChars.Length) { // the character is not white space
break;
}
}
}
return CreateTrimmedString(start, end);
}
[System.Security.SecurityCritical] // auto-generated
private String CreateTrimmedString(int start, int end) {
//Create a new STRINGREF and initialize it from the range determined above.
int len = end -start + 1;
if (len == this.Length) {
// Don't allocate a new string as the trimmed string has not changed.
return this;
}
if( len == 0) {
return String.Empty;
}
return InternalSubString(start, len);
}
[System.Security.SecurityCritical] // auto-generated
unsafe string InternalSubString(int startIndex, int length) {
Contract.Assert( startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!");
Contract.Assert( length >= 0 && startIndex <= this.Length – length, "length is out of range!");
String result = FastAllocateString(length);
fixed(char* dest = &result.m_firstChar)
fixed(char* src = &this.m_firstChar) {
wstrcpy(dest, src + startIndex, length);
}
return result;
}
代入や比較の際に半角空白を入れたり入れなかったり、記載方法がバラバラなのは気になるけどスルーします。
引数で渡された値を先頭末尾から除外したインデックスを保持しておき、Lengthを計算。
その後FastAllocateStringで新しい文字列の領域を確保して、wstrcpyで内容をコピーする作りのようですね。
文字列に変更がない場合は、そのまま値を返し、文字列が全部消えた場合はString.Emptyを返す作りです。
まとめ
普段何気なく使っているメソッドでも仕様をしっかり確認すれば、気付いていないもっと便利な使い方ができるのかもしれません。
こんなの気付いてないの私だけ?と思っていますが。同じ勘違いをしている同志が1人でも居る可能性にかけて記事にします。
最近のコメント