Skip Navigation Links

Specialization and Substitutability

In the article on inheritance, you saw how one class can derive from another. The derived class inherits the capabilities of the base and polymorphism enables us to change the behaviours of that class by adding capabilities.

Generally, a class should have well defined purpose and will provide a specific set of functionality. The first class in a hierarchy will be as generalised as possible and classes that derive from these base classes will be more and more specialized. An analogy for this idea is to imagine trees defined as a hierarchy of classes.

The most general type of tree is a trunk with branches having leaves. In the family of trees, we can specialize into two subtypes, Deciduous and Evergreen trees. Each of these types can specialize further into, for example, Oak or Ash that specialize from Deciduous and Holly or Pine that derive from the Evergreen type.

When we program, we will use a variable to store a type or a parameter in which a type may be passed. The actual data that can be passed in the parameter or stored in the variable depends upon the relationship between the declared type and the actual type.

Barbera Liskov, a computer scientist at M.I.T in the 1980's and now Institute Professor at the same university published a paper that showed how a variable could contain a type or a more specialized subtype. In short, given a storage location for type T we can subtitute any subtype S that derives in some way from the base type T.

To illustrate this, the slide show below explains the substitutions that would work for our Tree based heirarchy.

Covariance and contravariance

These are two terms that are related to the substitution principle and refer to the way types may be used when passed to parameters or when returned from methods.

Covariance rules apply when passing a parameter and when a type is passed which is different to but which may be converted to the type specified in the parameter declaration. Covariance allows that a type may undergo an implicit conversion if the type is a subtype of the declared type or if that conversion can be made with no loss of precision.  For example, when we declare a method like so:

        static void DoSomething(float f)

        {

        }

 

        static void Main()

        {

            int n = 10;

            float f = 10.5f;

            double d = 10.5;

 

            DoSomething(n);

            DoSomething(f);

            DoSomething(d); <-- Causes a compiler error because doubles are not covariant with floats

        }

 

The compiler will refuse to compile the last line because the double cannot be used without an explicit conversion that acknowledges that loss of precision is acceptable.

Similarly, when we return a value from a method, covariance rules apply like so:

        static float DoSomething1()

        {

            return (int)10;

        }

 

        static float DoSomething2()

        {

            return 10.5f;

        }

 

        static float DoSomething3()

        {

            return (double)10.5; <--Refuses to compile because a double cannot be returned where a float is expected

        }

Contravariance allows for a type to be used where a more specialized type is specified. For example the code which passes a double in a parameter designated for a float would not present a problem.

In the C# programming language support for contravariant operations has recently been added in C# 4.0. The specific cases are an advanced level subject that are not relavent to this discussion

 
Sponsored By
DaraizeTechnologies.com
Bob Powell

Create your badge

Copyright © Bob Powell 2000-2012.  All rights reserved.