c# - literal - string interpolation javascript




字符串插值問題 (3)

問題似乎是在使用字符串插值時插入一個括號,你需要通過複製它來逃避它。 如果你添加用於插值本身的括號,我們最終得到一個三個括號,例如你在行中的那個括號,它給你例外:

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

現在,如果我們觀察“ }}}” ,我們可以注意到第一個括號包含字符串插值,而最後兩個括號被視為字符串轉義括號字符。

然而,編譯器將前兩個視為scaped字符串字符,因此它在插值分隔符之間插入一個字符串。 基本上編譯器正在做這樣的事情:

string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug

您可以通過重新格式化行來解決此問題:

Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");

我試圖找出我的單元測試失敗的原因(下面的第三個斷言):

var date = new DateTime(2017, 1, 1, 1, 0, 0);

var formatted = "{countdown|" + date.ToString("o") + "}";

//Works
Assert.AreEqual(date.ToString("o"), $"{date:o}");
//Works
Assert.AreEqual(formatted, $"{{countdown|{date.ToString("o")}}}");
//This one fails
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

AFAIK,這應該可以正常工作,但看起來它沒有正確傳遞格式化參數,它只顯示為代碼的{countdown|o} 。 知道為什麼會失敗嗎?


這是我原來的答案的後續行動

確保這是預期的行為

就官方消息來源而言,我們應該參考msdn的Interpolated Strings

插值字符串的結構是

$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "  

並且每個插值都是用語法正式定義的

single-interpolation:  
    interpolation-start  
    interpolation-start : regular-string-literal  

interpolation-start:  
    expression  
    expression , expression  

這裡最重要的是

  1. optional-colon-format定義為regular-string-literal語法=>即它可以包含escape-sequence ,根據paragraph 2.4.4.5 String literals C#語言規範5.0的 paragraph 2.4.4.5 String literals
  2. 您可以在任何可以使用string literal地方使用插值字符串
  3. 要在插值字符串中包含大括號( {} ),請使用兩個花括號{{}} =>即編譯器以optional-colon-format 轉義兩個花括號
  4. 編譯器將包含的插值expressions掃描為平衡文本,直到找到逗號,冒號或關閉大括號=>即冒號破壞平衡文本以及關閉大括號

為了清楚起見,這解釋了$"{{{date}}}"之間的區別,其中date是一個expression ,所以它被標記化,直到第一個大括號與$"{{{date:o}}}" date又是一個expression ,現在它被標記化,直到第一個冒號,之後一個常規字符串文字開始,編譯器恢復轉義兩個花括號等...

還有來自msdn的字符串格式化常見問題解答 ,其中明確處理了此案例。

int i = 42;
string s = String.Format(“{{{0:N}}}”, i);   //prints ‘{N}’

問題是,為什麼最後一次嘗試失敗了? 為了理解這個結果,您需要了解兩件事:

提供格式說明符時,字符串格式化採取以下步驟:

確定說明符是否長於單個字符:如果是,則假定說明符是自定義格式。 自定義格式將為您的格式使用合適的替換,但如果它不知道如何處理某些字符,它將簡單地將其寫為以格式確定的文字確定單個字符說明符是否是受支持的說明符(例如數字格式為'N')。 如果是,則適當格式化。 如果沒有,拋出一個ArgumnetException

在嘗試確定是否應該轉義大括號時,只需按接收順序處理大括號。 因此, {{{將轉義前兩個字符並打印文字{ ,第三個大括號將開始格式化部分。 在此基礎上,在}}}前兩個大括號將被轉義,因此文字}將被寫入格式字符串,然後最後的大括號將被假定為結束格式化部分有了這些信息,我們現在可以弄清楚{{{0:N}}}情況中發生了什麼。 前兩個花括號被轉義,然後我們有一個格式化部分。 但是,在關閉格式化部分之前,我們還會轉義結束的大括號。 因此,我們的格式化部分實際上被解釋為包含0:N} 。 現在,格式化程序查看格式說明符,它看到N}為說明符。 因此,它將此解釋為自定義格式,並且由於N或}都不表示自定義數字格式的任何內容,因此這些字符只是寫出來的,而不是引用的變量的值。


這條線的問題

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

就是要在要轉義的變量的format string之後有3個捲曲引號並且它從左到右開始轉義,因此它將前2個捲曲引號視為格式字符串的一部分,將第三個引號作為結束字符串

所以它在o}轉換o並且它無法插值。

這應該工作

Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");

請注意,更簡單的$"{date}}}" (即沒有 format string的變量之後的3個捲曲)確實有效,因為它識別出第一個捲曲引用是結束引用,而格式說明符的解釋在: break之後正確的右括號標識。

要證明格式字符串像字符串一樣進行轉義 ,請考慮以下內容

$"{date:\x6f}"

被視為

$"{date:o}"

最後,雙轉義捲曲引號完全有可能是自定義日期格式的一部分,因此編譯器的行為絕對合理。 再一次,一個具體的例子

$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017

解析是一個基於表達式語法規則的正式過程,不能僅僅通過瀏覽它來完成。





string-interpolation