【问题标题】:Method not being resolved for dynamic generic type没有为动态泛型类型解析方法
【发布时间】:2011-01-12 22:08:48
【问题描述】:

我有这些类型:

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.Save(Attachment)' 的最佳重载方法匹配有一些无效参数

var obj = new Attachment() { /* set properties */ };
obj.Save();

我已经在 DomainObject.Save() 中验证了“this”肯定是附件,所以这个错误没有任何意义。谁能解释一下为什么该方法无法解决?

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

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

【问题讨论】:

  • 对此进行调查。不知道原因,所以我不会作为答案发布,但我认为问题出在this。在您的虚拟 Save 方法中,我使用它,添加了 dynamic obj = this; dao.Save(obj); 并在运行时解决。测试它以查看它在给定代码的情况下是否真正起作用。
  • @Ani,我将他的代码更新为可以编译但仍显示其运行时损坏行为的代码。这是一个有趣的问题。
  • @Anthony,您能否重新发布您的评论作为答案,以便我标记它?做动态 obj = this; 似乎确实有效dao.Save(obj);另外,任何人都可以解释为什么会这样吗?这是 DLR 中的错​​误吗?

标签: c# generics dynamic


【解决方案1】:

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

7.2.3 构成表达式的类型

当一个操作被静态绑定时, 组成表达式的类型 (例如,一个接收者,一个论点,一个 索引或操作数)总是 被认为是编译时类型 那个表情。当一个操作 是动态绑定的,a的类型 成分表达式被确定 根据不同的方式 成分的编译时类型 表达式:

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

• 一个 组成表达式 编译时类型是类型参数 被认为具有以下类型 类型参数绑定到 at 运行时

否则组成 表达式被认为有它的 编译时类型。

这里,组成表达式this 有一个编译时类型DomainObject&lt;int&gt;(简化:源代码是一个泛型类型,因此我们应该如何“查看”@987654323 的编译时类型变得复杂@,但希望我的意思可以理解),并且由于这不是动态类型或类型参数,其类型被视为其编译时类型

因此,活页夹寻找一个方法Save,采用DomainObject&lt;int&gt; 类型的单个参数(或者在编译时将DomainObject&lt;int&gt; 类型的对象传递给该方法是合法的)。

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

// 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&lt;Attachment&gt; 上唯一关注的候选方法是Attachment Save(Attachment),并且对于此方法,不存在从参数类型 (DomainObject&lt;int&gt;) 到参数类型的隐式转换参数(Attachment)。

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

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

thisdynamic 版本延迟到运行时的错误。反射没有同样的问题,因为它不会尝试在编译时提取有关方法调用的“部分”信息,这与 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); 

应该可以正常工作。

【讨论】:

  • 谢谢这澄清了行为。很有趣!
【解决方案2】:

阿尼的回答很好; 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 的 runtime type 用来做重载解析。但是aaa不是动态的,所以它的编译时类型是用来做重载解析的。在运行时,分析器会尝试解决这个问题:

M((Animal)aaa, (Tiger)ddd);

并选择 second 重载。它并没有说“好吧,在运行时 aaa 是 Giraffe,因此我应该解决这个问题:

M((Giraffe)aaa, (Tiger)ddd);

然后选择第三个重载。

这里也发生了同样的事情。当你说

dao.Save(this)

dao 的编译时类型是“动态的”,但“this”的编译时类型不是。因此在解决运行时重载解析问题时,我们使用“dao”的运行时类型,而使用参数的编译时类型。对于这种类型的组合,编译器会给出一个错误,因此运行时绑定器也是如此。请记住,运行时绑定器的工作是告诉您如果编译器拥有关于所有标记为“动态”的所有信息的所有可用信息,编译器会说什么。运行时绑定器的工作不是更改 C# 的语义以使 C# 成为动态类型语言。

【讨论】:

    【解决方案3】:

    这里已经有了很好的答案。我遇到了同样的问题。你是对的,反思确实有效。因为您通过在运行时解析的 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);
        }
    }
    

    这应该可行。

    【讨论】:

      【解决方案4】:

      代码令人困惑。我在这里看到两个可能的选择。

      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 的子级。但在这种情况下,getter 也不正确,情况实际上更糟。

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

      【讨论】:

      • 代码已更新,因此应该可以编译。现在更有意义了吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多