Class Member Overloading


Class member overloading allows you to keep a consistent interface by calling asingle logical method regardless of the type of data that will be passed in that method. When you use class member overloading, it is relatively easy to create situations where the physical procedure that will be invoked is not clear at first glance.

Ambiguous Overloading

The code in Listing 2-2 shows a base class member that is overloaded both within its own class and within a derived class. The task is to determine which procedure is going to be invoked by each of the test calls and also why.

Listing 2-2. Ambiguous Overloading
start example
 Option Strict On Class Test     Public Shared Sub Main()         Dim objMyTest As New Derived()         With objMyTest              .WriteLine(10)              .WriteLine(10.5)              .WriteLine("11")         End With         Console.ReadLine()    End Sub End Class Class Base     Public Sub WriteLine(ByVal AnyString As String)         Console.WriteLine(AnyString + " called Base:String")     End Sub     Public Sub WriteLine(ByVal AnyInteger As Integer)         Console.WriteLine(AnyInteger.ToString + " called Base:Integer")     End Sub End Class Class Derived : Inherits Base     Public Overloads Sub WriteLine(ByVal AnyDouble As Double)         Console.WriteLine(AnyDouble.ToString + " called Derived:Double")     End Sub End Class 
end example
 

Whereas the answers to the last two tests are easy to anticipate correctly, the first test is more difficult. The literal number 10 could be considered to be either an integer or a double , so therefore which of the two possible procedures will be invoked? If the compiler looks through the methods in the derived class first and uses any one that is found to be suitable, then the derived class method that accepts a double will be used. Alternatively, the compiler could look across all of the possible methods in the inheritance tree and then make a decision about the most appropriate method to call, which means that the base class that accepts an integer is the most likely candidate.

C# (and C++) developers normally know the answer to this question. The compiler performs overload resolution at each level of the inheritance hierarchy, starting with the most derived class. This breadth-first scan means that it will pick the most derived method that is suitable. Therefore, the result will be

start sidebar
 10 called Derived:Double 10.5 called Derived Double 11 called Base:String 
end sidebar
 

Unfortunately, this is the wrong answer when using VB .NET. The VB .NET compiler (unlike the C# compiler) looks across the whole inheritance tree before making a decision about which of the overloaded methods is most suitable. Because Overloads actually means hide by name and signature, the compiler takes the view that you are essentially adding a new member to the base class in this case. So the preceding code will produce the following result:

start sidebar
 10 called Base:Integer 10.5 called Derived:Double 11 called Base:String 
end sidebar
 

C# vs. VB .NET Overloading

As demonstrated in the previous section, C#'s way of resolving overloaded methods is different from VB .NET's. If you want to confuse a C# developer, examine the VB .NET code in Listing 2-3 and try to predict the result it will produce.

Listing 2-3. Member Overloading in VB .NET vs. C#
start example
 Option Strict On Class Test     Public Shared Sub Main()         Dim objDerived As New Derived()         Dim objTest As New MyTest()         objDerived.DoSomething(objTest)         Console.ReadLine()     End Sub End Class Class MyTest End Class Class Base     Public Overridable Sub DoSomething(ByVal NewValue As MyTest)         Console.WriteLine("Base:DoSomething(MyTest) called")     End Sub End Class Class Derived : Inherits Base     Public Overloads Overrides Sub DoSomething(ByVal NewValue As MyTest)         Console.WriteLine("Derived:DoSomething(MyTest) called")     End Sub     Public Overloads Sub DoSomething(ByVal NewValue As Object)         Console.WriteLine("Derived:DoSomething(Object) called")     End Sub End Class 
end example
 

This VB .NET program resolves the method overloading by showing what most developers would probably intuit as the most reasonable result given the ambiguous circumstances:

start sidebar
 Derived:DoSomething(MyTest) called 
end sidebar
 

Now translate the VB .NET code in Listing 2-3 into C#, and ask a C# developer to predict what this program will produce (see Listing 2-4).

Listing 2-4. C# Translation of Listing 2-3
start example
 class Test {     public static void Main()     {         Derived objDerived = new Derived();         MyTest objMyTest = new MyTest();         objDerived.DoSomething(objMyTest);         System.Console.ReadLine();     } } class MyTest {} class Base {     public virtual void DoSomething(MyTest NewValue)     {         System.Console.WriteLine("Base:DoSomething(MyTest) called");     } } class Derived : Base {     public override void DoSomething(MyTest NewValue)     {         System.Console.WriteLine("Derived:DoSomething(MyTest) called");     }     public void DoSomething(object NewValue)     {         System.Console.WriteLine("Derived:DoSomething(Object) called");     } } 
end example
 

This is an exact C# translation of the VB .NET code in Listing 2-3, so many developers will predict the same result. Unfortunately, C# produces the following different ”and maybe somewhat surprising ”result:

start sidebar
 Derived:DoSomething(Object) called 
end sidebar
 

In spite of the fact that the class instance passed exactly matches the parameter type of one of the members of the Derived class, the other more general member is the one actually chosen .

The reason for this is buried deep in the C# language specification. Any method marked as Override in C# is not added to the list of members considered as candidates for the resolution of an overload. Because of quirks like this, you should take some care when switching between VB .NET and C# to avoid the creation of quite subtle bugs .

The Overload That Broke the C# Developer's Back

This, however, is not the end of the member overload saga. Once again, I present a VB .NET program and its C# equivalent, and then attempt to predict the results of running each program. Listing 2-5 shows the VB .NET code.

Listing 2-5. More Member Overloading in VB .NET vs. C#
start example
 Option Strict On Class Test     Public Shared Sub Main()         Dim objDerived As New Derived()         objDerived.DoSomething(CLng(8))         Console.ReadLine()     End Sub End Class Class Base     Public Overridable Sub DoSomething(ByVal NewValue As Long)         Console.WriteLine("Base:DoSomething(Long) called")     End Sub     Public Sub DoSomething(ByVal NewValue As Double)         Console.WriteLine("Base:DoSomething(Double) called")     End Sub End Class Class Derived : Inherits Base     Public Overloads Overrides Sub DoSomething(ByVal NewValue As Long)         Console.WriteLine("Derived:DoSomething(Long) called")     End Sub     Public Overloads Sub DoSomething(ByVal NewValue As Integer)         Console.WriteLine("Derived:DoSomething(Integer) called")     End Sub End Class 
end example
 

Just as in Listing 2-3, this VB .NET program resolves the method overloading by showing what most developers would probably intuit as the most reasonable result given the ambiguous circumstances:

start sidebar
 Derived:DoSomething(Long) called 
end sidebar
 

The code shown in Listing 2-6 is a direct C# translation of the VB .NET program in Listing 2-5. Given that you have learned that members marked with Override are excluded from any C# member resolution group , what would a C# developer expect this program to produce?

Listing 2-6. C# Translation of Listing 2-5
start example
 class Test {     public static void Main()     {         Derived objDerived = new Derived();         objDerived.DoSomething((long) 8);         System.Console.ReadLine();     } } class Base {     public virtual void DoSomething(long NewValue)     {         System.Console.WriteLine("Base:DoSomething(Long) called");     }     public void DoSomething(double NewValue)     {         System.Console.WriteLine("Base:DoSomething(Double) called");     } } class Derived : Base {  override public void DoSomething(long NewValue)  {         System.Console.WriteLine("Derived:DoSomething(Long) called");     }     public void DoSomething(int NewValue)     {         System.Console.WriteLine("Derived:DoSomething(Integer) called");     } } 
end example
 

Just as in Listing 2-4, there is a member marked with Override here (the line in bold), and according to the C# language specification, that member won't be included in the group of members considered for overload resolution. So what will be the result of running this program?

start sidebar
 Derived:DoSomething(Long) called 
end sidebar
 

Now our hypothetical C# developer might be justified in feeling really confused ! Why is this program resolving the method overload in a different manner to the program in Listing 2-4, and why is the member that is being called not removed from the group of members considered for overload resolution?

The reasoning behind this is quite obvious once you've seen it, but it's rather subtle. Just as the C# language specification states, the Derived.DoSomething(long NewValue) member is not considered for the method resolution because it is marked with Override . In fact, Base. Do Something (long NewValue) is the only member in the group of possible overloads, so unsurprisingly that is the member actually chosen. But of course a derived member overrides that base member, so the derived method is the one executed. The trick here is to remember that the overload resolution process is just one step in the process of deciding which method is executed.

Contrast this with Listing 2-4. In that case, there is a derived method that is suitable, and C# will always use a derived method if it is considered suitable, even if a base method might seem more appropriate.

As stated earlier, you should take some care when switching between VB .NET and C# to avoid these types of bugs.




Comprehensive VB .NET Debugging
Comprehensive VB .NET Debugging
ISBN: 1590590503
EAN: 2147483647
Year: 2003
Pages: 160
Authors: Mark Pearce

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net