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

时间:2011-01-12 22:08:49

标签: 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>(); } }
}

然后,当我运行此代码时,它失败并出现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});
}

4 个答案:

答案 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)

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#成为动态类型语言。

答案 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的孩子。但在这种情况下,吸气剂也不正确,情况实际上更糟。

所以底线是您的类/接口层次结构存在问题。请澄清,我会相应地更新我的答案。