未解析动态泛型类型的方法

发布于 2024-10-12 01:30:26 字数 1050 浏览 2 评论 0原文

我有这些类型:

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: Best Heavyed method match for 'GenericDAO.Save(Attachment)' has some invalid argument

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});
}

I have these types:

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

Then when I run this code it fails with RuntimeBinderException: Best overloaded method match for 'GenericDAO<Attachment>.Save(Attachment)' has some invalid arguments

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

I've verified that in DomainObject.Save() "this" is definitely Attachment, so the error doesn't really make sense. Can anyone shed some light on why the method isn't resolving?

Some more information - It succeeds if I change the contents of DomainObject.Save() to use reflection:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

最近可好 2024-10-19 01:30:26

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

7.2.3 构成表达式的类型

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

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

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

否则组成部分
表达式被认为有其
编译时类型。

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

因此,绑定器会寻找一个方法 Save,该方法采用 DomainObject 类型的单个参数(或者向该方法传递 类型的对象是合法的)编译时的 DomainObject)。

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

// 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 Save(Attachment),并且对于此方法,不存在从参数类型 (DomainObject) 到参数类型 (附件)。

因此,我们得到了编译时错误:

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

this 是延迟到 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); 

应该可以正常工作。

The problem is that some aspects of the dynamic method-call are resolved at compile-time. This is by design. From the language specification (emphasis mine):

7.2.3 Types of constituent expressions

When an operation is statically bound,
the type of a constituent expression
(e.g. a receiver, and argument, an
index or an operand) is always
considered to be the compile-time type
of that expression. When an operation
is dynamically bound, the type of a
constituent expression is determined
in different ways depending on the
compile-time type of the constituent
expression:

• A constituent expression
of compile-time type dynamic is
considered to have the type of the
actual value that the expression
evaluates to at runtime

• A
constituent expression whose
compile-time type is a type parameter
is considered to have the type which
the type parameter is bound to at
runtime

Otherwise the constituent
expression is considered to have its
compile-time type.

Here, the constituent expression this has a compile-time type DomainObject<int> (simplification: the source-code is in a generic type, so that complicates how we should "view" the compile-time type of this, but hopefully, what I mean is understood), and since this is not of type dynamic or a type-parameter, its type is taken as its compile-time type.

So the binder looks for a method Save taking a single parameter of type DomainObject<int> (or to which it would have been legal to pass an object of type DomainObject<int> at compile-time).

It would have looked somewhat like this had the binding happened at compile-time:

// 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);

But this can't work since the only candidate-method of concern on GenericDao<Attachment> is Attachment Save(Attachment), and for this method, no implicit conversion exists from type of the argument (DomainObject<int>) to the type of the parameter (Attachment).

So we get the compile-time error:

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

And this is the error that is deferred until run-time with the dynamic version. Reflection doesn't have the same problem because it doesn't attempt to extract "partial" information about the method-call at compile-time, unlike the dynamic version.

Fortunately, the fix is simple, defer the evaluation of the type of the constituent-expression:

dao.Save((dynamic)this);

This moves us into option 1 (compile-time type dynamic). The type of the constituent-expression is deferred until run-time, and this helps us bind to the right method. Then the statically-bound equivalent of the code is something like:

// 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); 

which should work fine.

許願樹丅啲祈禱 2024-10-19 01:30:26

阿尼的回答很好;阿尼要求我自行决定添加一些额外的解释性文字,所以就这样吧。

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

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 是 Giraffe,因此我应该解决问题:

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

并选择第三个重载。

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

dao.Save(this)

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

Ani's answer is quite fine; Ani asked me to add some extra explanatory text at my discretion, so here you go.

Basically what's going on here is that a dynamic invocation where some of the arguments are not dynamic causes the dynamic analysis to honour the compile-time information that was known. That's maybe not clear without an example. Consider the following overloads:

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);

What happens? We have to do a dynamic invocation, so the runtime type of ddd is used to do overload resolution. But aaa is not dynamic, so its compile time type is used to do overload resolution. At runtime the analyzer tries to solve this problem:

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

and chooses the second overload. It does not say "well, at runtime aaa is Giraffe, therefore I should be solving the problem:

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

and choose the third overload.

The same thing is happening here. When you say

dao.Save(this)

the compile-time type of dao is "dynamic", but the compile-time type of "this" is not. Therefore when solving the overload resolution problem at runtime, we use the runtime type of "dao" but the compile-time type of the argument. The compiler would have given an error for that combination of types, and therefore so does the runtime binder. Remember, the runtime binder's job is to tell you what would the compiler have said if it had all the information available about everything that was marked 'dynamic'. The runtime binder's job is not to change the semantics of C# to make C# a dynamically typed language.

戒ㄋ 2024-10-19 01:30:26

这里已经有很好的答案了。我遇到了同样的问题。你说得对,反思确实有效。因为您通过调用 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);
    }
}

这应该可行。

Quite good answers here already. I ran into the same problem. You're right reflection does work. Because you are specifying the type by saying GetType() which gets resolved at runtime.

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

Or we can use dynamics as follows

    dynamic d = this;
    dynamic e = @event;
    d.Apply(e);

So in your case

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);
    }
}

This should work.

戏舞 2024-10-19 01:30:26

代码很混乱。我在这里看到两种可能的选择。

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

所以最重要的是你的类/接口层次结构有问题。请澄清,我将相应地更新我的答案。

The code is confusing. I see two possible options here.

Dao is actually the parent of GenericDao because otherwise your getter has type mismatch:

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

Alternatively, Dao may be a child of GenericDAO. But in this case the getter is not correct either and the situation is actually worse.

So the bottom line is that there is a problem with your class/interface hierarchy. Please clarify and I will update my answer accordingly.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文