Calling .NET Return-Type-Based Overloaded Methods

10:28 am programming

This came dangerously close to a “Stupid .NET Trick”, but I’ve figured out an elegant solution. As far as I can see though, it wasn’t documented on Google, so I decided to post it here.

In Java (and, I suspect, many other languages), a method signature is determined by the method’s name and its parameters, but not by it’s return type. Thus, you cannot have two methods that differ only by their return type:

public boolean convertTo(String value) { ... }
public int convertTo(String value) { ... }

Here, the method signatures are identical, and thus the compiler sees two duplicate methods, not an overload. The usual way around this is to suffix the return type to the end of the method name.

C# nearly has the same restriction; you cannot write two (simple) methods in the same class that differ only by return type. However: the .NET runtime does not have this restriction. C# (and VB, and presumably the other CLR languages) take advantage of this by allowing overloading based on return types when implementing different interfaces.

For example: Consider these interfaces:

public interface Foo {
    void perform();
}
public interface Bar {
    int perform();
}

This doesn’t work:

public class NoInterfaces {
    public void perform() {
    }
    public int perform() {
      return -1;
    }
}

If you declare a class that implements both Foo and Bar, you must provide both perform() methods. C# gives you a way past the signature conflict by allowing you to specify the source interface of the duplicate method. However, you cannot make both methods public: you must choose at most one (and potentially zero) method to make public, and the others must have no access modifier at all:

public class BothInterfaces : Foo, Bar {
  public void perform() {
  }

  int Bar.perform() {
    return -1;
  }
}

Note that the Bar version of BotherInterfaces.perform() (or any other method without an access modifier) is very nearly uncallable, even from within the class itself (that is: it’s even less accessible than a private method). There is a way to call it though, which I’ll discuss below.

To further complicate things: you can in fact declare overloaded public methods with differing return types if they’re declared at different levels of inheritance:

public class ImplementsFoo : Foo {
  public void perform() {
    throw new NotImplementedException();
  }
}

public class ExtendsFooImplementsBar
  : ImplementsFoo, Bar {
  public new int perform() {
    return -1;
  }
}

ExtendsFooImplementsBar gains “public void perform()” by virtue of subclassing ImplementsFoo, and also must declare “public int perform()” when implementing Bar. The trick is that ExtendsFooImplementsBar.perform() must hide (not overload/override) ImplementsFoo.perform() (hence the “new” keyword in the ExtendsFooImplementsBar declaration). The hidden ImplementsFoo.perform() is not accessible by external callers; however it is accessible from within ExtendsFooImplementsBar by calling base.perform().

I’ve seen this crop up in two places:

  1. System.Collections.IEnumerable declares GetEnumerator(), which returns System.Collections.IEnumerator. System.Collections.Generic.IEnumerable<T> declares GetEnumerator(), which returns System.Collections.Generic.IEnumerator<T>. Since System.Collections.Generic.IEnumerable<T> also inherits from System.Collections.IEnumerable, it also must declare System.Collections.IEnumerator GetEnumerator() (without the generic type parameter).

    This isn’t much of a problem, as:

    1. You almost always want to use the generic-supporting version whenever possible.
    2. IEnumerator GetEnumerator() can (and should) almost always simply call IEnumerator<T> GetEnumerator() and return its value (as the generic-supporting IEnumerator is also a non-generic IEnumerator by virtue of the inheritance).
  2. Iesi.Collections.Generic.Set<T> (and it’s subclasses) inherits from System.Collections.Generic.ICollection<T>, and thus inherits ICollection<T>’s void Add(<T>). However, it also implements Iesi.Collections.Generic.ISet<T>, which in turn declares bool Add(<T>).

    Both methods have good reasons for declaring their respective return types. Set’s bool Add() returns true or false depending on whether the value passed in is already in the Set. (Keep in mind that Sets can only contain a particular value once. Knowing whether the Add() call actually added the value can be useful, especially in sorted implementations of Set.) On the other hand, that functionality is not useful in ICollection (as determining the results of the Add() may be meaningless or expensive for other collection implementations), so returning void from Add() is appropriate for the most part. Set definitely should be implementing ICollection for interoperability purposes.

    A problem occurs when you need to access the void version of Set.Add() instead of the bool version. This is most common when using the method with a delegate. Consider this:

    // This is a standard .NET delegate
    public delegate void Action<T>(<T> target); 
    
    public void performOnAll(Action<Foo> action) {
      // Perform some action on multiple objects in
      // some sort of data structure
      // (perhaps the results of a database query?)
    }
    
    List<Foo> aList = new List<T>();
    // This works fine
    performOnAll(aList.Add);
    
    HashedSet<T> aSet = new HashedSet<T>();
    // This doesn't work because HashedSet.Add()
    // returns bool, not void
    performOnAll(aSet.Add);
    
    // Instead, you're forced to do this:
    performOnAll(delegate(Foo foo){
      aSet.Add(foo);
    });
    // (Or use a lambda in .NET 3.5)
    

    Iesi could have avoided this problem by renaming “bool Add()” to something like “bool AddIfPossible()”… but they didn’t. Fortunately, there’s a way around this.

Casting to the Rescue

There’s a relatively simple solution to this complicated problem: cast the reference to one of its superclasses/interfaces and you can access the methods from that interface.

int result;
BothInterfaces bothInterfaces = new BothInterfaces();
// This method returns void and thus
// can't be assigned to result
((Foo)bothInterfaces).perform();
result = ((Bar)bothInterfaces).perform();

ExtendsFooImplementsBar extendsFooImplementsBar
  = new ExtendsFooImplementsBar();
// This method returns void and thus
// can't be assigned to result
((Foo)extendsFooImplementsBar).perform();
result = ((Bar)extendsFooImplementsBar).perform();

All of these calls access the method from the respective interface as expected.

This also works for my Set/delegate problem:

performOnAll( ( (ICollection<Foo>) aSet).Add);

Lastly, it also allows you to call the otherwise uncallable zero-access-modifier method when implementing multiple interfaces:

public class BothInterfaces : Foo, Bar {
  public void perform() {
  }

  int Bar.perform() {
    return -1;
  }

  public void test() {
    // Can call the void version normally
    perform();
    // Can call Bar's perform via casting
    int result = ( (Bar) this).perform();
  }
}

All in all, it’s a pretty good solution… but understanding the problem it solves is quite sticky.

One Response

  1. Ted Says:

    Clever.

Leave a Comment

Your comment

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.