Item 34: Avoid Overloading Methods Defined in Base Classes

When a base class chooses the name of a member, it assigns the semantics to that name. Under no circumstances may the derived class use the same name for different purposes. And yet, there are many other reasons why a derived class may want to use the same name. It may want to implement the same semantics in a different way, or with different parameters. Sometimes that’s naturally supported by the language: Class designers declare virtual functions so that derived classes can implement semantics differently. Item 33 covered why using the new modifier could lead to hard-to-find bugs in your code. In this item, you’ll learn why creating overloads of methods that are defined in a base class leads to similar issues. You should not overload methods declared in a base class.

The rules for overload resolution in the C# language are necessarily complicated. Possible candidate methods might be declared in the target class, any of its base classes, any extension method using the class, and interfaces it implements. Add generic methods and generic extension methods, and it gets very complicated. Throw in optional parameters, and I’m not sure anyone could know exactly what the results will be. Do you really want to add more complexity to this situation? Creating overloads for methods declared in your base class adds more possibilities to the best overload match. That increases the chance of ambiguity. It increases the chance that your interpretation of the spec is different than the compilers, and it will certainly confuse your users. The solution is simple: Pick a different method name. It’s your class, and you certainly have enough brilliance to come up with a different name for a method, especially if the alternative is confusion for everyone using your types.

The guidance here is straightforward, and yet people always question if it really should be so strict. Maybe that’s because overloading sounds very much like overriding. Overriding virtual methods is such a core principle of object-oriented languages; that’s obviously not what I mean. Overloading means creating multiple methods with the same name and different parameter lists. Does overloading base class methods really have that much of an effect on overload resolution? Let’s look at the different ways where overloading methods in the base class can cause issues.

There are a lot of permutations to this problem. Let’s start simple. The interplay between overloads in base classes has a lot to do with base and derived classes used for parameters. For all the following examples, any class that begins with “B” is the base class, and any class that begins with “D” is the derived class. The samples use this class hierarchy for parameters:

public class B2 { }
public class D2 : B2 {}

Here’s a class with one method, using the derived parameter (D2):

public class B
{
    public void Foo(D2 parm)
    {
        Console.WriteLine("In B.Foo");
    }
}

Obviously, this snippet of code writes “In B.Foo”:

var obj1 = new D();
obj1.Bar(new D2());

Now, let’s add a new derived class with an overloaded method:

public class D : B
{
    public void Foo(B2 parm)
    {
        Console.WriteLine("In D.Foo");
    }
}

Now, what happens when you execute this code?

var obj2 = new D();
obj2.Foo(new D2());
obj2.Foo(new B2());

Both lines print “in D.Foo”. You always call the method in the derived class. Any number of developers would figure that the first call would print “in B.Foo”. However, even the simple overload rules can be surprising. The reason both calls resolve to D.Foo is that when there is a candidate method in the most derived compile-time type, that method is the better method. That’s still true when there is even a better match in a base class. Of course, this is very fragile. What do you suppose this does:

B obj3 = new D();
obj3.Foo(new D2());

I chose the words above very carefully because obj3 has the compile-time type of B (your Base class), even though the runtime type is D (your Derived class). Foo isn’t virtual; therefore, obj3.Foo() must resolve to B.Foo.

If your poor users actually want to get the resolution rules they might expect, they need to use casts:

var obj4 = new D();
                                                            
((B)obj4).Foo(new D2());
obj4.Foo(new B2());

If your API forces this kind of construct on your users, you’ve failed. You can easily add a bit more confusion. Add one method to your base class, B:

public class B
{
    public void Foo(D2 parm)
    {
        Console.WriteLine("In B.Foo");
    }
    
    public void Bar(B2 parm)
    {
        Console.WriteLine("In B.Bar");
    }
}

Clearly, the following code prints “In B.Bar”:

var obj1 = new D();
obj1.Bar(new D2());

Now, add a different overload, and include an optional parameter:

public class D : B
{
    public void Foo(B2 parm)
    {
        Console.WriteLine("In D.Foo");
    }
    
    public void Bar(B2 parm1, B2 parm2 = null)
    {
        Console.WriteLine("In D.Bar");
    }
}

Hopefully, you’ve already seen what will happen here. This same snippet of code now prints “In D.Bar” (you’re calling your derived class again):

var obj1 = new D();
obj1.Bar(new D2());

The only way to get at the method in the base class (again) is to provide a cast in the calling code.

These examples show the kinds of problems you can get into with one parameter method. The issues become more and more confusing as you add parameters based on generics. Suppose you add this method:

public class B
{
    public void Foo(D2 parm)
    {
        Console.WriteLine("In B.Foo");
    }
    
    public void Bar(B2 parm)
    {
        Console.WriteLine("In B.Bar");
    }
    
    public void Foo2(IEnumerable<D2> parm)
    {
        Console.WriteLine("In B.Foo2");
    }
}

Then, provide a different overload in the derived class:

public class D : B
{
    public void Foo(B2 parm)
    {
        Console.WriteLine("In D.Foo");
    }
    
    public void Bar(B2 parm1, B2 parm2 = null)
    {
        Console.WriteLine("In D.Bar");
    }
    public void Foo2(IEnumerable<B2> parm)
    {
        Console.WriteLine("In D.Foo2");
    }
}

Call Foo2 in a manner similar to before:

var sequence = new List<D2> { new D2(), new D2() };
var obj2 = new D();
    
obj2.Foo2(sequence);

What do you suppose gets printed this time? If you’ve been paying attention, you’d figure that “In D.Foo2” gets printed. That answer gets you partial credit. That is what happens in C# 4.0. Starting in C# 4.0, generic interfaces support covariance and contravariance, which means D.Foo2 is a candidate method for an IEnumerable<D2> when its formal parameter type is an IEnumerable<B2>. However, earlier versions of C# do not support generic variance. Generic parameters are invariant. In those versions, D.Foo2 is not a candidate method when the parameter is an IEnumerable<D2>. The only candidate method is B.Foo2, which is the correct answer in those ­versions.

The code samples above showed that you sometimes need casts to help the compiler pick the method you want in many complicated situations. In the real world, you’ll undoubtedly run into situations where you need to use casts because class hierarchies, implemented interfaces, and extension methods have conspired to make the method you want, not the method the compiler picks as the “best” method. But the fact that real-world situations are occasionally ugly does not mean you should add to the problem by creating more overloads yourself.

Now you can amaze your friends at programmer cocktail parties with a more in-depth knowledge of overload resolution in C#. It can be useful information to have, and the more you know about your chosen language the better you’ll be as a developer. But don’t expect your users to have the same level of knowledge. More importantly, don’t rely on everyone having that kind of detailed knowledge of how overload resolution works to be able to use your API. Instead, don’t overload methods declared in a base class. It doesn’t provide any value, and it will only lead to confusion among your users.

Item 42: Understand How to Make Use of the Expression API

.NET has had APIs that enable you to reflect on types or to create code at runtime. The ability to examine code or create code at runtime is very powerful. There are many different problems that are best solved by inspecting code or dynamically generating code. The problem with these APIs is that they are very low level and quite difficult to work with. As developers, we crave an easier way to dynamically solve problems.

Now that C# has added LINQ and dynamic support, you have a better way than the classic Reflection APIs: expressions and expression trees. Expressions look like code. And, in many uses, expressions do compile down to delegates. However, you can ask for expressions in an Expression format. When you do that, you have an object that represents the code you want to execute. You can examine that expression, much like you can examine a class using the Reflection APIs. In the other direction, you can build an expression to create code at runtime. Once you create the expression tree you can compile and execute the expression. The possibilities are endless. After all, you are creating code at runtime. I’ll describe two common tasks where expressions can make your life much easier.

The first solves a common problem in communication frameworks. The typical workflow for using WCF, remoting, or Web services is to use some code generation tool to generate a client-side proxy for a particular service. It works, but it is a somewhat heavyweight solution. You’ll generate hundreds of lines of code. You’ll need to update the proxy whenever the server gets a new method, or changes parameter lists. Instead, suppose you could write something like this:

var client = new ClientProxy<IService>();
var result = client.CallInterface<string>(
    srver => srver.DoWork(172));

Here, the ClientProxy<T> knows how to put each argument and method call on the wire. However, it doesn’t know anything about the service you’re actually accessing. Rather than relying on some out of band code generator, it will use expression trees and generics to figure out what method you called, and what parameters you used.

The CallInterface() method takes one parameter, which is an Expression

<Func<T, TResult>>. The input parameter (of type T) represents an object that implements IService. TResult, of course, is whatever the particular method returns. The parameter is an expression, and you don’t even need an instance of an object that implements IService to write this code. The core algorithm is in the CallInterface() method.

public TResult CallInterface<TResult>(Expression<
    Func<T, TResult>> op)
{
    var exp = op.Body as MethodCallExpression;
    var methodName = exp.Method.Name;
    var methodInfo = exp.Method;
    var allParameters = from element in exp.Arguments
                        select processArgument(element);
    Console.WriteLine("Calling {0}", methodName);
    
    foreach (var parm in allParameters)
        Console.WriteLine(
            "\tParameter type = {0}, Value = {1}",
            parm.Item1, parm.Item2);
    
    return default(TResult);
}
    
private Tuple<Type, object> processArgument(Expression
    element)
{
    object argument = default(object);
    LambdaExpression l = Expression.Lambda(
        Expression.Convert(element, element.Type));
    Type parmType = l.ReturnType;
    argument = l.Compile().DynamicInvoke();
    return Tuple.Create(parmType, argument);
}

Starting from the beginning of CallInterface, the first thing this code does is look at the body of the expression tree. That’s the part on the right side of the lambda operator. Look back at the example where I used ­CallInterface(). That example called it with srver.DoWork(172). It is a MethodCallExpression, and that MethodCallExpression contains all the information you need to understand all the parameters and the method name invoked. The method name is pretty simple: It’s stored in the Name property of the Method property. In this example, that would be ‘DoWork’. The LINQ query processes any and all parameters to this method. The interesting work in is processArgument.

processArgument evaluates each parameter expression. In the example above, there is only one argument, and it happens to be a constant, the value 172. However, that’s not very robust, so this code takes a different strategy. It’s not robust, because any of the parameters could be method calls, property or indexer accessors, or even field accessors. Any of the method calls could also contain parameters of any of those types. Instead of trying to parse everything, this method does that hard work by leveraging the LambdaExpression type and evaluating each parameter expression. Every parameter expression, even the ConstantExpression, could be expressed as the return value from a lambda expression. ProcessArgument() converts the parameter to a LambdaExpression. In the case of the constant expression, it would convert to a lambda that is the equivalent of () => 172. This method converts each parameter to a lambda expression because a lambda expression can be compiled into a delegate and that delegate can be invoked. In the case of the parameter expression, it creates a delegate that returns the constant value 172. More complicated expressions would create more complicated lambda expressions.

Once the lambda expression has been created, you can retrieve the type of the parameter from the lambda. Notice that this method does not perform any processing on the parameters. The code to evaluate the parameters in the lambda expression would be executed when the lambda expression is invoked. The beauty of this is that it could even contain other calls to ­CallInterface(). Constructs like this just work:

client.CallInterface(srver => srver.DoWork(
    client.CallInterface(srv => srv.GetANumber())));

This technique shows you how you can use expression trees to determine at runtime what code the user wishes to execute. It’s hard to show in a book, but because ClientProxy<T> is a generic class that uses the service interface as a type parameter, the CallInterface method is strongly typed. The method call in the lambda expression must be a member method defined on the server.

The first example showed you how to parse expressions to convert code (or at least expressions that define code) into data elements you can use to implement runtime algorithms. The second example shows the opposite direction: Sometimes you want to generate code at runtime. One common problem in large systems is to create an object of some destination type from some related source type. For example, your large enterprise may contain systems from different vendors each of which has a different type defined for a contact (among other types). Sure, you could type methods by hand, but that’s tedious. It would be much better to create some kind of type that “figures out” the obvious implementation. You’d like to just write this code:

var converter = new Converter<SourceContact,
    DestinationContact>();
DestinationContact dest2 = converter.ConvertFrom(source);

You’d expect the converter to copy every property from the source to the destination where the properties have the same name and the source object has a public get accessor and the destination type has a public set accessor. This kind of runtime code generation can be best handled by creating an expression, and then compiling and executing it. You want to generate code that does something like this:

// Not legal C#, explanation only
TDest ConvertFromImaginary(TSource source)
{
    TDest destination = new TDest();
    foreach (var prop in sharedProperties)
        destination.prop = source.prop;
    return destination;
}

You need to create an expression that creates code that executes the pseudo code written above. Here’s the full method to create that expression and compile it to a function. Immediately following the listing, I’ll explain all the parts of this method in detail. You’ll see that while it’s a bit thorny at first, it’s nothing you can’t handle.

private void createConverterIfNeeded()
{
    if (converter == null)
    {
        var source = Expression.Parameter(typeof(TSource),
            "source");
        var dest = Expression.Variable(typeof(TDest),
            "dest");
    
        var assignments = from srcProp in
                          typeof(TSource).GetProperties(
                              BindingFlags.Public |
                              BindingFlags.Instance)
                          where srcProp.CanRead
                          let destProp = typeof(TDest).
                              GetProperty(
                              srcProp.Name,
                              BindingFlags.Public |
                              BindingFlags.Instance)
                          where (destProp != null) &&
                              (destProp.CanWrite)
                          select Expression.Assign(
                              Expression.Property(dest,
                                  destProp),
                              Expression.Property(source,
                                  srcProp));
    
        // put together the body:
        var body = new List<Expression>();
        body.Add(Expression.Assign(dest,
            Expression.New(typeof(TDest))));
        body.AddRange(assignments);
        body.Add(dest);
    
        var expr =
            Expression.Lambda<Func<TSource, TDest>>(
                Expression.Block(
                new[] { dest }, // expression parameters
                body.ToArray() // body
                ),
                source // lambda expression
            );
    
        var func = expr.Compile();
        converter = func;
    }
}

This method creates code that mimics the pseudo code shown before. First, you declare the parameter:

var source = Expression.Parameter(typeof(TSource), "source");

Then, you have to declare a local variable to hold the destination:

var dest = Expression.Variable(typeof(TDest), "dest");

The bulk of the method is the code that assigns properties from the source object to the destination object. I wrote this code as a LINQ query. The source sequence of the LINQ query is the set of all public instance properties in the source object where there is a get accessor:

from srcProp in typeof(TSource).GetProperties(
                BindingFlags.Public | BindingFlags.Instance)
                where srcProp.CanRead

The let declares a local variable that holds the property of the same name in the destination type. It may be null, if the destination type does not have a property of the correct type:

let destProp = typeof(TDest).GetProperty(
               srcProp.Name,
               BindingFlags.Public | BindingFlags.Instance)
               where (destProp != null) &&
                     (destProp.CanWrite)

The projection of the query is a sequence of assignment statements that assigns the property of the destination object to the value of the same property name in the source object:

select Expression.Assign(
    Expression.Property(dest, destProp),
    Expression.Property(source, srcProp));

The rest of the method builds the body of the lambda expression. The Block() method of the Expression class needs all the statements in an array of Expression. The next step is to create a List<Expression> where you can add all the statements. The list can be easily converted to an array.

var body = new List<Expression>();
body.Add(Expression.Assign(dest,
    Expression.New(typeof(TDest))));
body.AddRange(assignments);
body.Add(dest);

Finally, it’s time to build a lambda that returns the destination object and contains all the statements built so far:

var expr =
    Expression.Lambda<Func<TSource, TDest>>(
        Expression.Block(
        new[] { dest }, // expression parameters
        body.ToArray() // body
        ),
        source // lambda expression
    );

That’s all the code you need. Time to compile it and turn it into a delegate that you can call:

var func = expr.Compile();
converter = func;

That is complicated, and it’s not the easiest to write. You’ll often find ­compiler-like errors at runtime until you get the expressions built correctly. It’s also clearly not the best way to approach simple problems. But even so, the Expression APIs are much simpler than their predecessors in the Reflection APIs. That’s when you should use the Expression APIs: When you think you want to use reflection, try to solve the problem using the Expression APIs instead.

The Expression APIs can be used in two very different ways: You can create methods that take expressions as parameters, which enables you to parse those expressions and create code based on the concepts behind the expressions that were called. Also, the Expression APIs enable you to create code at runtime. You can create classes that write code, and then execute the code they’ve written. It’s a very powerful way to solve some of the more difficult general purpose problems you’ll encounter.