標籤:

關於 C# 中「string + int」的執行機制的疑惑?

易知在 C# 中 string 類型能夠直接與其他類型(比如 int )進行加法運算,但是在string.cs並沒有對 String 類的 + 符號進行重載。

於是嘗試了幾種不同的加法方式如下(都在 Program.Main 方法中):

1. string + int

源代碼為:

Console.WriteLine("12" + 34);

生成的IL代碼為(省略無關部分):

IL_000d: call string [mscorlib]System.String::Concat(object,
object)
IL_0012: call void [mscorlib]System.Console::WriteLine(string)

2. int + string

源代碼為:

Console.WriteLine(12 + "34");

生成的IL代碼為(省略無關部分):

IL_000d: call string [mscorlib]System.String::Concat(object,
object)
IL_0012: call void [mscorlib]System.Console::WriteLine(string)

3. int + string + int

源代碼為:

Console.WriteLine(12 + "3" + 45);

生成的IL為(省略無關部分):

IL_0014: call string [mscorlib]System.String::Concat(object,
object,
object)
IL_0019: call void [mscorlib]System.Console::WriteLine(string)

4. string + int + int + int

源代碼為:

Console.WriteLine("12" + 34 + 56 + 78);

生成的IL為(省略無關部分):

IL_002f: call string [mscorlib]System.String::Concat(object[])
IL_0034: call void [mscorlib]System.Console::WriteLine(string)

從編譯的情況能夠看出,每次執行 string 相關的加法都無比精準地調用了對應的 String.Concat 方法的某個重載實現,並且不是一個逐步執行的過程,而是一次性將所有 」加數「 一次性作為參數傳遞到該函數中。

於是疑問的地方在於,為什麼在沒有進行運算符重載(或者我沒發現?)的情況下編譯器知道執行 String.Concat 函數呢?以及為什麼編譯器能夠同時將表達式中的多個變數同時視為參數呢?以及如果需要對自己定義的一個類型實現這樣的操作應該怎麼做?

補充1:裝箱拆箱題主是知道的,只是因為問的是 "string + int" 的過程所以沒有手動調用 ToString() 。

補充2:真的調的是 Concat ... 不是 Contact ...


C#的string連接運算是語言規範里特別規定的,而不是一個普通的重載運算符,所以它的實現在C#編譯器里,而在string.cs源碼里就沒出現。

以Roslyn的C#編譯器實現為例來看點細節。

Roslyn的C#編譯器的結構和流程都挺常規的。源碼經過語法分析後轉換為AST的形式,然後做語義分析,最後做代碼生成。

語義分析中,開頭一步是分析每個符號的類型。對二元運算表達式來說,這就是要決定這個二元運算符的類型及其重載決議(overload resolution),在Microsoft.CodeAnalysis.CSharp.OverloadResolution.BinaryOperatorOverloadResolution()實現。

其中string與各種類型的+運算的類型從下面鏈接里的表查找出來:

roslyn/BinaryOperatorEasyOut.cs at master · dotnet/roslyn · GitHub

例如說,object與string做+的話,算出的運算符類型就是BinaryOperatorKind.StringAndObject。

經過這一步,C#編譯器就知道某個+運算是不是涉及字元串拼接的了。

然後有一步叫做Lowering,主要是把一些語法糖功能轉換為用更基礎的功能的AST節點代替——也就是解除語法糖。其中有一步是專門針對字元串拼接的,把原本的二元運算表達式節點轉換為合適的String.Concat()方法調用節點:

roslyn/LocalRewriter_StringConcat.cs at master · dotnet/roslyn · GitHub

經過這一步,AST里就不復存在涉及字元串拼接的+運算了,全部替換成String.Concat()調用。

最後到代碼生成時,代碼生成器根本不需要關心如何處理涉及字元串拼接的+運算,因為前面都已經統一轉換成方法調用了。

就這樣,說簡單也簡單,說麻煩吧要看具體實現細節也非常非常多…


簡單地說就是,這是編譯器的內置規則,是特例,沒辦法自己做到。


C# Language Specification 有明確定義(見 + Operator (C# Reference)):

Binary + operators are
predefined for numeric and string types. For numeric types, + computes
the sum of its two operands. When one or both operands are of type
string
, + concatenates the string representations of the operands.


C#新的編譯器自動幫你在發現必須用字元串的地方調用ToString了,不過並不是所有的地方


因為+被編譯器轉換成String.Contact(params object[])的調用了。34被裝箱成object。

BTW,更高效的寫法不該是

Console.WriteLine("12" + 34.ToString())

么,避免了一次裝箱。


先全部轉obj,然後調用tostring.

多簡單的方法。


你看不管是string 還是int類型,contact 都使用了object的形式參數,編譯器已經將他們都作為object類型的數據,再統一用Object.tostring的方法來做轉換,自然這個問題就解決了。


推薦閱讀:

既然在變數前加一個&就可以得到地址,為什麼還需要指針?
有哪些語言可以做到:List<T> 實現介面 I(或繼承某個類)當且僅當 T 實現 I?
關於MSIL中的Nop的問題?
xamarin 怎麼發音?
快速開發桌面程序用什麼技術合適?

TAG:NET | C# | 編譯器 |