Late Binding - Myths and Reality
Written by Mike James   
Tuesday, 30 September 2014
Article Index
Late Binding - Myths and Reality
Casting
Real late binding

Banner

 

We All Know The Type

When you see a late binding example of this sort you have to try to imagine that you really don’t know the type being used at run time at design time – this is the whole point of late binding.

But wait a minute.

Suppose you accept this as the absolute truth, that we don’t know the type until run time.

How can you possibly know what members to expect?

How can you fill in the missing “Method1” specification if you really don’t know the type that is being used until run time?

In fact what use to you is an instance of an unknown type?

The fact of the matter is that you must know what the run time type is at design time to write any code that makes use of the object, even if you are using reflection.

To be more precise you have to know the type is at least a given class or any of its sub-classes because you need to know that a minimal set of methods and properties exist to be able to make use of them. That is it doesn't matter if the class has other methods than "Method1" because you aren't going to call them but it must have a "Method1" if you are going to avoid a runtime error.

If this is the case then why not take the same approach to the inputs as you do to the output. The object that Method1 returns is actually an int and a down cast from object to int solves the problem of using this “late bound” type completely.

So why not use a cast for the method call?

For example the following very simple code achieves the same result reflection or dynamic type:

int count =((Class1)late).Method1("Hello World");

As we know that late is actually an instance of Class1 we simply cast to that type and use the method we know it has. Notice that this works even if the object is a sub-class of Class1.

What is more, you even get the advantage of IntelliSense prompting to select the method. Of course if we get it wrong and late isn’t of type Class1, or if Class1 doesn’t have a Method1, then we generate a run time error but then so do other approaches to late binding.

It is also very easy to make sure that no runtime error actually occurs using:

Class1 temp = late as Class1;
if(temp!=null){
 int count = temp.Method1("Hello World");
}else{
 int count=0;
}

The as operator casts to the type if it can or returns a null reference if it can’t.

You could also claim that this method of late binding is strongly typed. You state clearly what the expected run time type of the object is and what happens if you are wrong.

You can use the same approach in VB .NET using the CType function to down cast.

 

 

 csharp

Conditional Casting

At this point you are probably thinking that I’m cheating.

In real late binding you really cannot know what the type is without using reflection to determine the runtime type.

True enough but, as the earlier argument indicates, the run time type of a late bound instance has to be one of a small number of possibilities, otherwise you couldn’t actually write the code at design time to handle it.

In fact there is another alternative which allows there to be an unlimited number of alternatives but I’ll come back to this in a moment.

For example, you might have the possibility that there are two classes, Class1 and Class2 with Method1 and Method2 respectively and which one is late bound is arbitrary. Such an example usually needs reflection-based late binding. In fact it is just as simple to handle using casting, but this time conditional casting.

First introduce Class2 to the example:

class Class2{
 public string Method2() {
  return "Hello Later";
 }
}

Its Method2 has a different signature and return type but this doesn’t make a great deal of difference.

We need a suitable mechanism for generating a type that I can’t possibly know until run time.

Randomly creating either Class1 or Class2 seems to fit this bill:

object late;
Random rnd=new Random();
if (rnd.NextDouble() < 0.5){
 late = new Class1();
}else{
 late = new Class2();
}

Now late references either Class1 or Class2 with a 50% probability of either.

Of course the solution to the problem is to simply try to cast to Class1 or Class2 and call the method corresponding to the one that works!

Class1 temp1 = late as Class1;
if(temp1!=null){
 int count = temp1.Method1("Hello World");
}else{
 int count=0;
}

Class2 temp2= late as Class2;
if(temp2!=null){
 String message = temp2.Method2();
}else{
 String message="";
}

This code works and it doesn’t produce any run time errors even if late turns out to be a reference to something other than the two classes in question. So in practice it isn’t any worse than using reflection or dynamic and in that case you would still have to test to discover which of Class1 or Class2 you had at run time.

It is also reasonable to say that conditional casting is a more strongly typed approach than reflection because at each stage you are stating your run time type assumptions and what happens if you are wrong – it really is type-based.

Of course you can invent more logical and neater ways to code the general idea. For example:

if (late is Class1){
 int count = ((Class1)late).Method1("Hello World");
}
if (late is Class2){
 String message =((Class2)late).Method2();
}

Inheritance and Conditional Casting

You may still not be happy with the example because in practice late binding is usually associated with a hierarchy of classes rather than a mutually exclusive choice between a small number of incompatible classes.

For example, if we change Class2 to be:

class Class2:Class1

Class2 now has a Method1 member and, from the point of calling Method1, it doesn’t matter if we treat Class2 as itself or as an example of Class1. For example:

if (late is Class1){
 int count = ((Class1)late).Method1("Hello World");
}

results in Method1 being called irrespective of whether or not late is Class1 or Class2. The reason is that the “is” operator returns true if late is of type Class1 or can be safely cast to Class1.

There is the small matter of polymorphism to consider in all of this.

For example, if Class2 overrides Method 1 as:

public new int  Method1(string text) {
 return 2 * text.Length;
}

i.e. returning twice the length of the string, then which method you call for a Class2 object depends on whether you treat it as a Class1 object when you get the original method or a Class2 object when you get the new method. That is it matters what type you cast the object to.

In other words if late is a reference to a  Class2 object:

int count = ((Class1)late).Method1("Hello");

calls the method defined in Class1 and if you use:

int count = ((Class2)late).Method1("Hello");

this calls the method in defined in Class2.

Also notice that the result of the cast isn't changed if Method1 is virtual and overriden in Class2. The difference between virtual and non-virtual only changes which method is called when the referencing variable type is Class1 and the object referenced is Class2.

That is if late is a reference to a Class2 object then what you get when you write:

Class1 temp = (Class2)late;
int count=temp.Method1("Hello");

depends on how Method1 is declared. If it is non-virtual then the call to Method1 uses Class1's definition even thought the object is of type Class2.

For non-virtual methods the method called depends on the design time type of temp. However if Method1 is virtual in Class1 and overridden in Class2 then it is Class2's version of the method that is called. That is for virtual methods the method called depends on the runtime type of the object referenced by temp.

And yes this is complicated as the behaviour all depends on whether or not you assign your cast to another variable.

If you use dynamic for the late reference then the behaviour is more or less the same. Which method you call is determined by the runtime type of the instance. That is:

dynamic late;
...
late.Method1("Hello");
 

results in Class1's Method1 being used if late is a reference to a Class1 object and to Class2's Method1 if it is a reference to a Class2 object. The casts are implied and once again virtual/non-virtual makes no difference unless you assign the cast to another variable before using it.

Banner

 

<ASIN:0596002521>

<ASIN:0321658701>
<ASIN:0596004206>

<ASIN:1118162137>

<ASIN:0321877586>

<ASIN:1449343503>

<ASIN:B00JDMPO7K>

<ASIN:1466581409>

 



Last Updated ( Tuesday, 30 September 2014 )