tek
tek

Reputation: 353

C# Dynamics: Convert.ChangeType versus Cast

Could someone explain why casting a dynamic object as a class returns that class while using Convert.ChangeType returns a dynamic object, particularly at runtime? For instance:

 dynamic dObject = new SomeClass();
 var dTest1 = dObject as SomeClass;                           // returns SomeClass
 var dTest2 = Convert.ChangeType(dObject, typeof(SomeClass)); // returns dynamic

The broader problem: I have a series of helper classes which implement a generic interface. I need to pass a list of these classes around to other objects; however the different helper classes use different types for the generic parameter, so I cannot pass a list of the helper classes directly:

interface IHelper<T>
{
    IEnumerable<T> Foo();
}
var HelperList = new List<IHelper<T>> // Can't do this because T varies from helper to helper!

So I thought I could fake out the runtime by creating a container class which contains the helper class and the generic type, but leverages dynamics:

class ContainerClass
{
    IHelper<dynamic> HelperClass;
    Type dType;                      // Specifies the type for the dynamic object
}

Now I can create and pass a List of ContainerClass around. All the processing works great until I need to assign the results from Foo() back to the destination IEnumerables, at which point I get runtime errors saying that type object cannot be converted to such and such concrete class, even though the unboxed object types match those required. If I attempt similar syntax as in dTest2 above, the runtime is still unable to figure out the "conversion".

I realize this is probably an abuse of dynamic and poor programming practice to boot. I will certainly use a different solution if and when I can identify one, but for now I either need to make this work or go with something less ambitious.

Upvotes: 9

Views: 11867

Answers (2)

dburner
dburner

Reputation: 1007

Based on Jon Skeet's answer I think you can do something really interesting and avoid the dynamic keyword because it has a performance impact.

Use IHelper<T> : IHelper. Now you can store the helpers like into a List<IHelper>. Now you could call the Foo method by maping types to a generic method.

public IEnumerable<T> UseHelper<T> (IHelper<T> helper)
{

}

delegate IEnumerable<object> UseHelperDelegate(IHelper helper)
Dictionary<Type, UseHelperDelegate> helpersMap;

helpersMap.Add(typeof(int), UseHelper<int>); // Add others if you want

public IEnmerable<object> UseHelperWithMap(IHelper helper)
{
    Type helperType = helper.GetType();
    IEnumerable<object> retValue;
    if (helpersMap.Contains(helperType))
    {
         retValue = helpersMap[helperType](helper);
    }
    else // if the type is not maped use DLR
    {
         dynamic dynamicHelper = helper;
         retValue = UseHelper(dynamicHelper)
         // I wonder if this can actually be added to the map here
         // to improve performance when the same type is called again.
    }
}

Note: you can cast IEnumerable<SomeClass> to IEnumerable<object> and UseHelper<SomeClass> to UsehelperDelegate because of Covariance and Contravariance.

Edit: It turns out you can actualy create a new concrete function from the generic and add it to the map. This way you can avoid using dynamic.

var useHelperGeneric = this.GetType().GetMethods().FirstOrDefault(
               m=> m.IsGenericMethod && m.Name == "UseHelper");
var useHelper = useHelperGeneric.MakeGenericMethod(new Type[] { helper.GetType() });
var newFunction = (UserHelperDelegate)useHelper.MakeDelegate(typeof(UseHelperDelegate));

helpersMap.Add(helper.GetType(), newFunction);

newFunction(helper);

Upvotes: 4

Jon Skeet
Jon Skeet

Reputation: 1500575

At execution time, there's no such thing as dynamic really.

However, the call to Convert.ChangeType is providing a dynamic value as an argument. Any method call using a dynamic argument is treated as having a return value of dynamic, because the compiler doesn't know what the actual signature will be until execution time.

However, if you use a cast, an is or an as expression, or a constructor call there's only one type that the result can be - so that's the type of the expression.

As for your broader problem - it's not clear to me that using dynamic would particularly help you. You may want to declare a base interface for IHelper<T> - a non-generic interface, that is only ever used for actual IHelper<T> instances. Then you can have a List<IHelper> where every element is actually an IHelper<T> for some T, but with T varying across instances. The IHelper interface isn't really required here, although if your IHelper<T> interface really contains other members which don't use T, those could be moved to IHelper instead. However, just having it for clarity can be useful.

Now, when you need to use a specific IHelper, then dynamic typing could briefly be useful. You can declare a generic method, and let dynamic typing figure out the type argument at execution time. For example:

private readonly IList<IHelper> helpers;

...

public void UseHelpers()
{
    foreach (dynamic helper in helpers)
    {
        UseHelper(helper); // Figures out type arguments itself
    }
}

private void UseHelper<T>(IHelper<T> helper)
{
    // Now you're in a generic method, so can use T appropriately
}

Upvotes: 9

Related Questions