Reputation: 353
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
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
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