GOTCHA 28 Polymorphism kicks in prematurely


GOTCHA #28 Polymorphism kicks in prematurely

Polymorphism is the most cherished feature in object-oriented programming. An important tenet in object modeling is that objects be kept in a valid state at all times. Ideally, you should never be able to invoke methods on an object until it has been fully initialized. Unfortunately in .NET, it isn't difficult to violate this with the use of polymorphism. Unlike C++, in .NET polymorphism kicks in even before the execution of the constructor has completed. This behavior is similar to Java.

Let's review polymorphism for a moment. It assures that the virtual/overridable method that is called is based on the real type of the object, and not just the type of the reference used to invoke it. For instance, say foo() is a virtual/overridable method on a base class, and a class that derives from the base overrides that method. Assume also that you have two references baseReference and derivedReference of the base type and derived type. Let both of these references actually refer to the same instance of the derived class. Now, regardless of how the method is called, either as baseReference.foo() or derivedReference.foo(), the same method foo() in the derived class is invoked. This is due to the effect of polymorphism or dynamic binding.

While this sounds great, the problem is that polymorphism enters into the picture before the derived class's constructor is even called. Consider Example 3-21.

Example 3-21. Polymorphism during construction

C# (PolymorphismTooSoon)

 //Room.cs using System; namespace ProblemPolymorphismConstruction {     public class Room     {         public void OpenWindow()         {             Console.WriteLine("Room window open");         }         public void CloseWindow()         {             Console.WriteLine("Room window closed");         }     } } //ExecutiveRoom.cs. using System; namespace ProblemPolymorphismConstruction {     public class ExecutiveRoom : Room     {     } } //Employee.cs using System; namespace ProblemPolymorphismConstruction {     public class Employee     {          public Employee()         {             Console.WriteLine("Employee's constructor called");              Work();         }         public virtual void Work()         {             Console.WriteLine("Employee is working");         }     } } //Manager.cs using System; namespace ProblemPolymorphismConstruction {      public class Manager : Employee     {         private Room theRoom = null;         private int managementLevel = 0;         public Manager(int level)         {             Console.WriteLine("Manager's constructor called");             managementLevel = level;             if (level < 2)                 theRoom = new Room();             else                theRoom = new ExecutiveRoom();         }          public override void Work()         {             Console.WriteLine("Manager's work called");              theRoom.OpenWindow();             base.Work();         }     } } //User.cs using System; namespace ProblemPolymorphismConstruction {     class User     {         static void Main(string[] args)         {             Console.WriteLine("Creating Manager");              Manager mgr = new Manager(1);             Console.WriteLine("Done");         }     } } 

VB.NET (PolymorphismTooSoon)

 'Room.vb Public Class Room     Public Sub OpenWindow()         Console.WriteLine("Room window open")     End Sub     Public Sub CloseWindow()         Console.WriteLine("Room window closed")     End Sub End Class 'ExecutiveRoom.vb Public Class ExecutiveRoom     Inherits Room End Class 'Employee.vb Public Class Employee      Public Sub New()         Console.WriteLine("Employee's constructor called")          Work()     End Sub     Public Overridable Sub Work()         Console.WriteLine("Employee is working")     End Sub End Class 'Manager.vb  Public Class Manager     Inherits Employee     Private theRoom As Room = Nothing     Private managementLevel As Integer = 0     Public Sub New(ByVal level As Integer)         Console.WriteLine("Manager's constructor called")         managementLevel = level         If level < 2 Then             theRoom = New Room         Else             theRoom = New ExecutiveRoom         End If     End Sub      Public Overrides Sub Work()         Console.WriteLine("Manager's work called")          theRoom.OpenWindow()         MyBase.Work()     End Sub End Class 'User.vb Module User     Sub Main()         Console.WriteLine("Creating Manager")          Dim mgr As New Manager(1)         Console.WriteLine("Done")     End Sub End Module 

In the example given above, you have a Room class with OpenWindow() and CloseWindow() methods. The ExecutiveRoom derives from Room, but does not have any additional functionality as yet. The Employee has a constructor that invokes its Work() method. The Work() method, however, is declared virtual/overridable in the Employee class.

In the Manager class, which inherits from Employee, you have a reference of type Room. Depending on the Manager's level, in the constructor of the Manager, you assign the theRoom reference to either an instance of Room or an instance of ExecutiveRoom. In the overridden Work() method in the Manager class, you invoke the method on theRoom to open the window and then invoke the base class's Work() method. Looks reasonable so far, doesn't it? But when you execute this program you get a NullReferenceException as shown in Figure 3-18.

Figure 3-18. Exception from Example 3-21


Notice that in the creation of the Manager object, the Employee's constructor is called first. From the Employee's constructor, the call to Work() polymorphically calls Manager.Work().

Why? In the Employee constructor, even though the self reference this/Me is of type Employee, the real instance is of type Manager. But at this point, the constructor of Manager has not been invoked. As a result, the reference theRoom is still null/Nothing. The Work() method, however, assumes that the object has been constructed and tries to access theRoom. Hence the NullReferenceException.

Ideally, no method should ever be called on an object until its constructor has completed. However, the above example shows that there are situations where this can happen.

As a side note, if you initialize theRoom at the point of declaration to a Room instance, you half-fix the problem. The C# code will run fine, but the VB.NET code will still throw the exception. The reason for this? The difference in the sequence of initialization between the two languages, as discussed in Gotcha #27, "Object initialization sequence isn't consistent."

IN A NUTSHELL

Understand the consequence of calling virtual/overridable methods from within a constructor. If you need to further initialize your object, provide an Init() method that users of your object can call after the constructor completes. This even has a name: two-phase construction.

SEE ALSO

Gotcha #23, "Copy Constructor hampers exensibility," Gotcha #24, "Clone() has limitations," Gotcha #27, "Object initialization sequence isn't consistent," Gotcha #43, "Using new/shadows causes "hideous hiding"," Gotcha #44, "Compilers are lenient toward forgotten override/overrides," Gotcha #45, "Compilers lean toward hiding virtual methods," and Gotcha #47, "Signature mismatches can lead to method hiding."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

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