6

我有一个通用方法,它通过将操作数之一转换为dynamic. 有两种不同的调用:

//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);

然后在运行时,调用第二个覆盖。在运行时 x 是 Giraffe 的事实被忽略了,因为它不是动态的。它在编译时是 Animal,因此在运行时它继续被分析为 Animal 类型的表达式。也就是说,分析就像您说的那样进行:

M(x, (Apple)y);

这显然选择了第二个重载。

我希望这很清楚。

现在我们来到问题的实质。当运行时类型无法访问时会发生什么? 让我们实际举一个例子:

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);

好的,会发生什么?我们有两个动态表达式,所以根据我之前的说法,在运行时我们再次进行分析,但假装你说

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

所以你会认为重载决议会选择Apple.MagicApple.M完全匹配的方法。但事实并非如此!我们不能假装上面的代码就是您所说的,因为该代码访问了其可访问域之外的私有嵌套类型!该代码将无法完全编译。但同样明显的是,我们不能让这段代码失败,因为这是一种常见的情况。

所以我必须修改我之前的陈述。运行时分析引擎实际上所做的是假装您插入了您可以合法插入的强制转换。在这种情况下,它意识到用户可以插入:

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

并且重载决议会选择Apple.M.

此外:假装演员总是类类型。可能已经插入了接口类型或类型参数类型转换,这会导致重载解析成功,但是通过使用“动态”,您表示您希望使用运行时类型,并且运行时类型对象永远不是接口或类型参数类型。

听起来你在同一条船上。如果动态表达式的运行时类型在调用站点上是不可访问的,则出于运行时分析的目的,它被视为最接近的可访问基类型。在您的情况下,最接近的可访问基类型可能是对象。

这一切都清楚了吗?

于 2013-01-07T04:38:53.513 回答