Introduction
C# now has the dynamic
keyword. By seeing a variable as dynamic
we allow C# to avoid its "statically typed" behavior and validate things at run-time, effectively being able to invoke a method even when the compiler has no clue about the real type of the object or if that method exists or not.
This is a very interesting behavior if we want to make .NET interact with non-statically typed languages and some say this allow for duck-typing in .NET or that it is even better than duck-typing, being really dynamic. Yet, I should say that until now all the situations where I believed dynamic
would be useful to me were, in fact, a failure and so I think dynamic
should be improved to become really useful.
Note: I am not saying dynamic
is useless. The truth is that I never had the need to interact with other languages, only to use .NET classes in dynamic manners and, well, you will see in this article why dynamic
was not an answer.
Problems with dynamic
As I see, dynamic
has the following problems:
- It can't set enum values in a dynamic manner;
- It can't do data-type conversions (this partially includes the previous item);
- It can't call public methods on internal types declared on another assembly, even when those types implement public interfaces and the invoked methods are part of the public interfaces;
- It can't call generic methods with run-time provided types.
As I know that only listing the problems may not be enough to understand them, I will explain each one of them.
Can't set enum values in a dynamic manner
The first time I tried to use dynamic
was in an application that was loading the database drivers at run-time. The entire application used the IDbConnection
, IDbCommand
and the other IDb*
interfaces, so I didn't need to have database specific code.
Yet, by a trait that I consider a failure in ADO.NET, if I set the DbType
of a parameter to Binary
it is limited to 8kb. So, I must use the database specific property and enum (like SqlDbType
, OracleDbType
or FbDbType
) to set a binary parameter to its right type as to support more than 8kb.
As I didn't want to add direct references to all the database drivers the application could support, I decided to make a switch
by the command.GetType().Name
to set the parameter. Something like:
dynamic parameter = actualParameter;
switch(command.GetType().Name)
{
case "FbCommand":
parameter.FbDbType = "Binary";
break;
}
The problem here is: I can't pass the "Binary"
value as a string
. I should pass it as an FbDbType
and, to do that, I should have a direct reference to the Firebird client library. But, if I do that, then I can cast the parameter to an FbDbParameter
and avoid the dynamic
completely.
But considering that enums are nothing more than a "name" for a numeric value, I think dynamic
should be able to support setting enum values as string
.
Even if you don't have a problem with the database drivers, think about any type declared in another assembly to which you don't want to reference (at least not directly). If it uses an enum property, where the enum type is also declared in such a library, do you prefer to use reflection to be able to set the enum value, or do you prefer to use dynamic
and pass the value as a string
?
Well, actually it is not important what I prefer, I must use reflection (or another alternative to dynamic
).
Can't do data-type conversions
Well, to be honest dynamic
can do data-type conversions, as long as they are implicit
ones. But data-type conversions valid for the type and marked as explicit
aren't directly supported.
You may say that it is right to not support them directly, after all those are explicit conversions, meaning that you must ask for them.
But if we see the MSDN documentation, explicit conversions must be used instead of implicit conversions when there's a chance of data-loss or a chance to throw exceptions.
Well, dynamic
is already throwing exceptions saying the value is invalid. If it tried to use the explicit conversion, it will have the chance to either succeed or to throw the conversion exception. I really believe it is better to try to do the conversion.
If there's some data-loss (like precision loss from converting a decimal
to int
). Well, it is dynamic, isn't it? What should happen if I pass a value of 1.8 to a variable of type int
? Maybe you believe that it must be rounded up, but that's not what the cast do. So, why can't the dynamic
call do the cast for you?
And let's not forget about the previous problem: How do you convert your value to a type that's in the assembly that you are using dynamically?
So, imagine that we have a method DoSomething
, with the following body:
public void DoSomething(Number number)
{
Console.WriteLine(number.Value);
}
Where the Number type is declared like this:
public struct Number
{
public readonly int Value;
public Number(int value)
{
Value = (value / 10) * 10;
}
public static implicit operator int (Number value)
{
return value.Value;
}
public static explicit operator Number (int value)
{
return new Number(value);
}
}
To this type, a value of 56
will be converted to 50
. And that's why the conversion from int
to Number
is explicit.
But now, try to call the DoSomething
method:
dynamic instance = Activator.CreateInstance(typeThatWasJustLoaded);
instance.DoSomething(56);
And an exception will be thrown. If the conversion was implicit, it would work.
But I don't think the conversion should be implicit. Yet I can't agree that the dynamic
behavior should keep the C# rules of implicit and explicit conversions.
Also, I even tried doing something like:
instance.DoSomething(Converter.ChangeType(56, typeOfNumberThatIGotThroughReflection));
But it doesn't work either. In this case, the problem is the Convert
class, which is incapable of converting an int
to Number
even if it has an explicit conversion (in fact, Convert.ChangeType
is not even capable of using an implicit conversion).
So, a solution to me is that dynamic
should either do consider the explicit conversions as implicit or, if that is considered too risky, to allow something like a "context" where those conversions are done. Something similar to how the "checked"/"unchecked" works, but for implicit/explicit.
Can't call public methods on internal types
Some people argue that it is an error to allow an internal type to have public members. If the type is internal, then everything inside it is also internal.
I can say that it seems right at first. If I can't see the existence of the type, how can I see its methods?
But this is not exactly true. In many situations a type is made internal as to not appear in Intellisense, yet an instance may be created by a factory/IoC container and returned to be seen by an interface. But dynamic
can't see those methods. It is not important if they are public in the type and also in the interface. If you don't cast to the interface, they will not be seen. And, if you can cast to the interface, then why should you use dynamic
?
I can understand why they decided not to support the methods of the interfaces, after all two interfaces may have methods with the same name and different implementations. But considering the method is public on the type, why not?
In my opinion, the type being internal is not telling that I can't see anything inside it. It only means I can't find the type directly. But as long as I have an instance of that type, well, I can find the type by doing an instance.GetType()
. Reflection will then allow me to see the public methods without having to specify that I want to see non-public members. Why is dynamic
not seeing them? It is not enforcing any security with that, after all the "safe reflection" is already capable of seeing the public members. So, in this case, dynamic
is simply constrained for no reason.
Can't call generic methods with run-time provided types
The best example for this is how we usually use IoC containers:
container.Resolve<SomeType>();
In this case, as there's no parameter that uses the generic argument, there's no type-inference. We must specify the type.
But what if I simply want to create a method like:
public dynamic UntypedGet(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
return _container.Get<type>();
}
Well, that's impossible as the generic argument must be a compile-time type, not a run-time Type
.
I can of course use reflection to achieve the good result, but shouldn't dynamic
be able to do such dynamic call?
Conclusion
My conclusion is that actually dynamic
has a very limited use. It of course simplifies the communication of C# with dynamic languages and thanks to the DynamicObject
allows for very interesting uses, like being able to "navigate xml files" through properties.
But when I see "dynamic" I hope it will help me access dynamically loaded modules, which is not really happening as I can't fill enum values when the enum types are part of that library, I can't call public method on an instance created by a factory if the type is internal, I can't be lazy with conversions, which forces me to know the target type at compile-time, even when using a "late-bound" solution and I can't use run-time Type
s to call generic methods.
So, at this moment I can't say dynamic
is as dynamic as it should be. I really hope it is improved but, for now, I prefer to stick with reflection and in many situations generate code at run-time to avoid the performance problems of the reflection invokes.
dynamic vs. Cast Services
I am adding this topic because I get impressed on how many people argue that dynamic
is correct, that it shouldn't do things "by magic". "How will it use a an enum type it doesn't know? Why would it do a conversion that you didn't ask magically?"
Well, I could argue "How could you call a method you don't know about?" But the truth is, we know something about the method we want to call. We actually don't have a real signature for it, but we know it exists (or we at least expect that). And we may actually know the name of an enum value it needs, without knowing the actual enum type (or even without having a compile-time reference to it).
And that's why I prefer duck-typing and structural-typing over interfaces instead of dynamic
. With duck-typing, we simply ask to cast an unknown object to an interface and the cast will succeed. When invoking the methods we can receive exceptions because the method isn't there, but that's very similar to a dynamic call.
With structural-typing, we ask to cast an unknown object to a given interface and, if it is compatible the cast works. If not, the cast fails immediately. This way we can avoid calling methods A and B if the method C isn't there too.
But every time I suggest using a cast service instead of dynamic
someone says "But we don't know the type or its methods, how would we be able to cast it to an interface we don't have?"
Honestly, this kind of question is based on the wrong idea. When we use dynamic
we know the methods that we want to invoke at compile time. For example, if we do
dynamicObject.Do();
We expect to have a method Do()
, be it without parameters or with any number of default values.
If we do:
dynamicObject.DoSomethingElse(5, "Test");
We expect to have a method DoSomethingElse
that receives an int
and string
. Or maybe that receives two object
s. This is different than reading a text file to decide which methods to invoke. In such a case we would really not know anything about the invoked methods at compile-time..
So, we can easily identify that we want to use Do()
and DoSomethingElse()
and write an interface like:
public interface ISpecificInterfaceForMyCast
{
void Do();
object DoSomethingElse(int id, string name);
}
And then ask the cast service to do its job. But then users always want the cast service to be able to do some automatic conversions.
For example, imagine that the real signatures were something like this:
void Do(params object[] userData);
Record DoSomethingElse(byte id, string name);
As we don't know that Record
type we want to receive it as object
. As we are using a kind of duck-typing, we want that byte id
to support an int
and do the conversion. Well, it doesn't need to be 100% automatic. Maybe we should configure the cast service to allow for certain conversion, but a good cast service would allow it to work.
So, if it is valid for a cast service, why it is not valid for dynamic
? In my opinion, because of bad design.
In fact, I think that dynamic
would be much better if it actually extracted an interface from our calls and then used a global cast service to do the conversion, which could be declared like this:
public interface ICastService
{
T TryCast<T>(object instance, out bool succeeded);
}
public static class ConfigurableCastService
{
public static ICastService CastService { get; set; }
public static ICastService Get()
{
ICastService result = CastService;
if (result == null)
throw new InvalidOperationException("The cast service is not configured.");
return result;
}
}
Such ConfigurableCastService
could come with an implementation that doesn't support any conversions, like the actual dynamic
. But then when we did this:
dynamic dynamicObject = sender;
Console.WriteLine(dynamicObject.Name);
dynamic record = dynamicObject.CreateRecord(5, "Test")
Console.WriteLine(record.Name + ": " + record.Id);
The compiler could extract 2 interfaces for us, something like this:
internal interface ICompilerGeneratedInterface1
{
object Name { get; }
ICompilerGeneratedInterface2 CreateRecord(int p0, string p1);
}
internal interface ICompilerGeneratedInterface2
{
object Name { get; }
object Id { get; }
}
And in the line dynamic dynamicObject = sender;
it would be compiled like:
bool succeeded;
ICompilerGeneratedInterface1 dynamicObject = ConfigurableCastService.Get().TryCast<ICompilerGeneratedInterface1>(sender, out succeeded);
if (!succeeded)
throw new SomeExceptionHere();
With that all calls that actually use dynamic
would continue to work with the default implementation but we would be free to replace how the cast is done (even for those interfaces generated by the compiler) and so we could add automatic conversion support for enums, data-types that support explicit conversion and the like. In fact, maybe most developers don't know how to create such cast services, but it would be possible to use any third-party cast service with the actual C# generated dynamic
code.
There would probably be some speed differences compared to the actual implementation, but actually doing a single cast to an interface and then doing many calls to the interface is faster than the actual dynamic
behavior.
A possible question: What about types that change at run-time?
Well, the generated interface implementation should be prepared for that. The interface will actually have an implementation for a given method, but to do the actual call the generated object would require a test on the target object, but that's nothing proibitive and can still take advantage of the other features.
Giving parameters to another method
A cast service that uses interfaces also has another advantage. You would actually finish with an object that implements the requested type. For example, if you need to call a method that receives an INameable
you can use the cast service to do the "cast", ending with an object that really implements that INameable
, and so you can give it as parameter. Now try to cast a dynamic
object that has a compatible structure with such INameable
but doesn't implement it. It will not work, after all the type doesn't implement the interface. In such an event, it is possible that we work around the problem by receiving a dynamic
parameter instead of an INameable
but, in that case, the method will not be telling anything about what it expects to do with such a parameter.
Second Conclusion
My conclusion is that a cast service actually do a better job than dynamic
but lacks the compile-time support that dynamic
has, which actually forces us to write an interface for the methods we want to call.
Well, some people actually believe it is better to have that extra step, so we can actually put some rule that we must obey (like an int parameter instead of any object, or that the method receives 2 parameters, not only one).
But as I don't believe the actual dynamic would change, I do believe it would be great if the C# compiler team decides to do at least one of these:
- Add another kind of dynamic that could actually extract the interfaces and call a global but configurable cast service;
- Add some configurable events/extension points before actually telling that some dynamic call failed.
Is there an actual work-around?
Yes. Two. You can use a cast service, even if you need to create the extra interfaces by hand. Or you can create your own dynamic type that add any extra behavior you need (like extra conversions). So, instead of writing:
dynamic dynamicObject = sender;
You write:
dynamic dynamicObject = new SpecialDynamic(sender);
And that SpecialDynamic
can do the extra magic.
Note: I am not saying to create an adapter for each dynamic object. I am talking about creating a single DynamicObject
implementation that's capable of doing the extra jobs.