开发者

Method not being resolved for dynamic generic type

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


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.


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.


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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜