Overload return value selection

Topics: C# Language Design
Jul 20, 2014 at 3:40 PM
I've always wondered why this wasn't implemented, and why method overload is only discernible by the number of arguments passed in (or the type information of those arguments).

My current idea is to be able to expressively select which overload you want by return type:
class Test {
    public string someMethod(int value) {
        // ...
    }
    public int someMethod(int value) {
        // ...
    }
}

var obj = new Test();
var a = obj.someMethod(0) int;
But I'm not sure how this would play out with polymorphism:
object someMethod(int value) {
    // ...
}
string someMethod(int value) {
    // ...
}

var obj = new Test();
var a = obj.someMethod(0) string; // should pick explicit string?
var b = obj.someMethod(0) object; // should pick explicit object?
I'm also unsure how this would look with long function call chains:
(obj.someOtherObject.someFunction() MyOtherType).call(); // would surrounding in parenthesis be enough?
And how about selecting the overload, but casting the result to a more derived type?
// parenthetical cast:
var t = (MyThirdType)obj.someOtherObject.someFunction() MyOtherType;

// 'as' cast:
var t = obj.someOtherObject.someFunction() MyOtherType as MyThirdType;
Perhaps a backwards arrow operator would help the readability:
var t = (MyThirdType)obj.someOtherObject.someFunction()<-MyOtherType;
Anyway, this is something I've wanted for a long time, and I think it's doable if the syntax is right. Curious to see the issues/criticism people have :)
Jul 21, 2014 at 4:15 PM
Backwards compatibility issues aside, if you have to write the type anyway... why not include it in the method name?
var a = obj.someMethodInt(0); // should pick explicit string?
var b = obj.someMethodString(0); // should pick explicit object?
You have some useful scenario?
Jul 21, 2014 at 5:22 PM
Olmo wrote:
Backwards compatibility issues aside, if you have to write the type anyway... why not include it in the method name?
Putting the return type in the method name isn't desirable to me. Seems better to have the method name describe what the method is actually doing, and the return type be selected when the function is called. Admittedly, there aren't a huge amount of cases where this sort of functionality is necessary, but I definitely prefer it in the cases where it could be:
var value = binaryReader.Read() byte;
It would also make declaring functions shorter, since currently you'd have to write the return type twice:
public byte ReadByte() { // <-- We already know it returns byte, why specify again?
    // ...
}
Jul 21, 2014 at 5:55 PM
Olmo wrote:
Backwards compatibility issues aside, if you have to write the type anyway... why not include it in the method name?
What I would like to see would be a means by which one overload for any signature could be designated as the preferred one, which would be used without complaint in all cases where no other overload is unambiguously better, and would be used by languages which don't understand return-type overloading. Consider a BigInteger type which includes overloads Int64 Mod(Int64) and Int32 Mod(Int32). Consider also that a common usage case might be:
BigInt2 = BigInt1.Mod(someInt);
and it might be common for BigInt1 to be in the range 0..someInt-1. In that usage case, having the method return an Int32 and then generating a new BigInteger which contains the same value as BigInt1, would be less efficient than having the Mod function determine whether a new BigInteger was needed and either return a new BigInteger with the proper value or else return BigInt1 unmodified.

Note that the code's net behavior would be the same whether the compiler used an overload that returned a BigInteger directly or instead used the Int32 overload followed by BigInteger op_implicit(Int32), but the former usage would save a GC allocation.
Jul 22, 2014 at 3:45 PM
Beannaich wrote:
public byte ReadByte() { // <-- We already know it returns byte, why specify again?
    // ...
}
If we have multiple "Read()" methods, then no, we don't know that it returns byte.
var value = binaryReader.ReadByte();
var value = binaryReader.Read() byte;
I don't see much difference here.
If I have to disambiguate manually at every call site, I'd rather use the name of the method than a whole new kind of language construct like your "type selector annotation"...

Wait, I think I see a point: you want to be able to do this?
T Foo<T>()
{
    return binaryReader.Read() T;
}
Jul 22, 2014 at 6:40 PM
eldritchconundrum wrote:
Wait, I think I see a point: you want to be able to do this?
T Foo<T>()
{
    return binaryReader.Read() T;
}
I don't see how that could work, since Read() supports only some Ts, but Foo() has to work for all Ts. (This would work with C++ templates, but not with C# generics.)
Developer
Jul 22, 2014 at 8:22 PM
supercat wrote:
What I would like to see would be a means by which one overload for any signature could be designated as the preferred one, which would be used without complaint in all cases where no other overload is unambiguously better, and would be used by languages which don't understand return-type overloading.
This is interesting. I'm not sure I like the solution, but I like ideas around the problem -- sometimes you're just really sure which overload you want. As far as other languages go, that doesn't make sense, though, because overload resolution is a C#/VB feature. We compile down to explicit method calls in IL (.NET requires the exact signature of the method you're calling in the 'call' instruction), so there's no cross-language overload resolution.
Jul 23, 2014 at 2:15 PM
Edited Jul 23, 2014 at 2:16 PM
eldritchconundrum wrote:
I don't see much difference here.
If I have to disambiguate manually at every call site, I'd rather use the name of the method than a whole new kind of language construct like your "type selector annotation"...
The point is, we can currently differentiate between overloads by name, parameters, and parameter type. That means the only we way we CAN'T distinguish between them is by return type. Shouldn't every aspect of the method signature be significant in determining it's uniqueness? The only thing I could see this breaking is code that relies on reflection (possibly). Since the API would have to be extended to support this.
type.GetMethod("Read", BindingFlags.Public | BindingFlags.Instance); // <-- which overload do you mean?
type.GetMethod("Read", BindingFlags.Public | BindingFlags.Instance, returnType: typeof(byte)); // <-- now reflection works again :-D
The reason this has nagged me actually began with BinaryReader/Writer
Writer.Write((byte)0); // <-- no need for 'WriteByte' in the method signature, since this is supported
Writer.Write((short)0);

Reader.ReadByte(); // <-- but here we have to explicitly say it in the name
When both could be more normalized:
Writer.Write((byte)0);
Reader.Read() byte;
Jul 23, 2014 at 3:13 PM
Beannaich wrote:
Reader.ReadByte(); // <-- but here we have to explicitly say it in the name
Methods should not modify the states of other objects in ways that depend upon the types of values passed to them. I would suggest that if Foo is a well-designed method, then Foo((long)someInt) should perform essentially the same action as Foo(someInt) in cases where both are legal. There's nothing wrong with having the latter method be faster, or having it be legal in some contexts where the former would not be (indeed, I would consider those to be excellent reasons for having such overloads), but having a method write a different number of bytes when called with a long versus an int would be a major code smell.

BTW, while it's too late to change the language in such fashion, I would have liked to have seen things like the * operator include both int*int->int and int*int->long overloads, using the latter except when the result was going to be stored or coerced to an int or other smaller type. Further, since some platforms perform transcendental operations more quickly on float than double values, it would have been helpful to have float Math.sqrt(float) and double Math.sqrt(double), but favor latter in all cases where the result wouldn't be coerced to a float [so that e.g. float fsqrt10 = Math.sqrt(10); would do a fast single-precision square-root calculation while double dsqrt10 = Math.sqrt(10); would yield a more precise double-precision calculation.]
Jul 23, 2014 at 8:36 PM
Here's a possibility. It's sort of a generalization of the following:
var action = () => {};
does not compile, while
Action action = () => {};
does. Essentially, the lambda automatically figures out its type based on what's expected from it.

I would propose, then that given two Foo methods that return, say, int and double, that this would choose the int one:
int x = Foo();
var y = (int)Foo();
or, given a single Bar(int), that this would also compile
Bar(Foo())
much like today's resolution of lambda literals.

Of course, we're still left with the problem of another .net language trying to figure out what the heck to do with two methods with the same signature except for return type - it seems like the other language would have to be modified to accept such a thing without blowing up.