5

C#の動的タイプの制限の理由を教えてください。それらについては、「Pro C#2010と.NET4プラットフォーム」で読みました。ここに抜粋があります(ここで本を引用することが違法である場合は、教えてください。抜粋を削除します):

dynamicキーワードを使用して非常に多くのことを定義できますが、その使用法にはいくつかの制限があります。それらはストッパーではありませんが、動的データ項目はメソッドを呼び出すときにラムダ式またはC#匿名メソッドを利用できないことを知っておいてください。たとえば、次のコードは、ターゲットメソッドが実際に文字列値を取り、voidを返すデリゲートパラメータを実際に受け取ったとしても、常にエラーになります。

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

この制限を回避するには、第11章で説明されている手法(匿名メソッドやラムダ式など)を使用して、基になるデリゲートを直接操作する必要があります。もう1つの制限は、データの動的ポイントが拡張メソッドを理解できないことです(第12章を参照)。残念ながら、これにはLINQAPIからの拡張メソッドも含まれます。したがって、dynamicキーワードで宣言された変数は、LINQtoObjectsおよびその他のLINQテクノロジ内での使用が非常に制限されています。

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

前もって感謝します。

4

3 に答える 3

15

トーマスの予想はかなり良いです。拡張メソッドに関する彼の推論は的確です。基本的に、拡張メソッドを機能させるには、実行時に呼び出しサイトで、コンパイル時に使用されているディレクティブが何であるかを知る必要があります。この情報をコールサイトに保持できるシステムを開発するための十分な時間や予算がなかっただけです。

ラムダの場合、実際には、ラムダが式ツリーに移動するのかデリゲートに移動するのかを判断するという単純な問題よりも状況が複雑になります。次のことを考慮してください。

d.M(123)

ここで、dは動的型の式です。*呼び出しサイト「M」への引数として実行時にどのオブジェクトを渡す必要がありますか?明らかに、123をボックス化して渡します。次に、ランタイムバインダーのオーバーロード解決アルゴリズムは、dのランタイムタイプとint 123のコンパイル時タイプを調べ、それを処理します。

さて、もしそれが

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

では、引数としてどのオブジェクトを渡す必要がありますか?「xのタイプが何であれ、Fooと呼ばれる未知の関数を呼び出す1つの変数のラムダメソッド」を表す方法はありません。

この機能を実装したいとします。何を実装する必要がありますか?まず、バインドされていないラムダを表す方法が必要です。式ツリーは、すべてのタイプとメソッドが既知であるラムダを表すためだけに設計されています。新しい種類の「型なし」式ツリーを発明する必要があります。次に、ランタイムバインダーにラムダバインディングのすべてのルールを実装する必要があります。

その最後の点を考慮してください。ラムダにはステートメントを含めることができますこの機能を実装するには、ランタイムバインダーにC#で可能なすべてのステートメントのセマンティックアナライザー全体が含まれている必要があります。

それは私たちの予算から桁違いに大きかった。その機能を実装したいのであれば、今日もC#4に取り組んでいます。

残念ながら、これは、LINQが動的ではうまく機能しないことを意味します。これは、LINQはもちろん、あらゆる場所で型指定されていないラムダを使用するためです。うまくいけば、C#のいくつかの仮想的な将来のバージョンでは、より完全な機能を備えたランタイムバインダーと、バインドされていないラムダの同像表現を実行する機能があります。しかし、私があなただったら、私は息を止めませんでした。

更新:コメントは、セマンティックアナライザーについてのポイントについての説明を求めています。

次の過負荷を考慮してください。

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であると仮定します。ランタイムバインダーは何をする必要がありますか?

ランタイムバインダーは、実行時に、式がMの各オーバーロードの各デリゲートタイプに変換可能かどうかを判断する必要がありますx=>{...}

これを行うには、ランタイムバインダーが2番目のオーバーロードが適用できないことを判別できる必要があります。該当する場合は、usingステートメントの引数としてintを使用できますが、usingステートメントの引数は使い捨てである必要があります。つまり、ランタイムバインダーは、usingステートメントのすべてのルールを認識し、usingステートメントの使用の可能性が合法か違法かを正しく報告できる必要があります。

明らかに、それはusingステートメントに限定されません。ランタイムバインダーは、特定のステートメントラムダが特定のデリゲート型に変換可能かどうかを判断するために、すべてのC#のすべてのルールを知っている必要があります。

本質的に、ILではなくDLRツリーを生成するまったく新しいC#コンパイラであるランタイムバインダーを作成する時間がありませんでした。ラムダを許可しないことで、メソッド呼び出し、算術式、およびその他のいくつかの単純な種類の呼び出しサイトをバインドする方法を知っているランタイムバインダーを作成するだけで済みます。ラムダを許可すると、ランタイムバインディングの問題が、実装、テスト、および保守に数十倍または数百倍のコストがかかります。

于 2010-08-28T01:31:10.523 に答える
8

ラムダス:動的オブジェクトのパラメーターとしてラムダをサポートしない理由の1つは、コンパイラーがラムダをデリゲートとしてコンパイルするか、式ツリーとしてコンパイルするかを知らないためだと思います。

ラムダを使用する場合、コンパイラーはターゲットパラメーターまたは変数のタイプに基づいて決定します。それがFunc<...>(または他のデリゲート)である場合、ラムダを実行可能なデリゲートとしてコンパイルします。ターゲットがターゲットの場合、Expression<...>ラムダを式ツリーにコンパイルします。

これで、型がある場合dynamic、パラメーターがデリゲートであるか式であるかがわからないため、コンパイラーは何をすべきかを決定できません。

拡張メソッド:ここでの理由は、実行時に拡張メソッドを見つけるのが非常に難しい(そしておそらく非効率的である)ためだと思います。まず、ランタイムは、を使用して参照されている名前空間を知る必要がありますusing。次に、ロードされたすべてのアセンブリ内のすべてのクラスを検索し、(名前空間によって)アクセス可能なクラスをフィルタリングしてから、拡張メソッドを検索する必要があります...

于 2010-08-28T00:58:27.533 に答える
2

エリック(とトーマス)はそれをよく言いますが、これが私がそれをどう思うかです。

このC#ステートメント

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

has no meaning without a lot of context. Lambda expressions themselves have no types, rather they are convertible to delegate (or Expression) types. So the only way to gather the meaning is to provide some context which forces the lambda to be converted to a specific delegate type. That context is typically (as in this example) overload resolution; given the type of a, and the available overloads Method on that type (including extension members), we can possibly place some context that gives the lambda meaning.

Without that context to produce the meaning, you end up having to bundle up all kinds of information about the lambda in the hopes of somehow binding the unknowns at runtime. (What IL could you possibly generate?)

In vast contrast, one you put a specific delegate type there,

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

カザム!物事は簡単になりました。ラムダ内のコードに関係なく、ラムダのタイプが正確にわかります。つまり、メソッド本体と同じようにILをコンパイルできます(たとえば、Console.WriteLine呼び出している多くのオーバーロードのどれかがわかります)。 。そして、そのコードには1つの特定のタイプ(Action<int>)があります。これは、ランタイムバインダーが、そのタイプの引数を取るがをa持っているかどうかを簡単に確認できることを意味します。Method

C#では、裸のラムダはほとんど意味がありません。C#ラムダは、意味を与え、多くの可能な強制と過負荷から生じるあいまいさを排除するために静的コンテキストを必要とします。典型的なプログラムはこのコンテキストを簡単に提供しますが、dynamicケースにはこの重要なコンテキストがありません。

于 2010-08-28T06:38:52.940 に答える