未针对动态generics类型解析方法
我有这些类型:
public class GenericDao { 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(); } } }
然后,当我运行此代码时,它失败并出现RuntimeBinderException:’GenericDAO 的最佳重载方法匹配.Save(Attachment)’有一些无效的参数
var obj = new Attachment() { /* set properties */ }; obj.Save();
我已经validation在DomainObject.Save()中“这个”肯定是附件,所以错误并没有真正意义。 任何人都可以解释为什么这个方法没有解决?
更多信息 – 如果我更改DomainObject.Save()的内容以使用reflection,它会成功:
public virtual void Save() { var dao = Dao; var type = dao.GetType(); var save = ((Type)type).GetMethod("Save"); save.Invoke(dao, new []{this}); }
问题是动态方法调用的某些方面是在编译时解决的。 这是设计的。 从语言规范(强调我的):
7.2.3组成表达的类型
当操作静态绑定时,组成表达式的类型(例如,接收者,参数,索引或操作数)始终被认为是该表达式的编译时类型。 当动态绑定操作时,组成表达式的类型以不同的方式确定,具体取决于组成表达式的编译时类型:
•编译时类型dynamic的组成表达式被认为具有表达式在运行时计算的实际值的类型
•编译时类型为类型参数的组成表达式被视为具有类型参数在运行时绑定的类型
• 否则,组成表达式被认为具有编译时类型。
这里,组成表达式具有编译时类型DomainObject
(简化:源代码是generics类型,因此复杂化了我们应该如何“查看”它的编译时类型,但希望是什么我的意思是理解),因为它不是动态类型或类型参数,所以它的类型被视为其编译时类型 。
因此,绑定器查找一个方法Save
,使用类型为DomainObject
的单个参数(或者在编译时传递类型为DomainObject
的对象是合法的)。
如果绑定发生在编译时,它看起来有点像这样:
// Extra casts added to highlight the error at the correct location. // (This isn't *exactly* what happens.) DomainObject o = (DomainObject ) (object)this; GenericDao dao = (GenericDao )Dao; // Compile-time error here. // A cast is attempted from DomainObject -> Attachment. dao.Save(o);
但这不起作用,因为GenericDao
上唯一关注的候选方法是Attachment Save(Attachment)
,对于此方法,从参数类型( DomainObject
)到类型不存在隐式转换。参数( Attachment
)。
所以我们得到编译时错误:
The best overloaded method match for 'GenericDao.Save(Attachment)' has some invalid arguments Argument 1: cannot convert from 'DomainObject' to 'Attachment'
这是在dynamic
版本运行时推迟的错误。 reflection没有相同的问题,因为它不会尝试在编译时提取有关方法调用的“部分”信息,这与dynamic
版本不同。
幸运的是,修复很简单,推迟了成分表达式类型的评估:
dao.Save((dynamic)this);
这将我们带入选项1(编译时类型dynamic
)。 成分表达式的类型推迟到运行时 ,这有助于我们绑定到正确的方法。 那么静态绑定的代码等价物就像:
// Extra casts added to get this to compile from a generic type Attachment o = (Attachment)(object)this; GenericDao dao = (GenericDao )Dao; // No problem, the Save method on GenericDao // takes a single parameter of type Attachment. dao.Save(o);
这应该工作正常。
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);
怎么了? 我们必须进行动态调用,因此ddd的运行时类型用于执行重载解析。 但是aaa不是动态的,因此它的编译时类型用于执行重载决策。 在运行时,分析器尝试解决此问题:
M((Animal)aaa, (Tiger)ddd);
并选择第二次过载。 它没有说“好吧,在运行时aaa是长颈鹿,因此我应该解决问题:
M((Giraffe)aaa, (Tiger)ddd);
并选择第三个重载。
同样的事情发生在这里。 当你说
dao.Save(this)
dao的编译时类型是“动态”,但编译时类型“this”不是。 因此,在运行时解决重载决策问题时,我们使用运行时类型“dao”但参数的编译时类型。 编译器会为这种类型组合提供错误,因此运行时绑定程序也是如此。 请记住,运行时绑定程序的工作是告诉您如果编译器具有关于标记为“动态”的所有信息的所有可用信息,那么编译器会说些什么 。 运行时绑定程序的工作不是改变C#的语义,使C#成为动态类型语言。
这里已经有了很好的答案。 我遇到了同样的问题。 你是正确的反思确实有效。 因为您通过说GetType()在运行时解析来指定类型。
var method = ((object)this).GetType().GetMethod("Apply", new Type[] { @event.GetType() }); //Find the right method method.Invoke(this, new object[] { @event }); //invoke with the event as argument
或者我们可以使用动力学如下
dynamic d = this; dynamic e = @event; d.Apply(e);
所以在你的情况下
public abstract class DomainObject { // Some properties protected abstract dynamic Dao { get; } public virtual void Save() { var dao = Dao; dynamic d = this; //Forces type for Save() to be resolved at runtime dao.Save(d); } }
这应该工作。
代码令人困惑。 我在这里看到两种可能的选择。
Dao实际上是GenericDao的父级,因为否则你的getter类型不匹配:
public class Dao { void Save(); } public class GenericDao : Dao { public virtual T Save(T) {...} } // error here because GenericDao does not implement Dao. protected dynamic Dao { get { return new GenericDAO(); } }
或者,Dao可能是GenericDAO的孩子。 但在这种情况下,吸气剂也不正确,情况实际上更糟。
所以底线是您的类/接口层次结构存在问题。 请澄清,我会相应地更新我的答案。