Complete guide to dynamic keyword in C#
The dynamic keyword, a new addition to Microsoft .NET C# 4.0 language, is believed to change the type binding to a variable from compile time to runtime. This also means that apart from the CLR interpreting the variable type dynamically at runtime, the compiler also has to ignore type-matching of a variable during the compilation process.
This is a paradigm shift from what has been followed since the time of Pascal, C and C++ ages and this article focuses on understanding how dynamic works internally and the best practices to be followed when using dynamic keyword
To understand this, let’s consider a code snippet and analyse it.
private static void Main(string[] args) { Program program = new Program(); /* Simple Examples * Will work on any data type * on which + can be applied */ Console.WriteLine(program.Add(2, 3)); Console.WriteLine(program.Add(2.0d, 3.0d)); Console.WriteLine(program.Add("Puneet", "G")); /* Will work on any data type * on which = can be applied */ Console.WriteLine(program.Equals(2, 3)); Console.WriteLine(program.Equals("Puneet", "G")); //Console.WriteLine(program.Add(program, program)); }
In the above snippet, the Add method takes in 2 dynamic parameters and returns a dynamic value. This means you can typically pass any data type (that supports a + operation) and expect it to work as normal. When you pass in a data type that does not support a + operation, the program will throw a RuntimeBinderException
The output of above program as expected is:
5
5PuneetG
False
False
<RuntimeBinderException>
At compile time, CLR converts a dynamic keyword to classes in Microsoft.CSharp.RuntimeBinder and System.Runtime.CompilerServices namespace. The task of these classes is to invoke DLR at runtime and enable following conversion
So the CLR essentially creates an object of CallSite object. This CallSite is a runtime-binding handler that creates a dynamic object and allows access its properties and methods. This access is done using Reflection methods. This CallSite object is a part of DLR engine and the DLR engine interprets these calls as ‘dynamic invocations' If the DLR does not know the type of object, it will take effort in finding out. After discovering the type of object, next is to check if it is a special object (like IronRuby, IronPython, DCOM, COM, etc) or it is C# object.
The CLR takes these DLR objects, passes them through
- Metadata Analysers - This analyser detects the type of objects
- Semantic Analysers – This analyser checks if the intended operations (method calls, properties) can be performed on the object. If there is any mismatch found in these 2 steps, a runtime exception is thrown.
- Emitter – The emitter builds an Expression Tree and emits it. This expression tree is sent back to DLR to build a Object-Cache dictionary. DLR also calls compile on this expression tree to emit a IL.
On second call, the Object-Cache dictionary is used to skip the re-creation of expression tree for the same object and IL is emitted. Post IL execution of a dynamic type is same as all other types.
Creating new dynamic objects
The reason why languages such as IronRuby and IronPython today exists is because .NET language allows you to create your own dynamic types. You can write your own interpreters in C# or VB.NET and let the community use them as they wish. Let’s see this in an example where we create a new SampleDynamicObject and use its properties and methods.
dynamic sample = new SampleDynamicObject(); // TryGetMember will be invoked sample.Name // Since the return value is true, it will not // throw an exception even if there is no property // called Name Console.WriteLine(sample.Name); // TryInvokeMember will be invoked PrintDetails // However it will throw not implemented exception // as it is not handled in TryInvokedMember method sample.PrintDetails();
public class SampleDynamicObject : System.Dynamic.DynamicObject { public override bool TryInvokeMember(System.Dynamic.InvokeMemberBinder binder, object[] args, out object result) { return base.TryInvokeMember(binder, args, out result); } public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result) { // Property:= Name if (binder.Name == "Name") { result = "Puneet"; return true; } else { result = 0; return false; } } }
TryInvokeMember is called when any method is called on the dynamic object, while TryGetMember is called when a getter of a property of the dynamic object is called. Similarly there are other overridable methods that can be implemented.
dynamic vs. object
Another aspect of understanding dynamic is to understand how dynamic types are different from System.Object
One variable is typed as object
by the compiler and all instance members will be verified as valid by the compiler. The other variable is typed as dynamic
and all instance members will be ignored by the compiler and called by the DLR at execution time.
- Dynamic type can be considered as a special static type that the compiler ignores while compilation which is not the case with System.Object
- Any operation on object requires type-casting which adds a hit to the performance. Similarly, defining an object dynamic also involves some extra logic for interpretation. This extra logic is also referred as Duck Typing
- An object can be converted to a dynamic type implicitly. An implicit conversion can be dynamically applied to an expression of type dynamic
Limitations of dynamic types
The keyword dynamic restricts a lot of functionalities as the type of object is not pre-defined. Some of the known limitations are
- Inability to use LINQ, Extension Methods, Lambda expressions
- Inability to check if type conversions are done correctly
- Polymorphism can not be fully supported
- C# language constructs such as using block can not be applied to dynamic types
- Design principles like Inversion of Control and Dependency Injection are difficult to implement
Where best to use dynamic types
Some of the areas where, I believe, dynamic types can be used are
- To leverage runtime type of generic parameters
- To receive anonymous types
- To create custom domain objects for data-driven objects
- To create a cross-language translator (like IronRuby, IronPython, etc) that leverages capabilities of .NET CLR
I hope this helps to understand the dynamic keyword and how it differs from System.Object type