2

Ok, back to basics. I am wondering how to correctly overload a method with a params argument.

Here's my scenario. I start with my regular method:

public void MyMethod(MyObject mo)
{
    // method body
}

And I created an overload for it that looks like this:

public void MyMethod(MyObject mo, params string[] fields)
{
    // new method body

    MyMethod(mo);
}

The obvious intention is for MyMethod(new MyObject()); to execute the original method and MyMethod(new MyObject(), "field0"/*, etc...*/); to execute the overloaded method. But that isn't what I'm finding to be the case.

What actually happens is that MyMethod(new MyObject()); executes the overloaded method!

I don't understand that. In this type of scenario, how would I execute the original method?

UPDATE with actual code

Okay, so here is the actual code that produces the described behavior.

Class1Base.cs:

public class Class1Base
{
    public virtual void MyMethod(MyObject ob)
    {
        Console.WriteLine("Called Class1Base");
    }
}

Class1.cs:

public class Class1 : Class1Base
{
    public override void MyMethod(MyObject ob)
    {
        Console.WriteLine("called overridden method");
    }

    public void MyMethod(MyObject ob, params string[] fields)
    {
        Console.WriteLine("called OVERLOADED method");
    }
}

MyObject.cs:

public class MyObject
{
    public int Id { get; set; }
    public string Description { get; set; }
}

Then, when I execute this code in this fashion:

var myClass = new Class1();
var myObject = new MyObject();
myClass.MyMethod(myObject);
myClass.MyMethod(null);
myClass.MyMethod(null, "string");

The console shows:

called OVERLOADED method
called OVERLOADED method
called OVERLOADED method

I would have expected it to show:

called overridden method
called overridden method
called OVERLOADED method

Why doesn't it?

quakkels
  • 11,676
  • 24
  • 92
  • 149
  • 1
    Is it an option to just remove the old overload entirely and move it's functionality to the new overload? As you've seen, you can supply 0 values to a `params` method and it will just create an array of size zero for the argument. – Servy Jun 05 '13 at 16:50
  • I suppose it could be an option, but for cleaner, more testable code I would prefer to use an overload. It sounds like there isn't a way to accomplish this type of overloading with the `params` keyword. – quakkels Jun 05 '13 at 16:54
  • Curious. It actually makes sense that it would be using the "overload", which in fact is acting more like a method that is "hiding" the original. I wonder how you'd ever call the original method. Does the compiler warn of this? – DonBoitnott Jun 05 '13 at 16:54
  • @DonBoitnott - No compiler warning. – quakkels Jun 05 '13 at 16:55
  • 1
    I'm pretty sure the spec says that the `params` method should *lose* overload resolution if it is only applicable in its expanded form. Are you sure this it the behavior you're seeing? Can you show us the call sites? – dlev Jun 05 '13 at 17:00
  • What do you mean by call sites? – quakkels Jun 05 '13 at 17:01
  • This might help: http://stackoverflow.com/questions/5173339/how-does-the-method-overload-resolution-system-decide-which-method-to-call-when – DonBoitnott Jun 05 '13 at 17:02
  • @quakkels I mean can you show us the code that is actually calling these methods. I suspect there is more going on than you've shown us. – dlev Jun 05 '13 at 17:02
  • @dlev - Please see comment on you answer. – quakkels Jun 05 '13 at 17:11
  • The question has been updated. – quakkels Jun 05 '13 at 17:34

1 Answers1

5

I don't think you're telling us the whole story. Section 7.3.5.2 of the C# 5 spec (titled "Better function member") says in part:

• Otherwise, if MP is applicable in its normal form and MQ has a params array and is applicable only in its expanded form, then MP is better than MQ.

That seems to be the case here, since the params version needs to be "expanded" to a zero-length array. And in fact, trying your code locally produces the expected result of calling the non-params version.

Update: In response to your edit, the answer is now clear: You are calling the methods from Class1, which means that when performing overload resolution, methods marked as override are not considered initially. And since a non-overridden method is applicable (albeit in its expanded form,) that is the method chosen.

Specifically, section 7.6.5.1 read in part:

• The set of candidate methods is reduced to contain only methods from the most derived types: For each method C.F in the set, where C is the type in which the method F is declared, all methods declared in a base type of C are removed from the set.

The base class MyMethod() is excluded from the candidate set, and so it won't be selected by the algorithm.


The precise reasoning behind this behavior is to avoid a manifestation of the "brittle base-class" problem. Suppose we had the following class hierarchy:

class A
{
}

class B : A
{
    public void MyMethod(object o) { }
}

And the following call-site:

new B().MyMethod("a string");

That will obviously resolve to the MyMethod() that takes an object. But now suppose the creator of A (perhaps who works on another team) decides A should have a MyMethod(), too. So they change their class:

class A
{
    public void MyMethod(string s);
}

And now imagine what would happen if we didn't exclude methods from base types. Your call that was originally resolving to B.MyMethod() all of a sudden would resolve to A.MyMethod() (since the string is a better match.) The designers of C# didn't want to allow another person on a totally different team to silently change the meaning of your code.

For more information on brittle base-class issues, Eric Lippert's (old) blog has a number of posts on the topic. Just search around a bit.

dlev
  • 48,024
  • 5
  • 125
  • 132
  • I've got the same result here. Everything working as expected. – Thomas C. G. de Vilhena Jun 05 '13 at 17:10
  • Hmmm. I created a new sample project to recreate the error and I'm unable to duplicate. I can't post the code that is behaving this way. I'll work to recreate the behavior and post that. – quakkels Jun 05 '13 at 17:11
  • Please see my question with updates which duplicate the described behavior. – quakkels Jun 05 '13 at 17:34
  • Thank you for such a detailed explanation. But, I don't see how that should be in play here. For example, if I change `public void MyMethod(MyObject ob, params string[] fields)` to `public void MyMethod(MyObject ob, string field)` then it works as expected. Could it be that something specific to `params` is causing this? – quakkels Jun 05 '13 at 18:27
  • 1
    @quakkels You have two methods, both of which would theoretically be applicable. However, the method that is originally declared in the base class is ignored initially, so that leaves a single applicable method (the one with `params` in the derived class.) If you change the derived class version to `MyMethod(MyObject ob, string field)`, then *it is no longer applicable*, and so the base class version is once again considered. – dlev Jun 05 '13 at 18:41
  • I see. That's an interesting wrinkle. – quakkels Jun 05 '13 at 18:45