A New Internet Library: Add Your Website/Blog or Suggest A Website/Blog to our Free Web Directory http://anil.myfunda.net.

Its very simple, free and SEO Friendly.
Submit Now....

Saturday, November 29, 2008

Constructors and Inheritance - Why is this still so painful?

Recently my team discovered a limitation in the RelativeDateTimeValidator that ships with the Enterprise Library Validation Application Block. This validator is used to check if a DateTime object occurs within a configured time before or after Now. It's a useful validator for checking things like birth dates and expiry dates. However we discovered that it assumes the date being validated is in local time - if the date is specified in UTC (which we do in our app) the calculation will be wrong. Fixing the logic was no big deal - it just involved checking if the DateTimeKind is UTC and if so, converting it to a local time before doing the validation. However like many people we didn't want to modify the original EntLib codebase, so instead I built a new class imaginatively titled RelativeDateTimeValidatorEx that inherits from RelativeDateTimeValidator and corrects this problem. But while the important code change was only a couple of lines, I was forced to manually implement every one of the 14 constructors from the base class (with no code beyond calling each base constructor) to ensure equivalent and compatible functionality. In my case I didn't need to change the public interface at all. But even in the (probably more common) case where a derived class includes new functionality that warrants new constructors, it's very common to want to include all of the base class's original constructors as well.

To provide a quick example (but thankfully with fewer constructors), imagine the following base class:

public class BaseClass  
{  
    public BaseClass(int a) : this(a, "Foo")  
    {  
    }  
    public BaseClass(int a, string b) : this(a, b, DateTime.Now)  
    {  
    }  
    public BaseClass(int a, DateTime c) : this(a, "Foo", c)  
    {  
    }  
    public BaseClass(int a, string b, DateTime c)  
    {  
        // Shared initialisation code  
    }  
}

Now if I want to build a new class that provides equivalent functionality and supports a new optional parameter, I'm left with this at a minimum:

public class DerivedClass : BaseClass  
{  
    public DerivedClass(int a) : base(a)  
    {  
    }  
    public DerivedClass(int a, string b) : base(a, b)  
    {  
    }  
    public DerivedClass(int a, DateTime c) : base(a, b, c)  
    {  
    }  
    public DerivedClass(int a, string b, DateTime c) : base(a, b, c)  
    {  
    }  
    public DerivedClass(int a, string b, DateTime c, long d) : base(a, b, c)  
    {  
        // New initialisation code  
    }  
}

.and if I want to offer more permutations, the number of constructors may grow exponentially.

The good news is that C# 4.0 will finally include support for optional parameters (a very useful feature that VB programmers have enjoyed for years), which will partly mitigate this issue by providing an alternative to writing a crap-load of constructors. However even with optional parameters, it would be nice if you could tell C# that you want your derived class to inherit all of the base class's constructors.

On a somewhat related note, it's often bothered me that there is no way to include constructors as a mandatory part of a class's contract. This comes up every time you use a plug-in pattern where you create instances of a class at runtime using Activator.CreateInstance and you require all of your plug-ins to be initialised with the same information. The most logical approach is to require each plug-in to implement the same constructor. While you can make this work by passing the constructor parameters to Activator.CreateInstance, this is completely untypesafe as the compiler can't enforce that all your plug-ins have the required signature (this is a common problem for people implementing custom EntLib plug-ins that receive their configuration properties via a NameValueCollection passed to the constructor. People who forget (or don't know) to implement this constructor often have a hard time figuring out what is wrong).

The other approach you sometimes see is to create the instances using a default (parameterless) constructor and set the initialisation parameters through a public interface method like Initialize(NameValueCollection properties). But this approach has its own downsides. First, it means your plug-in classes are most likely constructed in an unusable state. Second, it still suffers from the same type safety problems, as there is no guarantee that every plug-in class implements a parameterless constructor.

So why isn't it possible to specify constructors as a part of a class (or interface) contract? And to make it useful, there should be a compiler-verifiable way to create instances of unknown types that call the said constructor. I'm no language designer, but I'm thinking of something like this:

public class PlugIn  
{  
    public virtual PlugIn(NameValueCollection properties)  
    {  
    }  
}  
// Create an instance  
Type plugInType = Type.GetType(plugInTypeNameFromConfigFile);  
NameValueCollection properties = LoadPropertiesFromConfigFile();  
PlugIn myConcretePlugIn = Plugin.New(plugInType, properties); // Compiler-checked

This is probably a bastardisation of the virtual keyword but you get the idea. Another benefit of this approach is that you could safely new-up instances of generic types in a lot more situations. Currently you can specify a where T : new() constraint for a generic parameter that specifies that the generic type must have a default constructor. I've often thought this was overly restrictive, and that it should be possible to do something like where T : new(int, string). This may still be desirable, but if constructors could be specified as a part of a construct then the type alone could allow the code to create new instances. For example, if you had a generic parameter T where T : PlugIn then your code could safely execute T myPlugIn = new T(properties).

Anyway, end of rant. I'm interested in hearing whether any other programming languages have either of the features I've proposed, or if there are any good reasons I haven't thought of which would make these features undesirable or impossible.



Source Click Here.

No comments:

Post a Comment

Post your comments here:

Originals Enjoy