簡體   English   中英

C#中動態類型的限制

[英]Limitations of the dynamic type in C#

你能否告訴我一些C#中動態類型限制的原因? 我在“Pro C#2010和.NET 4平台”中讀到了它們。 這是摘錄(如果引用書籍在這里是非法的,請告訴我,我將刪除摘錄):

雖然可以使用dynamic關鍵字定義很多東西,但是它的使用存在一些限制。 雖然它們不是show stoppers,但要知道動態數據項在調用方法時不能使用lambda表達式或C#匿名方法。 例如,以下代碼將始終導致錯誤,即使目標方法確實采用了一個帶有字符串值並返回void的委托參數。

 dynamic a = GetDynamicObject(); // Error! Methods on dynamic data can't use lambdas! a.Method(arg => Console.WriteLine(arg)); 

要繞過此限制,您需要使用第11章(匿名方法和lambda表達式等)中描述的技術直接使用底層委托。 另一個限制是動態數據點無法理解任何擴展方法(參見第12章)。 不幸的是,這還包括來自LINQ API的任何擴展方法。 因此,使用dynamic關鍵字聲明的變量在LINQ to Objects和其他LINQ技術中的使用非常有限:

 dynamic a = GetDynamicObject(); // Error! Dynamic data can't find the Select() extension method! var data = from d in a select d; 

提前致謝。

托馬斯的推測非常好。 他對推廣方法的推理是現場的。 基本上,為了使擴展方法起作用,我們需要調用站點在運行時以某種方式知道在編譯時使用指令是什么 我們根本沒有足夠的時間或預算來開發一個系統,可以將這些信息保存到呼叫站點。

對於lambda,情況實際上比確定lambda是表達樹還是委托的簡單問題更復雜。 考慮以下:

d.M(123)

其中d是動態類型的表達式。 *什么對象應該在運行時作為調用站點“M”的參數傳遞? 很明顯,我們選擇了123並通過了。 然后運行時綁定程序中的重載解析算法查看d的運行時類型和int 123的編譯時類型並使用它。

現在怎么樣呢

d.M(x=>x.Foo())

我們應該將什么對象作為參數傳遞? 我們無法表示“一個變量的lambda方法,它調用一個名為Foo的未知函數,無論x的類型是什么”。

假設我們想要實現這個功能:我們要實現什么? 首先,我們需要一種方法來表示一個未綁定的lambda 表達樹僅用於表示已知所有類型和方法的lambda 我們需要發明一種新的“無類型”表達式樹。 然后我們需要在運行時綁定器中實現lambda綁定的所有規則。

考慮最后一點。 Lambdas可以包含語句 實現此功能要求運行時綁定程序包含C#中每個可能語句的整個語義分析器

這是我們預算中的數量級。 如果我們想要實現該功能,我們今天仍然會在C#4上工作。

不幸的是,這意味着LINQ在動態方面不能很好地工作,因為LINQ當然會在所有地方使用無類型的lambda。 希望在一些假設的未來版本的C#中,我們將擁有一個功能更全面的運行時綁定器,並能夠對未綁定的lambda進行同色表示。 但如果我是你,我不會屏住呼吸。

更新:評論要求澄清關於語義分析器的觀點。

考慮以下重載:

class C {
  public void M(Func<IDisposable, int> f) { ... }
  public void M(Func<int, int> f) { ... }
  ...
}

和一個電話

d.M(x=> { using(x) { return 123; } });

假設d是編譯時類型動態和運行時類型C.運行時綁定器必須做什么?

運行時綁定程序必須在運行時確定表達式x=>{...}是否可以轉換為M的每個重載中的每個委托類型。

為此,運行時綁定程序必須能夠確定第二個重載不適用。 如果它適用,那么你可以使用int作為using語句的參數,但using語句的參數必須是一次性的。 這意味着運行時綁定程序必須知道using語句的所有規則,並能夠正確地報告對using語句的任何可能使用是合法還是非法

顯然,這不僅限於使用聲明。 運行時綁定程序必須知道所有C#的所有規則,以確定給定的語句lambda是否可轉換為給定的委托類型。

我們沒有時間編寫一個運行時綁定程序,它本質上是一個生成DLR樹而不是IL的全新C#編譯器 通過不允許lambdas,我們只需要編寫一個運行時綁定器,它知道如何綁定方法調用,算術表達式和一些其他簡單的調用站點。 允許lambda使得運行時綁定的問題實現,測試和維護的成本要高幾十倍或幾百倍。

Lambdas :我認為不支持lambdas作為動態對象參數的一個原因是編譯器不知道是將lambda編譯為委托還是表達式樹。

使用lambda時,編譯器根據目標參數或變量的類型決定。 當它是Func<...> (或其他委托)時,它將lambda編譯為可執行委托。 當目標是Expression<...>它將lambda編譯為表達式樹。

現在,當你有一個dynamic類型時,你不知道參數是委托還是表達式,所以編譯器無法決定做什么!

擴展方法 :我認為這里的原因是在運行時查找擴展方法將非常困難(並且可能也是低效的)。 首先,運行時需要知道什么名稱空間使用參考using 然后,它需要搜索所有已加載程序集中的所有類,過濾那些可訪問的(通過命名空間),然后搜索那些擴展方法...

埃里克(和托馬斯)說得很好,但這就是我的想法。

這個C#語句

a.Method(arg => Console.WriteLine(arg)); 

沒有很多背景,沒有意義。 Lambda表達式本身沒有類型,而是可以轉換為delegate (或Expression )類型。 因此,收集含義的唯一方法是提供一些強制lambda轉換為特定委托類型的上下文。 該上下文通常(如本示例中)重載決策; 給定的類型a ,以及可用的重載Method上該類型(包括擴展名成員),我們都不可能把一些背景,讓拉姆達意義。

如果沒有上下文來產生意義,你最終必須捆綁關於lambda的各種信息,以期在運行時以某種方式綁定未知數。 (你可能會產生什么IL?)

與此形成鮮明對比的是,你在那里放了一個特定的委托類型,

a.Method(new Action<int>(arg => Console.WriteLine(arg))); 

咔嚓! 事情變得容易了。 無論lambda中有什么代碼,我們現在都知道它具有什么類型,這意味着我們可以像編譯任何方法體一樣編譯IL(我們現在知道,例如, Console.WriteLine的多個重載中的哪一個我們'重新打電話)。 和該代碼具有一個特定類型( Action<int> ),這意味着它易於運行時粘合劑,以查看是否a具有Method即采用該類型的參數。

在C#中,裸體lambda幾乎毫無意義。 C#lambdas需要靜態上下文來賦予它們意義,並排除由許多可能的強制和過載引起的模糊性。 典型的程序很容易提供這種上下文,但dynamic案例缺乏這種重要的背景。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM