2017-09-15 170 views
1

我正在研究IQueryable的實現;但是,在我深入研究之前,我想確保我完全理解我需要評估的表達樹會是什麼樣子。特別是,我對LINQ查詢語法在編譯過程中如何轉換爲方法語法感到好奇。LINQ如何解決命名衝突?

我正在使用LINQPad來查看編譯器生成的方法。我注意到,在嵌套迭代中,會生成一個臨時變量名稱來存儲上層迭代的狀態。這裏有一個例子:

from Event in EventQueue 
from Ack in Event.Acknowledgements 
where Ack.User == User.Name 
select Event 

這相當於:

EventQueue 
    .SelectMany(
    Event => Event.Acknowledgements, 
    (Event, Ack) => 
     new 
     { 
     Event = Event, 
     Ack = Ack 
     } 
) 
    .Where(temp0 => (temp0.Ack.User == User.Name)) 
    .Select(temp0 => temp0.Event) 

當然,我的第一反應是試圖打破這一點,看看發生了什麼。所以我寫了下面的查詢:

from Event in EventQueue 
from Ack in Event.Acknowledgements 
let temp0 = Ack.User 
where Ack.User == temp0 
select Event 

這幾乎是「WHERE 1 = 1」並返回所有事件;不過,我不明白它是如何工作的,因爲我給出的方法鏈永遠不會編譯:

EventQueue 
    .SelectMany(
    Event => Event.Acknowledgements, 
    (Event, Ack) => 
     new 
     { 
     Event = Event, 
     Ack = Ack 
     } 
) 
    .Select(
    temp0 => 
     new 
     { 
     temp0 = temp0, 
     temp0 = temp0.Ack.User // Anonymous object with identically-named properties 
     } 
) 
    .Where(temp1 => (temp1.temp0.Ack.User == temp1.temp0)) 
    .Select(temp1 => temp1.temp0.Event) 

這導致我到LINQPad不會從編譯器拉這些方法鏈的結論,因爲查詢工作,而這種方法鏈顯然不會。 LINQPad很可能自己生成方法鏈。

C#編譯器(本例中爲Roslyn)如何處理與生成的代碼的命名衝突?

回答

6

這使我得出結論,LINQPad並沒有從編譯器中拉出這些方法鏈。

正是因爲它將它從編譯器所做的事情中拉出來,您看到了這一點。

你拿了一些C#代碼,編譯它,然後使用一個工具再次給你一個代碼視圖。

如果我們手動將其從查詢語法C#代碼轉換成C#擴展方法調用,我們就可能想出這樣的:現在

EventQueue.SelectMany(
    Event => Event.Acknowledgements, 
    (Event, Ack) => { Event = Event, Ack = Ack} 
) 
    .Select(x => new { x = x, temp0 = x.Ack.User}) 
    .Where(y => (y.x.Ack.User == y.temp0)) 
    .Select(y => y.x.Event) 

,這樣做,有兩個地方我必須想出一個lambda參數的名稱。我在這裏用xy去。我們可以使用foobartheUnbearableLightnessOfBeingforgettingWhatYouCameForTheMomentYouSetFootInAShop或其他。

當您嘗試將C#編譯器的輸出轉換回C#並選擇以temp0,然後temp1等爲開頭的命名方案時,您使用的工具也做了類似的工作。這很不幸,因爲你有一些明確的叫做temp0,它沒有考慮到這種情況。真的,因爲無論如何,temp0是一個壞名字,如果我參與構建這個工具,那麼解決這個問題並不是我的高優先級。

C#編譯器(本例中爲Roslyn)如何處理與生成的代碼的命名衝突?

兩種方式:

  1. 不需要到。很多C#構造在生成的IL中根本沒有任何名稱。

考慮:

public int DoSum() 
{ 
    int x = 2; 
    int y = 3; 
    int z = x * y + 2; 
    return z - 2; 
} 

這樣做的IL將是這樣的:

ldc.i4.2  
ldc.i4.3  
mul   
ldc.i4.2  
add   
ldc.i4.2  
sub   
ret 

注意,沒有xyz在那裏。從IL返回到C#的某些內容將不得不在那裏創建名稱。

  1. 使用無效的C#名稱。

如果需要在生成的IL中執行某個名稱並且該名稱不存在於源中,那麼C#編譯器將使用一個有效的名稱作爲.NET標識符,但不是有效的作爲C#標識符。允許標識符的.NET規則比C#規則寬鬆得多。

因此,它可以使用參數的名稱,如<>h__TransparentIdentifier0<>h__TransparentIdentifier1這是不允許的C#變量名,但完全沒問題由.NET規則一般等​​,並知道它只需要跟蹤它自己創造的名稱:由於這些名稱在C#中無效,因此作者在C#中放置的內容不會有衝突。 (這也是如何做的yield創建的枚舉類型不會與您創建的任何類衝突,等等)。

再一次,從IL返回到C#的東西將不得不在這裏組成新的名字,以嘗試生成有效的C#。

您可能會抱怨該工具在使用temp0時出錯,但雖然它可能很好地檢查與用戶定義名稱的衝突,但對於「給我這個一般任務從編譯器做的事情回到C#中「。如果你想要編譯器真正做的,使用IL選項卡。