Click here to Skip to main content
15,880,796 members
Articles / Programming Languages / C# 4.0

dynamic could be better

Rate me:
Please Sign up or sign in to vote.
4.58/5 (20 votes)
31 Dec 2013CPOL13 min read 33.3K   17   24
This article explains why the dynamic keyword is not as useful as it should be.

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:

C#
dynamic parameter = actualParameter;
switch(command.GetType().Name)
{
  case "FbCommand":
    parameter.FbDbType = "Binary";
    break;

  // the real code had other cases and a default to throw an exception,
  // but actually it uses ADO+.NET> and so this code is not needed anymore.
}

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:

C#
public void DoSomething(Number number)
{
  Console.WriteLine(number.Value);
}

Where the Number type is declared like this:

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

// Note: This struct is only here to show the problem.
// It is far from a complete data-structure, so don't create
// types like this.
}

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:

C#
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:

C#
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:

C#
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:

C#
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 Types 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

C#
dynamicObject.Do();

We expect to have a method Do(), be it without parameters or with any number of default values.

If we do:

C#
dynamicObject.DoSomethingElse(5, "Test");

We expect to have a method DoSomethingElse that receives an int and string. Or maybe that receives two objects. 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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

  1. Add another kind of dynamic that could actually extract the interfaces and call a global but configurable cast service;
  2. 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:

C#
dynamic dynamicObject = sender;

You write:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
Question[My vote of 2] Your starting premises are flawed Pin
_groo_24-Feb-16 23:17
_groo_24-Feb-16 23:17 
GeneralMy vote of 2 Pin
Viktor Stolbovoy31-Dec-13 2:48
Viktor Stolbovoy31-Dec-13 2:48 
GeneralRe: My vote of 2 Pin
Paulo Zemek31-Dec-13 3:15
mvaPaulo Zemek31-Dec-13 3:15 
GeneralRe: My vote of 2 Pin
Viktor Stolbovoy31-Dec-13 5:06
Viktor Stolbovoy31-Dec-13 5:06 
GeneralRe: My vote of 2 Pin
Paulo Zemek31-Dec-13 5:39
mvaPaulo Zemek31-Dec-13 5:39 
GeneralRe: My vote of 2 Pin
Viktor Stolbovoy31-Dec-13 6:55
Viktor Stolbovoy31-Dec-13 6:55 
GeneralRe: My vote of 2 Pin
Paulo Zemek31-Dec-13 7:13
mvaPaulo Zemek31-Dec-13 7:13 
GeneralRe: My vote of 2 Pin
Viktor Stolbovoy31-Dec-13 8:09
Viktor Stolbovoy31-Dec-13 8:09 
GeneralRe: My vote of 2 Pin
Paulo Zemek31-Dec-13 8:30
mvaPaulo Zemek31-Dec-13 8:30 
GeneralRe: My vote of 2 Pin
Viktor Stolbovoy31-Dec-13 9:18
Viktor Stolbovoy31-Dec-13 9:18 
GeneralRe: My vote of 2 Pin
Paulo Zemek31-Dec-13 10:35
mvaPaulo Zemek31-Dec-13 10:35 
GeneralRe: My vote of 2 Pin
Viktor Stolbovoy31-Dec-13 11:04
Viktor Stolbovoy31-Dec-13 11:04 
QuestionQuestion.. Pin
FatCatProgrammer25-Dec-13 15:28
FatCatProgrammer25-Dec-13 15:28 
AnswerRe: Question.. Pin
Paulo Zemek25-Dec-13 15:43
mvaPaulo Zemek25-Dec-13 15:43 
GeneralRe: Question.. Pin
FatCatProgrammer26-Dec-13 5:23
FatCatProgrammer26-Dec-13 5:23 
GeneralRe: Question.. Pin
Paulo Zemek26-Dec-13 5:46
mvaPaulo Zemek26-Dec-13 5:46 
GeneralRe: Question.. Pin
FatCatProgrammer26-Dec-13 6:52
FatCatProgrammer26-Dec-13 6:52 
QuestionVery good article! Pin
Volynsky Alex24-Dec-13 8:02
professionalVolynsky Alex24-Dec-13 8:02 
AnswerRe: Very good article! Pin
Paulo Zemek24-Dec-13 8:11
mvaPaulo Zemek24-Dec-13 8:11 
GeneralRe: Very good article! Pin
Volynsky Alex24-Dec-13 8:18
professionalVolynsky Alex24-Dec-13 8:18 
QuestionMy vote of 5 Pin
Florian Rappl22-Dec-13 23:38
professionalFlorian Rappl22-Dec-13 23:38 
AnswerRe: My vote of 5 Pin
Paulo Zemek23-Dec-13 3:23
mvaPaulo Zemek23-Dec-13 3:23 
SuggestionDynamic Var Pin
cocis4820-Dec-13 8:15
cocis4820-Dec-13 8:15 
GeneralRe: Dynamic Var Pin
Paulo Zemek20-Dec-13 8:17
mvaPaulo Zemek20-Dec-13 8:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.