2007/11/09

WTF: Overloading and Generics

Recently I discovered some behaviour of .NET that I couldn't explain. I still wonder whether this is an error or "ByDesign". I found it when I tried to call ActiveRecordMediator's Exists method, which threw out an unexpected exception. Minutes later, I stared unbelievingly to the screen: A complete different method overload was called. I sat down and created a short example that doesn't use ActiveRecord, so it can be understood from any .NET-developer:
public interface IString
{
  string Content{ get;}
}

public class DString:IString
{
  private readonly string content;

  public string Content
  {
    get { return content; }
  }

  public DString(string content)
  {
    this.content = content;
  }
}

public static class Class1
{
  public static void Foo(params IString[] bars)
  {
    foreach (IString s in bars)
    {
      Console.WriteLine(s.Content);
    }
  }

  public static void Foo<T>(T bar)
  {
    throw new NotImplementedException();
  }

}

[TestFixture]
public class Tester
{
  [Test]
  public void CallBar()
  {
    Class1.Foo(new DString("baz"));
  }
}
So what would you expect when running the unit test? That's what I got: TestCase 'ClassLibrary2.Tester.CallBar' failed: System.NotImplementedException : The method or operation is not implemented. What happens here? If you add exactly one optional parameter, the compiler somehow thinks, you meant Foo<istring>() instead of Foo(). This happens only if the type parameter is an interface. I tried with string and that worked like expected, calling Foo(). So, is this a bug or a documented behaviour. Do you know ressources that document it? I didn't find any...

1 comment:

Jon Skeet said...

It's working as expected, but your explanation isn't what the compiler is doing.

Your call is Foo(x) where x is of compile-time type DString.

The choices are:

void Foo(params IString[] bars)
void Foo<T>(T bar)

Type inference determines that the second call is applicable, with T=DString. So in other words, you then have a situation similar to:

void Foo (params IString[] bars)
void Foo (DString bar)

and you're calling it with a single DString parameter.

Now which overload do you expect it to use? :)

Hope that helps,
Jon