我有以下类型:
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>(); } }
}
然后,当我运行此代码时,它失败并出现RuntimeBinderException:“GenericDAO&lt; Attachment&gt ;.Save(Attachment)”的最佳重载方法匹配有一些无效的参数
var obj = new Attachment() { /* set properties */ };
obj.Save();
我已经验证在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});
}
答案 0 :(得分:25)
问题是动态方法调用的某些方面是在编译时解决的。这是设计的。从语言规范(强调我的):
7.2.3构成表达的类型
当一个操作静态绑定时, 组成表达式的类型 (例如接收者和争论,a 索引或操作数)总是如此 被认为是编译时类型 那个表达。当一个操作 是动态绑定的,类型 组成表达是确定的 以不同的方式取决于 编译时类型的成分 表达式:
•构成表达 编译时类型动态是 被认为有类型的 表达式的实际值 在运行时评估
•A 组成表达 编译时类型是一个类型参数 被认为具有哪种类型 type参数绑定到 运行
•否则成分 表达被认为有其表达 编译时类型。
这里,组成表达式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);
但这不起作用,因为GenericDao<Attachment>
上关注的唯一候选方法是Attachment Save(Attachment)
,对于此方法,参数类型(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'
此是在dynamic
版本的运行时延迟的错误。反射没有相同的问题,因为它不会尝试在编译时提取有关方法调用的“部分”信息,这与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<Attachment> dao = (GenericDao<Attachment>)Dao;
// No problem, the Save method on GenericDao<Attachment>
// takes a single parameter of type Attachment.
dao.Save(o);
应该可以正常工作。
答案 1 :(得分:12)
基本上,这里发生的是动态调用,其中某些参数不是动态的,导致动态分析遵循已知的编译时信息。没有一个例子,这可能并不清楚。考虑以下重载:
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#成为动态类型语言。
答案 2 :(得分:1)
这里已经有了很好的答案。我遇到了同样的问题。你正确的反思确实有效。因为您通过说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);
}
}
这应该有用。
答案 3 :(得分:0)
代码令人困惑。我在这里看到两种可能的选择。
Dao实际上是GenericDao的父级,因为否则你的getter类型不匹配:
public class Dao
{
void Save();
}
public class GenericDao<T> : Dao
{
public virtual T Save(T) {...}
}
// error here because GenericDao does not implement Dao.
protected dynamic Dao { get { return new GenericDAO<Attachment>(); } }
或者,Dao可能是GenericDAO的孩子。但在这种情况下,吸气剂也不正确,情况实际上更糟。
所以底线是您的类/接口层次结构存在问题。请澄清,我会相应地更新我的答案。