19

In C# I need to be able to define a method but have it return one or two return types. The compiler gives me an error when I try to do it, but why isn't it smart enough to know which method I need to call?

int x = FunctionReturnsIntOrString();

Why would the compiler prevent me from having two functions with different return types?

charlieday
  • 193
  • 1
  • 1
  • 5
  • 1
    charlieday, could you provide a few more details on why you want a method that can return multiple return types? I'm wondering if the Convert class in the System namespace might be a good model to follow. It has multiple conversion methods of different return types but it encodes the return type into the name of the method. E.g. ToBoolean, ToByte, ToChar, etc. – Tim Stewart Jun 02 '09 at 05:22
  • 4
    The reason is because in your scenario, type information would be flowing "both ways". A basic principle of language design is that you should be able to analyze the type of each part of an expression independently. We want to be able to determine the type of the call expression independently of what it is being assigned to. The exception to this rule is lambda expressions; making type information "flow both ways" in lambda expressions was very difficult. See "anonymous methods vs lambda expressions" in my blog for details. – Eric Lippert Jun 02 '09 at 06:08
  • 1
    you can look my answer here http://stackoverflow.com/questions/1366136/overloading-on-basis-of-return-type-only/14337184#14337184 – Ashish Gupta Jan 15 '13 at 12:48

10 Answers10

26

From the last paragraph of section 1.6.6 of the C# 3.0 language specification:

The signature of a method must be unique in the class in which the method is declared. The signature of a method consists of the name of the method, the number of type parameters and the number, modifiers, and types of its parameters. The signature of a method does not include the return type.

In IL two methods can differ by return type alone, but outside of reflection there is no means to call a method that differs only be return type.

Timothy Carter
  • 15,459
  • 7
  • 44
  • 62
22

While it may be obvious in this particular scenario, there are many scenarios where this is in fact not obvious. Lets take the following API for an example

class Bar { 
  public static int Foo();
  public static string Foo();
}

It's simply not possible for the compiler to know which Foo to call in the following scenarios

void Ambiguous() {
  Bar.Foo();
  object o = Bar.Foo();
  Dog d = (Dog)(Bar.Foo());
  var x = Bar.Foo();
  Console.WriteLine(Bar.Foo());
}

These are just a few quick samples. More contrived and evil problems surely exist.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    You can have this same problem with overloaded parameters and the C# compiler gives you a warning. What's the problem with a warning about return types? – charlieday Jun 02 '09 at 04:55
  • 1
    Please describe such an example in C# with overloaded parameters. In principle the C# compiler will allows generate an error message in the face of ambiguity. It won't just pick one and give you an error. – Dustin Campbell Jun 02 '09 at 05:02
  • 1
    @charlieday, I don't believe the ability of the C# compiler to give the warning is the issue. My *guess* is the issue is more along the lines of cost. This is a feature that has limited value and a non-trivial number of scenarios where it will outright fail (see my samples). Such features are often cut due to low benefit vs. cost. I don't work on the C# team though and cannot speak to whether or not this ever came up in a design meeting. Truthfully, I'm not even sure if it's legal at an IL level. – JaredPar Jun 02 '09 at 05:06
  • 2
    It is legal at the IL level, but there is no such thing as "overload resolution" at the IL level, so that makes it much easier. At the IL level, you always explicitly state exactly which method you are calling. – Eric Lippert Jun 02 '09 at 06:03
  • 1
    @dustin: It primarily has to do with nulls. Say you have two methods: void Foo(string x); void Foo(Bar x); And you choose to call your method like so: Foo( null ); The C# compiler will give you a warning because it can't figure out which method you mean to call, so you have to change your code to accommodate: Foo( (string) null ); It's an edge case, but my point is "if C# is already doing it for parameters, why not for return types". (Which has pretty much been answered already) – charlieday Jun 02 '09 at 12:53
  • @charlieday -- that particular example results in an error: "The call is ambiguous between the following methods or propertyes: 'Program.Foo(string)' and 'Program.Foo(Program.Bar)'" – Dustin Campbell Jun 02 '09 at 15:13
  • The compiler almost never gives you a _warning_ if overload resolution gives an ambiguity; that is almost always an error. The situations where we give a warning in the face of ambiguity are bizarre in the extreme; here's an example: http://blogs.gotdotnet.com/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx – Eric Lippert Jun 02 '09 at 17:04
  • 1
    Yeah, I meant error. And my point still stands: if the compiler is already having to put up those issues, why not on return types? – charlieday Jun 02 '09 at 20:18
  • 1
    A simple remedy for ambiguity with regard to overloading based on function returns (which would be useful in a number of overload scenarios, actually) would be to specify that there must be one overload which is specified as the 'default' one to use except in cases where the return value will be immediately and unconditionally cast to some other type. That wouldn't handle all the scenarios where return-type overloading might be helpful, but it would still allow it to be advantageous on occasion. – supercat Jan 21 '13 at 05:56
10

Have you though about using generics to return the correct type.

public T SomeFunction<T>()
{
    return T
}

int x = SomeFunction<int>();
string y = SomeFunction<string>();

Note: This code has not been tested

Kane
  • 16,471
  • 11
  • 61
  • 86
  • This can get get a bit tricky sometimes but if done properly is definitely the way to go. I usually go a step further and declare the function boolean, and the return object is sent by reference. This way you know when an error has occurred while avoid any casting issues. It gives you the power to do: if (!SomeFunction(ref returnString)) DoErrorHandling(); – David Božjak Jun 02 '09 at 04:58
  • 6
    this code doesn't work. – Luke Schafer Jun 02 '09 at 05:03
9

Function differing only with return values do not qualify for the overloading.

int x = FunctionReturnsIntOrString();
double y = FunctionReturnsIntOrString();

In the above case compiler can identify the correct functions but consider the cases in which return values are not specified, it is ambiguous.

FunctionReturnsIntOrString();   //int version
FunctionReturnsIntOrString();   //double version

Compiler cannot resolve the overloaded methods here.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
aJ.
  • 34,624
  • 22
  • 86
  • 128
4

it is possible to get the syntax you showed out in the question, but you have to engage in quite a good amount of cleverness in order to get there.

Let's set up the problem a little more concretely. The key point here, I think, is the syntax. We want this:

int intValue = GetData();
string stringValue = GetData();
DateTime[] dateTimeArrayValue = GetData();

However, we do not want this to work:

double doubleValue = GetData();
DateTime? nullableDateTimeValue = GetData();

In order to accomplish this, we have to use an intermediary object in the return value from GetData(), whose definition looks like this:

public class Data
{
    public int IntData { get; set; }
    public string StringData { get; set; }
    public DateTime[] DataTimeArrayData { get; set; }

    public MultiReturnValueHelper<int, string, DateTime[]> GetData()
    {
        return new MultiReturnValueHelper<int, string, DateTime[]>(
            this.IntData, 
            this.StringData, 
            this.DataTimeArrayData);
    }
}

Your implementation, of course, would be quite different, but this will do. Now let's define MultiReturnValueHelper.

public class MultiReturnValueHelper<T1, T2, T3> : Tuple<T1, T2, T3>
{
    internal MultiReturnValueHelper(T1 item1, T2 item2, T3 item3)
        : base(item1, item2, item3)
    {
    }

    public static implicit operator T1(MultiReturnValueHelper<T1, T2, T3> source)
    {
        return source.Item1;
    }

    public static implicit operator T2(MultiReturnValueHelper<T1, T2, T3> source)
    {
        return source.Item2;
    }

    public static implicit operator T3(MultiReturnValueHelper<T1, T2, T3> source)
    {
        return source.Item3;
    }
}

Repeat this definition for T1, T2, T3, etc for the generic case.

You can also bind the return value helper very closely to the class or method that returns it, enabling you to create this same sort of effect for indexers, where you can get and assign a discrete set of types. That's where I've found it the most useful.

Another application is to enable syntax like the following:

data["Hello"] = "World";
data["infamy"] = new DateTime(1941, 12, 7);
data[42] = "Life, the universe, and everything";

The precise mechanism to accomplish this syntax is left as an exercise to the reader.

For a more general-purpose solution to this problem (which is, I think, the same as a discriminated union problem), please see my answer to that question.

Community
  • 1
  • 1
Philip Taron
  • 143
  • 8
  • Having dealt with code like this before: `data["Hello"] = "World"; data["infamy"] = new DateTime(1941, 12, 7); data[42] = "Life, the universe, and everything";` I want nothing to do with it ever again. If you ever need syntax like that, just assume that you are doing it wrong and should consider using a typed object instead. – aaronburro Aug 20 '15 at 20:57
4

I think you want to seriously reconsider what you're doing and how, but you CAN do this:

int i = FunctionReturnsIntOrString<int>();
string j = FunctionReturnsIntOrString<string>();

by implementing it like this:

private T FunctionReturnsIntOrString<T>()
{
    int val = 1;
    if (typeof(T) == typeof(string) || typeof(T) == typeof(int))
    {
        return (T)(object)val;
    }
    else
    {
        throw new ArgumentException("or some other exception type");
    }
}

but there are sooo many reasons not to.

Luke Schafer
  • 9,209
  • 2
  • 28
  • 29
  • I suppose the primary reason not to do that is because it will throw an InvalidCastException at runtime if T == typeof(string). – Dustin Campbell Jun 02 '09 at 05:05
  • +1 for giving a solution, but also explaining that this is probably not a good programming practice. – ebrown Jun 02 '09 at 05:06
  • 3
    While this does smell bad, I'm wondering if you could enumerate the reasons why one ought not write code like this? – Justin R. Jan 26 '12 at 19:13
2

The return type is not part of the method's signature, only the name and the parameter types. Because of this, you can't have two methods that only differ by the return type. One way to get around this would be for your method to return an object, then the calling code must cast it to either an int or string.

However, it might be better to create two different methods, or create a class to return from the method that can either contain an int or a string.

Andy White
  • 86,444
  • 48
  • 176
  • 211
  • But in IL, the return type is used to identify the method... using ildasm you can see the retrn type specified when a callvirt instruction is specified. – charlieday Jun 02 '09 at 04:53
  • It doesn't matter what's in IL - what matters is the C# specification, specifically section 3.6, where it says "The signature of a method specifically does not include the return type" . See http://msdn.microsoft.com/en-us/library/aa691131(loband).aspx – Bevan Jun 02 '09 at 05:02
  • Right -- remember, there is no such thing as "overload resolution" in MSIL. In IL, you have to always explicitly state exactly which method you are calling. That's not the case in C#. – Eric Lippert Jun 02 '09 at 06:02
2

Because sometimes it really cannot tell which one it should then use (your example it could, but not all cases are that clear-cut):

void SomeMethod(int x) { ... }
void SomeMethod(string x) { ... }

In this context, if you call SomeMethod(FunctionReturnsIntOrString()) what should the compiler do?

jerryjvl
  • 19,723
  • 7
  • 40
  • 55
1

In C# there is no overridin return types, IL supports that kind of overriding but C# not...yet.

Arsen Mkrtchyan
  • 49,896
  • 32
  • 148
  • 184
1

You are also not required to assign a value to a called method. For example:

int FunctionReturnsIntOrString() {return 0;}
string FunctionReturnsIntOrString() {return "some string";}

//some code
//...

int integer = FunctionReturnsIntOrString(); //It probably could have figured this one out

FunctionReturnsIntOrString(); //this is valid, which method would it call?
ebrown
  • 811
  • 1
  • 8
  • 13