未针对动态泛型类型解析方法

kel*_*oti 11 c# generics dynamic

我有这些类型:

public class GenericDao<T>
{
    public T Save(T t)
    {            
        return t;
    }
}

public abstract class DomainObject {
    // Some properties

    protected abstract dynamic Dao { get; }

    public virtual void Save() {
        var dao = Dao;
        dao.Save(this);
    }
}

public class Attachment : DomainObject
{
    protected dynamic Dao { get { return new GenericDao<Attachment>(); } }
}
Run Code Online (Sandbox Code Playgroud)

然后,当我运行此代码时,它失败并出现RuntimeBinderException:'GenericDAO <Attachment>的最佳重载方法匹配.Save(附件)'有一些无效的参数

var obj = new Attachment() { /* set properties */ };
obj.Save();
Run Code Online (Sandbox Code Playgroud)

我已经验证在DomainObject.Save()中"这个"肯定是附件,所以错误并没有真正意义.任何人都可以解释为什么这个方法没有解决?

更多信息 - 如果我更改DomainObject.Save()的内容以使用反射,它会成功:

public virtual void Save() {
    var dao = Dao;
    var type = dao.GetType();
    var save = ((Type)type).GetMethod("Save");
    save.Invoke(dao, new []{this});
}
Run Code Online (Sandbox Code Playgroud)

Ani*_*Ani 26

问题是动态方法调用的某些方面是在编译时解决的.这是设计的.从语言规范(强调我的):

7.2.3组成表达的类型

当操作静态绑定时,组成表达式的类型(例如,接收者,参数,索引或操作数)始终被认为是该表达式的编译时类型.当动态绑定操作时,组成表达式的类型以不同的方式确定,具体取决于组成表达式的编译时类型:

•编译时类型dynamic的组成表达式被认为具有表达式在运行时计算的实际值的类型

•编译时类型为类型参数的组成表达式被视为具有类型参数在运行时绑定的类型

否则,组成表达式被认为具有编译时类型.

这里,组成表达式this具有编译时类型DomainObject<int>(简化:源代码是泛型类型,因此使我们应该"查看"编译时类型的方式变得复杂this,但希望我的意思是理解的),由于它不是动态类型或类型参数,因此将其类型作为其编译时类型.

因此,绑定器查找Save采用单个参数类型的方法DomainObject<int>(或者DomainObject<int>在编译时传递类型对象的合法方法).

如果绑定发生在编译时,它看起来有点像这样:

// Extra casts added to highlight the error at the correct location. 
// (This isn't *exactly* what happens.)
DomainObject<int> o = (DomainObject<int>) (object)this;
GenericDao<Attachment> dao = (GenericDao<Attachment>)Dao;

// Compile-time error here. 
// A cast is attempted from DomainObject<int> -> Attachment.
dao.Save(o);
Run Code Online (Sandbox Code Playgroud)

但这不起作用,因为唯一关注的候选方法GenericDao<Attachment>Attachment Save(Attachment),并且对于此方法,不存在从argument(DomainObject<int>)的类型到参数(的类型)的隐式转换Attachment.

所以我们得到编译时错误:

The best overloaded method match for 'GenericDao<Attachment>.Save(Attachment)' has some invalid arguments
Argument 1: cannot convert from 'DomainObject<int>' to 'Attachment'
Run Code Online (Sandbox Code Playgroud)

是推迟到运行的时间与错误dynamic的版本.反射没有相同的问题,因为它不会尝试在编译时提取有关方法调用的"部分"信息,这与dynamic版本不同.

幸运的是,修复很简单,推迟评估组成表达式的类型:

dao.Save((dynamic)this);
Run Code Online (Sandbox Code Playgroud)

这将我们转移到选项1(编译时类型dynamic).成分表达式类型推迟到运行时,这有助于我们绑定到正确的方法.然后,静态绑定的代码等价物是这样的:

// Extra casts added to get this to compile from a generic type
Attachment o = (Attachment)(object)this;
GenericDao<Attachment> dao  = (GenericDao<Attachment>)Dao;

// No problem, the Save method on GenericDao<Attachment> 
// takes a single parameter of type Attachment.
dao.Save(o); 
Run Code Online (Sandbox Code Playgroud)

这应该工作正常.


Eri*_*ert 12

Ani的答案很好; Ani让我自行添加一些额外的解释性文字,所以你走了.

基本上,这里发生的是动态调用,其中一些参数不是动态的,导致动态分析遵守已知的编译时信息.没有一个例子,这可能并不清楚.考虑以下重载:

static void M(Animal x, Animal y) {}
static void M(Animal x, Tiger y) {}
static void M(Giraffe x, Tiger y) {}
...
dynamic ddd = new Tiger();
Animal aaa = new Giraffe();
M(aaa, ddd);
Run Code Online (Sandbox Code Playgroud)

怎么了?我们必须进行动态调用,因此ddd 的运行时类型用于执行重载解析.但是aaa不是动态的,因此它的编译时类型用于执行重载解析.在运行时,分析器尝试解决此问题:

M((Animal)aaa, (Tiger)ddd);
Run Code Online (Sandbox Code Playgroud)

并选择第二次过载.它并没有说"好吧,在运行时aaa是长颈鹿,所以我应该解决这个问题:

M((Giraffe)aaa, (Tiger)ddd);
Run Code Online (Sandbox Code Playgroud)

并选择第三个过载.

同样的事情发生在这里.当你说

dao.Save(this)
Run Code Online (Sandbox Code Playgroud)

dao的编译时类型是"动态",但编译时类型"this"不是.因此,在运行时解决重载决策问题时,我们使用运行时类型"dao"但参数的编译时类型.编译器会为这种类型组合提供错误,因此运行时绑定程序也是如此.请记住,运行时绑定程序的工作是告诉您如果编译器具有关于标记为"动态"的所有内容的所有可用信息,那么编译器会说出什么.运行时绑定程序的工作不是改变C#的语义,使C#成为动态类型语言.