Type safety is a principle in programming languages which prevents accessing the values in variables inconsistent with their declared type. There are two kinds of type safety based on when the checks are performed:
- Static type safety is checked at compile time.
- Dynamic type safety is checked at runtime.
Without the checks, the values could be read from the memory as if they were of another type than they are, which would result in undefined behavior and data corruption.
Static Type Safety
C# implements measures for both kinds of type safety.
Static type safety prevents access to non-existent members of the declared type:
string text = "String value";
int textLength = text.Length;
int textMonth = text.Month; // won't compile
DateTime date = DateTime.Now;
int dateLength = date.Length; // won't compile
int dateMonth = date.Month;
The two lines marked with a comment won’t compile, because the declared type of each variable doesn’t have the member we are trying to access. This check cannot be circumvented even if we try to cast the variable to the type which does have the requested member:
string text = "String value";
int textLength = text.Length;
int textMonth = ((DateTime)text).Month; // won't compile
DateTime date = DateTime.Now;
int dateLength = ((string)date).Length; // won't compile
int dateMonth = date.Month;
The two lines with comments still won’t compile. Although the type we’re trying to cast the value to has the member we’re accessing, the compiler doesn’t allow the cast because there is no cast operation defined for converting between the two types.
Are you a .NET/C# developer looking for a resource covering New Technologies, in-depth Tutorials and Best Practices?
Well, you are in luck! We at DotNetCurry release a digital magazine once every two months aimed at Developers, Architects and Technical Managers and cover ASP.NET Core, C#, Patterns, .NET Core, ASP.NET MVC, Azure, DevOps, ALM, TypeScript, Angular, React.js, and much more. Subscribe to this magazine for FREE and receive all previous, current and upcoming editions, right in your Inbox. No Gimmicks. No Spam Policy.
Click here to Download the Magazines For Free
Dynamic Type Safety
However, static type safety in C# is not 100% reliable. By using specific language constructs, the static type checks can still be circumvented as demonstrated by the following code:
public interface IGeometricShape
{
double Circumference { get; }
double Area { get; }
}
public class Square : IGeometricShape
{
public double Side { get; set; }
public double Circumference => 4 * Side;
public double Area => Side * Side;
}
public class Circle : IGeometricShape
{
public double Radius { get; set; }
public double Circumference => 2 * Math.PI * Radius;
public double Area => Math.PI * Radius * Radius;
}
IGeometricShape circle = new Circle { Radius = 1 };
Square square = ((Square)circle); // no compiler error
var side = square.Side;
The line marked with the comment will compile without errors, although we can see from the code that the circle variable contains a value of type Circle which can’t be cast to the Square type.
The compiler does not perform the static analysis necessary to determine that the circle variable will always contain a value of type Circle. It only checks the type of the variable. Since the compiler allows downcasting from the base type (the IGeometricShape interface) to a derived type (the Square type) because it might be valid for certain values of the variable, our code will compile.
Despite that, no data corruption will happen because of this invalid cast.
At runtime, the compiled code will not execute. It will throw an InvalidCastException when it detects that the value in the circle variable can’t be cast to the Square type. This is an example of dynamic type safety in C#. To avoid the exception being thrown at runtime, we can include our own type checking code and handle different types in a different way:
if (shape is Square)
{
Square square = ((Square)shape);
var side = square.Side;
}
if (shape is Circle)
{
Circle circle = ((Circle)shape);
var radius = circle.Radius;
}
Since object is the base type for all reference types in .NET, static type checking can almost always be circumvented by casting a variable to the object type first and then to the target type. Using this trick, we can modify the code from our first example to make it compile:
string text = "String value";
int textLength = text.Length;
int textMonth = ((DateTime)(object)text).Month;
DateTime date = DateTime.Now;
int dateLength = ((string)(object)date).Length;
int dateMonth = date.Month;
Of course, thanks to dynamic type safety in C#, an InvalidCastException will still be thrown. This approach was taken advantage of in .NET to implement common data structures before the introduction of generics while still preserving type safety, albeit only at runtime. These data structures are still available today, although their use is discouraged in favor of their generic alternatives.
var list = new ArrayList();
list.Add("String value");
int length = ((string)list[0]).Length;
int month = ((DateTime)list[0]).Month; // no compiler error
ArrayList is an example of a non-generic data structure in the .NET framework. Since it doesn’t provide any type information about the values it contains, we must cast the values back to the correct type on our own before we can use them. If we cast the value to the wrong type as in the line marked with a comment, the compiler can’t detect that. The type checking will only happen at runtime.
Dynamic Binding in C#
In all the examples so far, we used static binding.
If the code tried to access a non-existent member of a type, it would result in a compilation error. Even when we cast a value to a different type, the compiler still performed the same checks for this new type which did not necessarily match the actual value.
In contrast, with dynamic binding, the compiler does not do any type checking at compile time. It simply assumes that the code is valid, no matter which member of the type it tries to access. All checking is done at runtime when an exception is thrown if the requested member of the type does not exist.
Dynamic binding is an important feature of dynamically-typed languages, such as JavaScript. However, although C# is primarily considered a statically-typed language with a high level of type safety, it also supports dynamic binding since C# 4.0 when this functionality was added to simplify interaction with dynamically-typed .NET languages such as IronPython and IronRuby, introduced at that time.
The Dynamic Keyword
The core of dynamic binding support in C# is the dynamic keyword. With it, variables can be declared to be dynamically-typed. For such variables, static type checking at compile time is completely disabled, as in the following example:
dynamic text = "String value";
int textLength = text.Length;
int textMonth = text.Month; // throws exception at runtime
dynamic date = DateTime.Now;
int dateLength = date.Length; // throws exception at runtime
int dateMonth = date.Month;
In the above example, using the dynamic keyword doesn’t make much sense. It only postpones the checking till runtime, while it could have already been done at compile time if we used static types. It brings no advantages but introduces two significant disadvantages:
- Our code could easily be defective as in the example above because type errors are not detected at compile time.
- Even if there are no defects in code, additional type checks are required at runtime which affects performance.
Of course, there are better use cases for the dynamic keyword, otherwise it wouldn’t have been added to the language. For example, it can be used to avoid the restriction of anonymous types to a single method because the type name is visible only to the compiler and therefore can’t be declared as the return value of a method. If the method is declared with a dynamic return value, the compiler will allow it to return a value of an anonymous type, although the exact type isn’t (and can’t be) declared explicitly:
public dynamic GetAnonymousType()
{
return new
{
Name = "John",
Surname = "Doe",
Age = 42
};
}
Of course, the returned value can be fully used outside of the method, with the disadvantage that no IntelliSense and compile time type checking will be available for it as neither the compiler nor the editor is aware of the actual type being returned:
dynamic value = GetAnonymousType();
Console.WriteLine($"{value.Name} {value.Surname}, {value.Age}");
Since anonymous types are declared as internal, this will only work within a single assembly. The code in different assemblies will still be able to get the value when calling the method, but any attempts to access its members will fail with a RuntimeBinderException.
However, that’s only the beginning of what can be done with dynamic type. A great example of how much value dynamic binding can add when used correctly is the JSON.NET library for serializing and deserializing JSON. It can be best explained with the following example:
string json = @"
{
""name"": ""John"",
""surname"": ""Doe"",
""age"": 42
}";
dynamic value = JObject.Parse(json);
Console.WriteLine($"{value.name} {value.surname}, {value.age}");
Like the anonymous type example from before, the Parse method returns a value which can be dynamically bound. This allows the JSON object properties to be accessed as the properties of the parsed object although the compiler could have no knowledge about them. We can pass in any JSON object at runtime and still access its properties in code. This functionality couldn’t have been implemented with anonymous types.
So, how was it implemented then? There are two helper objects in the .NET framework which we can use to implement similar functionalities.
ExpandoObject
ExpandoObject is the simpler one of the two. It allows members to be added to or removed from any instance of it at runtime. If assigned to a variable declared as dynamic, these members will be dynamically bound at runtime:
dynamic person = new ExpandoObject();
person.Name = "John";
person.Surname = "Doe";
person.Age = 42;
Console.WriteLine($"{person.Name} {person.Surname}, {person.Age}");
Members are not limited to being properties. They can be methods as well. To achieve that, a corresponding lambda can be assigned to a member, cast to a matching delegate type. Later, it can be invoked with the standard syntax for calling a method:
person.ToString = (Func<string>)(() => $"{person.Name} {person.Surname}, {person.Age}");
Console.WriteLine(person.ToString());
To see which members were added to the ExpandoObject at runtime, we can cast the instance to the IDictionary<string, object> interface and enumerate its key-value pairs:
var dictionary = (IDictionary<string, object>)person;
foreach (var member in dictionary)
{
Console.WriteLine($"{member.Key} = {member.Value}");
}
Using the same interface, we can also remove a member at runtime:
dictionary.Remove("ToString");
While ExpandoObject can be useful in simple scenarios, it gives very little control to the code instantiating and initializing it. Consuming code always has full access to the instance and can modify it to the same extent as the code creating it. Methods are added to it in a similar way to properties, i.e. as lambdas assigned to each individual instance. There is no way to define methods at the class level and automatically make them accessible from every instance.
DynamicObject
DynamicObject gives more control to the developer and avoids many of the disadvantages of ExpandoObject but at the same time requires more code to achieve similar results:
class MyDynamicObject : DynamicObject
{
private readonly Dictionary<string, object> members = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (members.ContainsKey(binder.Name))
{
result = members[binder.Name];
return true;
}
else
{
result = null;
return false;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
members[binder.Name] = value;
return true;
}
}
With the above implementation, MyDynamicObject supports dynamic adding of properties at runtime just like the ExpandoObject does:
dynamic person = new MyDynamicObject();
person.Name = "John";
person.Surname = "Doe";
person.Age = 42;
Console.WriteLine($"{person.Name} {person.Surname}, {person.Age}");
We can even add methods to it, using the same approach: by assigning lambdas as delegates to its members:
person.AsString = (Func<string>)(() => $"{person.Name} {person.Surname}, {person.Age}");
Console.WriteLine(person.AsString());
We didn’t use the standard method name ToString this time because it wouldn’t work as expected. If we did, DynamicObject’s default ToString method would still be called. Unlike ExpandoObject, DynamicObject first tries to bind all member accesses to its own members and only if that fails, it delegates the resolution to its TryGetMember method. Since DynamicObject has its ToString method just like any other object in the .NET framework, the call will be bound to it and TryGetMember won’t be called at all.
Thanks to this behavior, we can implement methods directly in our derived class and they will be invoked as expected:
public bool RemoveMember(string name)
{
return members.Remove(name);
}
If we need a more dynamic behavior, we can still override the TryInvokeMember method. In the following example, we expose all methods of our internal dictionary without having to write specific code for each method. This gives the caller access to it, similar to what ExpandoObject does.
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
try
{
var type = typeof(Dictionary<string, object>);
result = type.InvokeMember(binder.Name,
BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance,
null, members, args);
return true;
}
catch
{
result = null;
return false;
}
}
With the methods above present in our class, we can invoke both the statically defined RemoveMember method and all the dynamically defined methods the same way, if we are using dynamic binding. Any methods not defined in our class will be delegated by TryInvokeMember method to the inner dictionary using reflection.
person.Remove("AsString");
Console.WriteLine(person.ContainsKey("AsString"));
If we wanted to, we could also directly expose the inner dictionary when casting the object to the correct dictionary type just like ExpandoObject does. Of course, one way would be to overload the cast operator:
public static explicit operator Dictionary<string, object>(MyDynamicObject instance)
{
return instance.members;
}
If we needed to implement casting in a more dynamic manner, we could always override the TryConvert method instead:
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type.IsAssignableFrom(members.GetType()))
{
result = members;
return true;
}
else
{
result = null;
return false;
}
}
Both implementations would give us access to the inner dictionary by casting an instance of the class to the correct type:
var dictionary = (Dictionary<string, object>)person;
Both the ExpandoObject and the DynamicObject implement the same IDynamicMetaObjectProvider interface to implement the underlying dynamic binding logic. To get full control over the binding process, we can do the same. However, achieving our goal by using either ExpandoObject or DynamicObject is a better option because they make our implementation much simpler.
Conclusion:
Having support for dynamic binding in C# opens some possibilities which would not be available in a strictly strong typed language.
We will typically be using these possibilities when interacting with other languages and runtime environments which are more dynamically typed, or when using third-party libraries which take advantage of dynamic binding to improve usability.
In certain scenarios, we might even implement some dynamically bound classes ourselves. However, we should always do our best to only use dynamic binding when this is the only or the best available approach. Although we could also use it to circumvent strong typing in other cases, we should try to avoid that because the seemingly simpler code will very probably expose us to hidden bugs which could otherwise be detected by the compiler.
This article was technically reviewed by Yacoub Massad.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Damir Arh has many years of experience with software development and maintenance; from complex enterprise software projects to modern consumer-oriented mobile applications. Although he has worked with a wide spectrum of different languages, his favorite language remains C#. In his drive towards better development processes, he is a proponent of Test-driven development, Continuous Integration, and Continuous Deployment. He shares his knowledge by speaking at local user groups and conferences, blogging, and writing articles. He is an awarded Microsoft MVP for .NET since 2012.