|Deep C# - Dynamic C#|
|Written by Mike James|
|Thursday, 16 January 2020|
Page 2 of 5
Before moving on to consider dynamic typing it is worth spending a few minutes considering how static typing works and how it determines the way things work in C#.
First, recall that in C# 2.0 you always had to determine the type of the result of an expression and use an appropriate type or super type to store it and this determination had to be made at compile time.That is C# 2.0 is a statically typed language.
Even now the compiler always looks at an expression, determines the type of the expression and makes sure that this is compatible the "assignment" variable.
Notice that this applies to assignment proper, method parameters and return types. Type determination can also affect the flow of control in a program - but static typing can only determine it at compile time.
For example, consider the two overloaded methods defined within some object:
then when you write:
which method is called depends on the type of X.
If X is an int the first method is called and if it is a double the second method is called.
You may well be used to the idea that overloaded methods are called according to signature but you might not have realized how important this makes the determination of type as part of the flow of control in your program.
The method call MyMethod(X) resolved according to the signature of the call might be thought to be expressed by the equivalent code:
but notice that this code actually determines the type of X at runtime not compile time. If there was some way for X to change to a double before the if MyMethodDouble would be called.
If you examine the code and remember that this is a static language you can see that MyMethodInt is always called - there is no possibility of anything else happening because X is always an int once it has been declared to be an int.
In this sense the whole if statement and testing is completely unnecessary you might as well write:
In a statically typed language there is no way a variable can change the type it is assigned when it is declared.
However even static typing is a little more subtle than this. The extra complexity comes from the idea that any type that derives from another type should be usable in place of the parent type.That is, if I declare a class as:
then MyClass2 can be used in place of MyClass and generally treated as if it was of type MyClass - this is the content of the Liskov substitution principle, a sub-class has all of the properties and methods of its parent class, by inheritance and can therefore be used in place of it.
For example a method like:
can be called with a parameter of type MyClass or MyClass2. So, from the point of view of method signatures, derived classes act like their parent classes. So what if we now overload MyMethod with a specific version for MyClass2?
Now if I call MyMethod with a parameter of type MyClass2 which method will be called?
The answer is, unsurprisingly the one with the signature that matches the parameter most closely. In this case the version of MyMethod with the MyClass2 parameter is called.
This is a general principle: the method with the signature that most closely matches the types used in the signature is used.
Although this use of derived types in method calls is a little more complicated notice that it uses the declared type of each variable and not what the variable actually references.
That is which version of the method is called can always be determined at compile time. Another way of saying this is that by default all method calls are early bound.
In fact the only example of late binding in C# occurs when a method is declared to be virtual. In this case the method that is called does depend on runtime type, not of its parameters but on the runtime type of the class it belongs to. That is, if MyMethod isn't declared as virtual then:
will always call the MyMethod defined as part of what ever type MyObject is declared to be. In other words, you can read through the program's code until you find:
and then without examining the program any further you can conclude with 100% certainty that the version of MyMethod that will be called is the one defined as part of MyClass - even if MyMethod has been overridden in a class derived from MyClass.
That is if MyMethod isn't virtual then (notice that the variable is of type MyClass but the object it references is MyClass2:
still calls the appropriate version of MyMethod defined as part of MyClass not any alternative defined in MyClass2. This is once again early binding as the method call is determined at compile time. That is the variable MyObject is of type MyClass so it is the MyClass version of MyMethod that is called no matter what MyClass2 does to it.
If, on the other hand, MyMethod is declared as virtual in MyClass and overriden correctly in MyClass2 then in this case late binding operates and:
does call the new version of MyMethod as defined as part of MyClass2.
This is what the whole virtual/non-virtual distinction is about. All non-virtual methods are early bound according to the type of the variable holding the reference to the object but virtual methods are late bound according to the type of the object the variable references at run time.
So apart from the use of virtual methods all binding in C# is early binding and only the declared type of a variable can influence what a program does.
As we will see dynamic typing promotes the determination of type to something that can determine what happens at runtime beyond the use of virtual.
But before we look at dynamic typing we need to take a look at a related feature that is often confused with dynamic typing - anonymous typing.
|Last Updated ( Thursday, 16 January 2020 )|