Lets say I have a library, version 1.0.0, with the following contents:
public class Class1
{
public virtual void Test()
{
Console.WriteLine( "Library:Class1 - Test" );
Console.WriteLine( "" );
}
}
public class Class2 : Class1
{
}
and I reference this library in a console application with the following contents:
class Program
{
static void Main( string[] args )
{
var c3 = new Class3();
c3.Test();
Console.ReadKey();
}
}
public class Class3 : ClassLibrary1.Class2
{
public override void Test()
{
Console.WriteLine("Console:Class3 - Test");
base.Test();
}
}
Running the program will output the following:
Console:Class3 - Test
Library:Class1 - Test
If I build a new version of the library, version 2.0.0, looking like this:
public class Class1
{
public virtual void Test()
{
Console.WriteLine( "Library:Class1 - Test V2" );
Console.WriteLine( "" );
}
}
public class Class2 : Class1
{
public override void Test()
{
Console.WriteLine("Library:Class2 - Test V2");
base.Test();
}
}
and copy this version to the bin folder containing my console program and run it, the results are:
Console:Class3 - Test
Library:Class1 - Test V2
I.e, the Class2.Test method is never executed, the base.Test call in Class3.Test seems to be bound to Class1.Test since Class2.Test didn't exist when the console program was compiled. This was very surprising to me and could be a big problem in situations where you deploy new versions of a library without recompiling applications.
Does anyone else have experience with this?
Are there any good solutions?
This makes it tempting to add empty overrides that just calls base in case I need to add some code at that level in the future...
Edit:
It seems to be established that the call is bound to the first existing base method at compile time. I wonder why. If I build my console program with a reference to version 2 of my library (which should mean that the call is compiled to invoke Class2.Test ) and then replace the dll in the bin folder with version 1 the result is, as expected:
Console:Class3 - Test
Library:Class1 - Test
So there is no runtime error when Class2.Test doesn't exist. Why couldn't the base call have been compiled to invoke Class2.Test in the first place?
It would be interesting to get a comment from Eric Lippert or someone else who works with the compiler...
This was the subject of my blog on March 29th:
http://blogs.msdn.com/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx
Turns out that C# 1.0 did it your way, and that this decision causes some interesting crashes and performance problems. We switched it to do it the new way in C# 2.0.
I learned a lot from this question. See the blog for details.
When I build the executable with the first version of the library (I named mine "Thing"), and disassemble it I get:
L_000d: call instance void [Thing]Thing.Class1::Test()
Rebuilding it with the new DLL referenced:
L_000d: call instance void [Thing]Thing.Class2::Test()
So that confirms the decision to which method is referred to is made compile-time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With