3

So, I'm having an issue with similar code to below:

public static String MyFunc<T>(this IEnumerable<T> list) where T : struct
{
    ... some code ...
    return myString;
}

public static String MyFunc<T>(this T o) where T : struct
{
    ... some code ...
    return myString;
}

The problem is that when trying to do call MyFunc on a List it uses the second function instead of the one that accepts an IEnumerable. I know this has to do with variance, but I'm uncertain as to how to force it to use the first function rather than the second. The code I would use to call the first one would be:

List<int> x = new List<int>();
String s = x.MyFunc();

The above code immediately goes to the second function and I need it to use the first. How can I force the desired behavior? Incidentally, I'm using .NET 4.0

Thomas
  • 3,664
  • 3
  • 15
  • 12
  • 1
    And in this case it wouldn't actually compile, due to the `where T : struct` in the second case; that overload would be chosen, but then be found to be invalid. – Jon Skeet Jun 05 '14 at 18:05

1 Answers1

7

The reason that it's currently picking the second method is that a conversion from a type to itself (second method, T=List<int>, conversion from List<int> to List<int>) will always be "better" than a conversion to the type it implements (first method, T=int, conversion from List<int> to IEnumerable<int>). This has nothing to do with variance, by the way - it's just the method overloading algorithm and type inference.

Note that with your current code, although the second overload is picked, it will then be found to be invalid because T violates the T : struct constraint. The constraint is only checked after the overload is chosen. See Eric Lippert's blog post on this for more details.

I suggest you just give the two methods different names.

EDIT: As noted by Anthony in comments, this can work if you call it as:

x.AsEnumerable().MyFunc();

Or just change the declaration to:

IEnumerable<int> x = new List<int>();
x.MyFunc();

It's not entirely clear to me exactly why it's better here - in this case after type argument substitution, you've basically got IEnumerable<T> as the parameter type in both cases. However, I would still strongly recommend using different names here. The fact that it's got me puzzling over the spec to work out which overload is being called should be enough indication that the behaviour won't be immediately clear to everyone reading the code.

EDIT: I think the reason is here (from the C# 5 spec, section 7.5.3.2):

  • A type parameter is less specific than a non-type parameter

So just T is less specific than IEnumerable<T>, even though the latter involves a type parameter. It's still not clear to me whether this is the language designers' intended behaviour... I can see why a type which involves type parameters should be seen as less specific than a type which doesn't involve type parameters, but not quite this wording...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I was just hoping there was a way to force it to prioritize one over the other. – Thomas Jun 05 '14 at 18:00
  • 1
    @user976921: Why *not* just give the two methods different names? That way it will be absolutely obvious to anyone reading the code which method is involved too. – Jon Skeet Jun 05 '14 at 18:06
  • @user976921, type the variable as `IEnumerable`, cast it, invoke `AsEnumerable()`, etc., but at that point, that's more (essentially undocumented) boilerplate for you and it would be simpler to just follow Jon's advice of having a differently named method. – Anthony Pegram Jun 05 '14 at 18:08
  • @AnthonyPegram: Using `AsEnumerable` won't help - it will still call the second overload, just with `T=IEnumerable`. – Jon Skeet Jun 05 '14 at 18:09
  • The reason I wanted to keep the name the same was to keep uniformity with it as both functions will essentially do the same thing, the only difference being one handles multiples(IEnumerable), where as the other handles only one(). Also, .AsEnumerable does choose the correct Method, but as you say, creates it's own issues. – Thomas Jun 05 '14 at 18:11
  • Jon, I'm going to have to disagree on that one. I was admittedly unsure, so I tested my assumption in LinqPad (not the same code as in the OP, but similar overloads). But with the return type of AsEnumerable being `IEnumerable`, I don't think I had much reason to question myself, would you agree? – Anthony Pegram Jun 05 '14 at 18:12
  • @AnthonyPegram: I do apologize, that *does* indeed work. Will try to work out why, and edit it into my answer. – Jon Skeet Jun 05 '14 at 18:18
  • Yes, essentially, my version omitted the constraints and the return type on the extension methods, but the inputs were otherwise the same. With the return type of AsEnumerable being plain-jane `IEnumerable`, there's no reason it wouldn't select that overload on the extension method, although it could, of course, then fail a constraint check (if applicable). – Anthony Pegram Jun 05 '14 at 18:24
  • @AnthonyPegram: At that point I can't find anything in the spec to determine which method is "better" from an overload resolution perspective. I'm scouring the spec to work it out, but it's definitely non-obvious... – Jon Skeet Jun 05 '14 at 18:25
  • Yes, I hadn't scrubbed my assumption against the spec and my knowledge of it is a bit rusty at the moment, I'll only say the thought just seemed intuitive that `IEnumerable` would be the best match, given that it *was* exact, and with the least number of steps to be exact, just as given a choice between `int` and `T`, the compiler chooses `int` for an integer argument. Perhaps not the same specification-described behavior, but on an intuitive level, it follows (for me). – Anthony Pegram Jun 05 '14 at 18:47