本文介绍了动态算子解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a generic method that calls operators by casting one of the operands to dynamic.There are two different calls:

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

这有效并调用static bool operator !=(MyClass a, MyClass b)(即使双方都是null).

This works and calls static bool operator !=(MyClass a, MyClass b) (even if both sides are null).

令我惊讶的是以下行的行为:

What surprised me is the behaviour of the following line:

//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 a, T b) 方法而不是操作符的情况下调用正确的操作符吗?

Why?
Can I call the right operator without explicitly calling a T Operators.Add<T>(T a, T b) method instead of the operator?

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

此方法在单独的程序集中调用(或尝试调用)operator *(T, object),如果主程序集中有相同的方法,它会正确调用operator *(T, T).

This method in a separate assembly calls (or tries to call) operator *(T, object), if the same method is in the main assembly it correctly calls operator *(T, T).

我用作类型参数的类是 internal,当我将其更改为 public 时问题就消失了,因此它似乎取决于类对该方法的可见性.

The class I use as type parameter is internal and the problem disappears when I change it to public, so it seems to depend on the class' visibility towards the method.

operator *(T, object) 即使类不可见也会成功调用.

operator *(T, object) is called successfully even if the class isn't visible.

推荐答案

听起来您偶然发现了动态功能的一个有趣的设计决策——不是错误,这是故意的.一段时间以来,我一直想写一篇关于这个的博客.

It sounds like you have stumbled upon an interesting design decision -- not a bug, this was deliberate -- of the dynamic feature. I've been meaning to blog about this one for some time.

首先,让我们退后一步.动态特性的基本思想是,包含动态类型操作数的表达式将其类型分析推迟到运行时.在运行时,类型分析是通过启动新版本的编译器并重新进行分析来完成的,这次将动态表达式视为其实际运行时类型的表达式.

First off, let's take a step back. The fundamental idea of the dynamic feature is that an expression containing an operand of dynamic type has its type analysis deferred until runtime. At runtime, the type analysis is done fresh by spinning up a new version of the compiler and re-do the analysis, this time treating the dynamic expression as though it were an expression of its actual runtime type.

因此,如果您有一个加法表达式,它在编译时的左侧编译时类型为对象,右侧编译时类型为动态,并且在运行时动态表达式实际上是一个字符串,则重新进行分析,左侧为对象,右侧为字符串.请注意,不考虑左侧的运行时类型.它的编译时类型是对象,而不是动态的.只有动态类型的表达式才具有在运行时分析中使用其运行时类型的属性.

So if you have an addition expression which at compile time has a left hand compile-time type of object, and a right-hand compile-time type of dynamic, and at runtime the dynamic expression is in fact a string, then the analysis is re-done with the left hand side being object and the right hand side being string. Notice that the runtime type of the left hand side is not considered. It's compile time type was object, not dynamic. Only expressions of dynamic type have the property that their runtime types are used in the runtime analysis.

只是为了确保清楚:如果您有:

Just to make sure that's clear: if you have:

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

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

then at runtime, the second override is called. The fact that at runtime x is Giraffe is ignored, because it wasn't dynamic. It was Animal at compile time, and so at runtime it continues to be analyzed as an expression of type Animal. That is, the analysis is done as though you had said:

M(x, (Apple)y);

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

and that obviously picks the second overload.

我希望这很清楚.

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

Now we come to the meat of the issue. What happens when the runtime type would not have been accessible? Let's actually work up an example:

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, what happens? We have two dynamic expressions, so according to my earlier statement, at runtime we do the analysis again but pretend that you said

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

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

And so you would think that overload resolution would choose the method Apple.MagicApple.M that exactly matches that. But it does not! We cannot pretend that the code above is what you said because that code accesses a private nested type outside its accessibility domain! That code would fail to compile entirely. But equally obviously we cannot allow this code to fail, because this is a common scenario.

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

So I must emend my previous statement. What the runtime analysis engine actually does is pretend that you inserted casts that you could legally have inserted. In this case, it realizes that the user could have inserted:

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

重载决议会选择Apple.M.

此外:假装转换总是针对类类型.可能存在可能已插入的接口类型或类型参数类型转换会导致重载解析成功,但是通过使用动态"表明您希望运行时类型使用,并且对象的运行时类型永远不会是接口或类型参数类型.

Moreover: the pretend casts are always to class types. It is possible that there is an interface type or a type parameter type cast that could have been inserted that would cause overload resolution to succeed, but by using "dynamic" you indicated that you wanted the runtime type to be used, and the runtime type of an object is never an interface or type parameter type.

听起来你们在同一条船上.如果动态表达式的运行时类型在调用站点上无法访问,则出于运行时分析的目的,将其视为最接近的可访问基类型.在您的情况下,最接近的可访问基类型可能是 object.

It sounds like you are in the same boat. If the dynamic expression's runtime type would not have been accessible at the call site then it is treated as being of its closest accessible base type for the purposes of runtime analysis. In your case, the closest accessible base type might be object.

这都清楚了吗?

这篇关于动态算子解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-13 09:43