ToString()
を実装する奇妙な方法に出くわしましたが、それがどのように機能するのか疑問に思っています。
public string tostr(int n)
{
string s = "";
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c;
}
return s;
}
イテレータはchar
のサイズを想定していますか?
String.Concat(object, object)
メソッドを暗黙的に呼び出し、指定された2つのオブジェクトの文字列表現を連結します。
string result = String.Concat("", n--);
次に、String.Concat(object, object)
メソッドはString.Concat(string, string)
を呼び出します。 Concat
のソースを読み取り、詳細を確認するには、まずここに移動します。 C#.NETのString.csソースコード 次に、そのページでTextBox
を検索します。 String
と入力し、結果のString.cs
リンクをクリックして、C#.NETのString.csソースコードページに移動し、Concat
メソッドを確認します。
これはメソッド定義です:
public static String Concat(Object arg0, Object arg1)
{
Contract.Ensures(Contract.Result<string>() != null);
Contract.EndContractBlock();
if (arg0 == null)
{
arg0 = String.Empty;
}
if (arg1==null)
{
arg1 = String.Empty;
}
return Concat(arg0.ToString(), arg1.ToString());
}
ご覧のとおり、これは最後にpublic static String Concat(String str0, String str1)
メソッドを呼び出します。
public static String Concat(String str0, String str1)
{
Contract.Ensures(Contract.Result<string>() != null);
Contract.Ensures(Contract.Result<string>().Length ==
(str0 == null ? 0 : str0.Length) +
(str1 == null ? 0 : str1.Length));
Contract.EndContractBlock();
if (IsNullOrEmpty(str0)) {
if (IsNullOrEmpty(str1)) {
return String.Empty;
}
return str1;
}
if (IsNullOrEmpty(str1)) {
return str0;
}
int str0Length = str0.Length;
String result = FastAllocateString(str0Length + str1.Length);
FillStringChecked(result, 0, str0);
FillStringChecked(result, str0Length, str1);
return result;
}
そして、これは基礎となるILであり、 Ildasm :
.method public hidebysig instance string
tostr(int32 n) cil managed
{
// Code size 74 (0x4a)
.maxstack 3
.locals init ([0] string s,
[1] string V_1,
[2] int32 V_2,
[3] char c,
[4] string V_4)
IL_0000: nop
IL_0001: ldstr ""
IL_0006: stloc.0
IL_0007: nop
IL_0008: ldarg.1
IL_0009: dup
IL_000a: ldc.i4.1
IL_000b: sub
IL_000c: starg.s n
IL_000e: box [mscorlib]System.Int32
IL_0013: call string [mscorlib]System.String::Concat(object)
IL_0018: stloc.1
IL_0019: ldc.i4.0
IL_001a: stloc.2
IL_001b: br.s IL_0039
IL_001d: ldloc.1
IL_001e: ldloc.2
IL_001f: callvirt instance char [mscorlib]System.String::get_Chars(int32)
IL_0024: stloc.3
IL_0025: nop
IL_0026: ldloc.0
IL_0027: ldloca.s c
IL_0029: call instance string [mscorlib]System.Char::ToString()
IL_002e: call string [mscorlib]System.String::Concat(string,
string)
IL_0033: stloc.0
IL_0034: nop
IL_0035: ldloc.2
IL_0036: ldc.i4.1
IL_0037: add
IL_0038: stloc.2
IL_0039: ldloc.2
IL_003a: ldloc.1
IL_003b: callvirt instance int32 [mscorlib]System.String::get_Length()
IL_0040: blt.s IL_001d
IL_0042: ldloc.0
IL_0043: stloc.s V_4
IL_0045: br.s IL_0047
IL_0047: ldloc.s V_4
IL_0049: ret
}// end of method tostr
この「ステップバイステップ」の説明:
// assume the input is 1337
public string tostr(int n) {
//line below is creating a placeholder for the result string
string s = "";
// below line we can split into 2 lines to explain in more detail:
// foreach (char c in n-- + "") {
// then value of n is concatenated with an empty string :
// string numberString = n-- + ""; // numberString is "1337";
// and after this line value of n will be 1336
// which then is iterated though :
// foreach(char c in numberString) { // meaning foreach(char c in "1337")
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c; // here each sign of the numberString is added into the placeholder
}
return s; // return filled placeholder
}
したがって、基本的にstring
をint
と連結すると、自動的にint.ToString
メソッドが呼び出され、文字列が結合されます。
n--
のタイプはint
であり、+
を使用して""
と連結することでstring
に変換されます。これはタイプstring
。さらに、string
はIEnumerable<char>
を実装し、その上でforeach
による実際の反復が行われます。
このコードは、私がその言語でひどいデザインを選択した結果であるため、理解できないように見えます。
_+
_演算子は、実際にはstring
には存在しません。参照ソース、または MSDNページ を見ると、string
に対して宣言されている演算子は_==
_と_!=
_のみです。
実際に起こることは、コンパイラーがその手品の1つを引き出し、_+
_演算子を静的メソッド_string.Concat
_の呼び出しに変換することです。
たまたまforeach (char c in string.Concat(n--, ""))
に遭遇した場合、意図はclearであるため、コードをよりよく理解できるでしょう。 2つのオブジェクトを文字列として連結し、結果の文字列を構成するchar
sを列挙します。
_n-- + ""
_を読むと、その意図は明確にはほど遠いものであり、_n-- + s
_(s
はstring
)である場合はさらに悪化します。
どちらの場合も、コンパイラは引数を文字列として連結することを決定し、この呼び出しをstring.Concat(object, object)
に自動的にマップします。 C#のテナントの1つは、コーダーの意図が明確でない限り、赤い旗を振って、コーダーに彼の意図を明確にするように依頼することです。この特定のケースでは、そのテナントは完全に違反されています。
私見、_string + string
_でないものはすべてコンパイル時エラーであるはずですが、その列車は何年も前に通過しました。
コードを分解して、なぜそれが起こるのかを示すには...
foreach(char c in n-- + "")
オペランドの1つとして文字列を指定して+
演算子を使用すると、+が実装されている限り、他のプリミティブオペランドが何であるかに関係なく、結果が文字列に変換されます。そうすることで、次のAutosデバッグウィンドウのスクリーンショットからわかるように、nはstring.Concat
メソッドに参加します...
ご想像のとおり、「34」でメソッドを呼び出しました。そこでのループ識別子はchar c
として定義され、文字列を通過します。これは、文字列型の「定義に移動」の結果のスクリーンショットからわかるように、string
がIEnumerable<char>
を実装しているために機能します。
したがって、その時点から、他のリスト/配列を反復処理する場合と同じように機能します...より正確には、foreach
でIEnumerableを実行し、個々のchar
を取得します。その間にn
はn-- // 33 in my case
に変更されました。この時点では、式n-- + ""
の結果を反復処理しているため、n
の値は重要ではありません。 n++
だったかもしれませんが、同じ結果が得られます。
行s = s + c;
はかなり自明である必要があり、そうでない場合に備えて、n-- + ""
の一時文字列の結果からプルした各文字が空の(最初の)string s = "";
に追加されます。前述のように、文字列が含まれる+
は文字列になるため、文字列になります。すべての文字の処理が完了すると、文字列表現が返されます。
私はReSharperを使用して関数をLinqステートメントに変換しました。これは、何が起こっているのかを理解するのに役立つ(または単に人々をさらに混乱させる)可能性があります。
public string tostrLinq(int n)
{
return string.Concat(n--, "").Aggregate("", (string current, char c) => current + c);
}
他の人がすでに述べたように、基本的に入力されたint
は空のstring
と連結され、基本的にint
の文字列表現を提供します。 string
がIEnumberable
を実装すると、foreach
ループは文字列をchar[]
に分割し、各反復で文字列の各文字を提供します。次に、ループ本体は、各char
を連結することにより、文字を文字列として結合します。
したがって、たとえば、5423
の入力が与えられると、それは"5423"
に変換され、次に"5"
、"4"
、"2"
、"3"
に分割され、最後に"5423"
にステッチバックされます。
しばらくの間、本当に頭を痛めたのはn--
でした。これによりint
が減少する場合は、代わりに"5422"
が返されませんか? MSDNの記事:インクリメント(++)およびデクリメント(-)演算子 を読むまで、これは私にはわかりませんでした。
インクリメント演算子とデクリメント演算子は、変数に格納されている値を変更してその値にアクセスするためのショートカットとして使用されます。どちらの演算子も、接頭辞または接尾辞の構文で使用できます。
If | Equivalent Action | Return value ===================================== ++variable | variable += 1 | value of variable after incrementing variable++ | variable += 1 | value of variable before incrementing --variable | variable -= 1 | value of variable after decrementing variable-- | variable -= 1 | value of variable before decrementing
したがって、デクリメント演算子はn
の最後に適用されるため、n
の値は、n
が1だけデクリメントされる前に、string.Concat
によって読み取られて使用されます。
つまりstring.Concat(n--,"")
はstring.Contact(n, ""); n = n - 1;
と同じ出力を返します。
したがって、"5422"
を取得するには、それをstring.Concat(--n, "")
に変更して、n
がstring.Contact
に渡される前に最初にデクリメントされるようにします。
TL; DR;この関数は、n.ToString()
を実行する方法についてのラウンドアバウトです。
興味深いことに、ReSharperを使用してfor
ループに変換しましたが、n
ループとは異なり、forループの反復ごとにforeach
がデクリメントされるため、関数は機能しなくなりました。 :
public string tostrFor(int n)
{
string s = "";
for (int index = 0; index < string.Concat(n--, "").Length; index++)
{
char c = string.Concat(n--, "")[index];
s = s + c;
}
return s;
}
n-- + ""
と同じです
n + ""
後でnの値を使用しないため
n + ""
と同じです
n.ToString()
+演算子をintおよびstringとともに使用すると、intがstringに変換されるため、
foreach (char c in n-- + "")
と同じです
foreach (char c in n.ToString())
残りは簡単です。
n--
はポストデクリメントであり、nの値は連結後にのみ変更されます。つまり、本質的には何もしていません。それは単にだったかもしれません
foreach (char c in n + "")
または
foreach (char c in (n++) + "")
いずれにしても何も変わりません。元の値が繰り返されます
文字列の連結:
_… string operator +(object x, string y);
_バイナリ_
+
_演算子は、一方または両方のオペランドが文字列型の場合に文字列連結を実行します。文字列連結のオペランドがnullの場合、空の文字列に置き換えられます。それ以外の場合、文字列以外のオペランドは、型ToString
から継承された仮想object
メソッドを呼び出すことによって文字列表現に変換されます。ToString
がnull
を返す場合、空の文字列に置き換えられます。 — ECMA-334 、201ページ。
したがって、n.ToString()
が呼び出されます。メソッド内の他のすべては、結果を分解および再構成するだけで、効果はありません。
次のように書くことができます:
_public string tostr(int n) => n.ToString();
_
しかし、なぜ?