6

オペランドの 1 つを にキャストして演算子を呼び出すジェネリック メソッドがありますdynamic。2 つの異なる呼び出しがあります。

//array is T[][]
//T is MyClass
array[row][column] != default(T) as dynamic

これは機能し、呼び出しますstatic bool operator !=(MyClass a, MyClass b)(両側が であってもnull)。

私が驚いたのは、次の行の動作です。

//array, a and b are T[][]
//T is MyClass
array[row][column] += a[line][i] * (b[i][column] as dynamic);

これは呼び出し
public static MyClass operator *(MyClass a, object b)
public static MyClass operator +(MyClass a, object b)

ではなく
public static MyClass operator *(MyClass a, MyClass b)
public static MyClass operator +(MyClass a, MyClass b)です。

(MyClass, object)オペレーターの原因を取り除く

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException wurde nicht behandelt.
  HResult=-2146233088
  Message=Der *-Operator kann nicht auf Operanden vom Typ "[...].MyClass" und "object" angewendet werden.
  Source=Anonymously Hosted DynamicMethods Assembly
  StackTrace:
       bei CallSite.Target(Closure , CallSite , MyClass , Object )
       bei System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
       bei [...].MatrixMultiply[T](T[][] a, T[][] b) in 
       [...]
  InnerException: 

(省略記号は私のものです)。

なんで?オペレーターの代わりにメソッド
を明示的に呼び出さずに、適切なオペレーターを呼び出すことはできますか?T Operators.Add<T>(T a, T b)

アップデート

public static T TestMethod<T>(this T a, T b)
    {
        return (T)(a * (b as dynamic));
    }

別のアセンブリ内のこのメソッドは、 を呼び出します (または呼び出しを試みます) operator *(T, object)。同じメソッドがメイン アセンブリ内にある場合、正しく呼び出しますoperator *(T, T)

型パラメータとして使用しているクラスはinternalで、 に変更すると問題がなくなるpublicので、メソッドに対するクラスの可視性に依存しているようです。

operator *(T, object)クラスが表示されていなくても、正常に呼び出されます。

4

1 に答える 1

13

動的機能の興味深い設計上の決定(バグではなく、これは意図的なものでした)に遭遇したようです。私はしばらくの間、これについてブログを書くつもりでした。

まず、一歩後退しましょう。動的機能の基本的な考え方は、動的型のオペランドを含む式の型分析が実行時まで延期されることです。実行時に、新しいバージョンのコンパイラーを起動して分析を再実行することにより、型分析が新たに実行されます。今回は、動的式を実際の実行時型の式であるかのように扱います。

したがって、コンパイル時に左側のコンパイル時タイプのオブジェクトと右側のコンパイル時タイプの動的な加算式があり、実行時に動的式が実際には文字列である場合、分析は次のようになります。左側をオブジェクト、右側をストリングにしてやり直します。 左側のランタイムタイプは考慮されていないことに注意してください。コンパイル時のタイプは動的ではなくオブジェクトでした。動的タイプの式のみが、ランタイムタイプがランタイム分析で使用されるというプロパティを持っています。

それが明確であることを確認するために:あなたが持っている場合:

void M(Giraffe g, Apple a) {...}
void M(Animal a, Fruit f) { ... }
...
Animal x = new Giraffe();
dynamic y = new Apple();
M(x, y);

次に、実行時に2番目のオーバーライドが呼び出されます。実行時にxがキリンであるという事実は、動的ではなかったため無視されます。コンパイル時にはAnimalであったため、実行時には、Animalタイプの式として分析され続けます。つまり、分析はあなたが言ったかのように行われます:

M(x, (Apple)y);

そしてそれは明らかに2番目の過負荷を選びます。

それがはっきりしていることを願っています。

今、私たちは問題の本質に到達します。ランタイムタイプにアクセスできなかった場合はどうなりますか? 実際に例を考えてみましょう。

public class Fruit {}
public class Apple : Fruit 
{
  public void M(Animal a) {}
  private class MagicApple : Apple 
  {
    public void M(Giraffe g) {}
  }
  public static Apple MakeMagicApple() { return new MagicApple(); }
}
...
dynamic d1 = Apple.MakeMagicApple();
dynamic d2 = new Giraffe();
d1.M(d2);

OK、どうなりますか?2つの動的な式があるので、以前のステートメントによると、実行時に分析を再度実行しますが、あなたが言ったふりをします

((Apple.MagicApple)d1).M((Giraffe)d2));

したがって、過負荷の解決では、それに完全に一致する方法が選択されると思いますApple.MagicApple.M。しかし、そうではありません!上記のコードは、アクセシビリティドメインの外部にあるプライベートのネストされた型にアクセスするため、上記のコードがあなたの言ったことであるとは言えません。そのコードは完全にコンパイルできません。しかし、これは一般的なシナリオであるため、同様に明らかに、このコードを失敗させることはできません。

だから私は私の前の声明を修正しなければなりません。ランタイム分析エンジンが実際に行うことは、合法的に挿入できたキャストを挿入したふりをすることです。この場合、ユーザーが以下を挿入できた可能性があることがわかります。

((Apple)d1).M((Giraffe)d2));

そして、過負荷の解決はを選択したでしょうApple.M

さらに、ふりをするキャストは常にクラスタイプになります。オーバーロード解決を成功させるために挿入された可能性のあるインターフェイスタイプまたはタイプパラメータタイプキャストが存在する可能性がありますが、「動的」を使用して、ランタイムタイプとランタイムタイプを使用することを指定しましたオブジェクトのは、インターフェースまたは型パラメーター型になることはありません。

同じ船に乗っているようですね。動的式のランタイムタイプが呼び出しサイトでアクセスできなかった場合、ランタイム分析の目的で、最も近いアクセス可能なベースタイプとして扱われます。あなたの場合、最も近いアクセス可能な基本タイプはオブジェクトである可能性があります。

それはすべて明らかですか?

于 2013-01-07T04:38:53.513 に答える