我有一个通用方法,通过将一个操作数强制转换为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)
。
答案 0 :(得分: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
。
此外:假装演员阵容总是属于班级类型。可能存在可能已插入的接口类型或类型参数类型转换,这将导致重载解析成功,但是通过使用“动态”,您指示您希望运行时类型使用,对象的运行时类型永远不是接口或类型参数类型。
听起来你是在同一条船上。如果在调用站点上无法访问动态表达式的运行时类型,那么为了运行时分析的目的,它将被视为最接近的可访问基类型。在您的情况下,最接近的可访问基类型可能是对象。
这一切都清楚了吗?