kelloti kelloti - 1 month ago 15
C# Question

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

Ani Ani
Answer

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.

Comments