简体   繁体   中英

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.

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
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM