10

If I have two yield return methods with the same signature, the compiler does not seem to be recognizing them to be similar.

I have two yield return methods like this:

    public static IEnumerable<int> OddNumbers(int N)
    {
        for (int i = 0; i < N; i++)
            if (i % 2 == 1) yield return i;
    }
    public static IEnumerable<int> EvenNumbers(int N)
    {
        for (int i = 0; i < N; i++)
            if (i % 2 == 0) yield return i;
    }

With this, I would expect the following statement to compile fine:

Func<int, IEnumerable<int>> generator = 1 == 0 ? EvenNumbers : OddNumbers; // Does not compile

I get the error message

Type of conditional expression cannot be determined because there is no implicit conversion between 'method group' and 'method group'

However, an explicit cast works:

Func<int, IEnumerable<int>> newGen = 1 == 0 ? (Func<int, IEnumerable<int>>)EvenNumbers : (Func<int, IEnumerable<int>>)OddNumbers; // Works fine

Am I missing anything or Is this a bug in the C# compiler (I'm using VS2010SP1)?

Note: I have read this and still believe that the first one should've compiled fine.

EDIT: Removed the usage of var in the code snippets as that wasn't what I intended to ask.

mherle
  • 504
  • 3
  • 12

8 Answers8

6

There are many possible delegate types that could match the signature of the EvenNumbers and OddNumbers methods. For example:

  • Func<int, IEnumerable<int>>
  • Func<int, IEnumerable>
  • Func<int, object>
  • any number of custom delegate types

The compiler won't try to guess which compatible delegate type you're expecting. You need to be explicit and tell it -- with a cast in your example -- exactly which delegate type you want to use.

LukeH
  • 263,068
  • 57
  • 365
  • 409
  • Why then this doesn't work: `(Func>)(1 == 0 ? OddNumbers : EvenNumbers)`? – Andrey Jul 22 '11 at 12:18
  • 4
    @Andrey, A method does not have an intrinsic type, only delegates do. Which is why the ternary operator cannot infer a type to return. – J. Steen Jul 22 '11 at 12:19
6

No. It is not a bug. It has nothing with yield. The thing is that expression type method group can be converted to delegate type only when it is assigned directly like: SomeDel d = SomeMeth.

C# 3.0 specification:

§6.6 Method group conversions

An implicit conversion (§6.1) exists from a method group (§7.1) to a compatible delegate type.

This is the only implicit conversion possible with method groups.

How ternary operator is evaluated in terms of types inferences:

A ? B : C:

Make sure that either B or C can be implicitly cast to one another's type. For example A ? 5 : 6.0 will be double because 5 can be implicitly cast to double. Type of A and B in this case is method group and there is no conversion between method group. Only to delegate and it can be enforced as you did.

Andrey
  • 59,039
  • 12
  • 119
  • 163
1

Well even

var gen = OddNumbers;

does not work. So you can't expect ternary operator to work.

I guess var can't infer a delegate type.

Kugel
  • 19,354
  • 16
  • 71
  • 103
  • `Func> generator = EvenNumbers;` does work. I cannot see why the ternary operator should not work. – leppie Jul 22 '11 at 12:07
  • See my now deleted answer of what I expected. Nothing to do with `var`, it is the ternary operator. – leppie Jul 22 '11 at 12:11
  • I agree that when not using "var" ternary operator should figure this out. But it doesn't. – Kugel Jul 22 '11 at 12:12
  • @leppie: The delegate inference is only used when you *directly* assign a method group to a delegate type. When there's no *direct* assignment then the compiler uses the standard process of trying to evaluate the rhs first *in isolation* and then determining if it's compatible with the lhs. In this case the rhs conditional operator can't determine what type to use, and fails. – LukeH Jul 22 '11 at 12:25
  • 1
    @LukeH: From a compiler implementer's point of view, I cannot see why this is difficult to do. The amount of analysis going into `?:` already makes sure both return types are compatible. – leppie Jul 22 '11 at 12:29
  • 3
    @leppie, but a method does not even have a type, so two methods can't have compatible types: they have none! – Falanwe Jul 22 '11 at 16:33
1

The yield Return has nothing to do with this.

You are not setting generator to an IEnumerable<int>, you are setting it to a MethodGroup, i.e. a function without the brackets to make the call.

The second statement casts the MethodGroups to Delegates which can be compared.

Perhaps you mean to do somthing like but,

var generator = 1 == 0 ? EvenNumbers(1) : OddNumbers(1);

I couldn't say for sure.

Jodrell
  • 34,946
  • 5
  • 87
  • 124
  • 2
    I really think he wants generator to be a delegate not an int. – Kugel Jul 22 '11 at 12:11
  • yes. I wanted one of the functions to be assigned to the `generator` variable. I'll go ahead and edit the question so that the people don't concentrate on the `var` part. – mherle Jul 22 '11 at 12:14
1

it doesn't have anything to do with iterators, the same code fails to compile if the methods are simple functions. The compiler is reluctant to automatically convert a method to a delegate object, forgetting to use the () in a method call is too common a mistake. You have to do it explicitly.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 1
    @Jodrell: There is an implicit conversion from a method group to a compatible delegate type, but in this particular example it's the conditional operator that's causing the problem. The type of the conditional expression has to be evaluated first, and that's the bit that can't be done because there's no implicit conversion between two methods groups since method groups don't have a type. – LukeH Jul 22 '11 at 13:29
  • This link provides a useful description of a "Method Group" http://stackoverflow.com/questions/886822/what-is-a-method-group-in-c/886840#886840 – Jodrell Jul 22 '11 at 14:32
1

Rollup of what works and does not:

Does not work:

var generator = 1 == 0 ? EvenNumbers : OddNumbers;
Func<int, IEnumerable<int>> generator = 1 == 0 ? EvenNumbers : OddNumbers;

Does work:

var generator = 1 == 0 ? (Func<int, IEnumerable<int>>)EvenNumbers : OddNumbers;

If it was anything to do with yield or var the latter should also fail.

My guess is a problem with the ternary operator.

leppie
  • 115,091
  • 17
  • 196
  • 297
0

The problem is that the statement

var gen = OddNumbers;

Can be interpreted both as

Func<int, IEnumerable<int>> gen = OddNumbers;

and

Expression<Func<int, IEnumerable<int>> gen = OddNumbers;

The compiler can't decide that, so you have to do this.

Henning Krause
  • 5,302
  • 3
  • 24
  • 37
  • Nope. It is the ternary operator failing. – leppie Jul 22 '11 at 12:10
  • 2
    There is, however, no real bug in the ternary operator. A method does not have an intrinsic type, only delegates do. Which is why the ternary operator cannot infer a type to return. – J. Steen Jul 22 '11 at 12:13
  • @J. Steen: replacing `var` with the intended type fails too. That is surprising. – leppie Jul 22 '11 at 12:14
  • 1
    @leppie, I'm assuming because the ternary operator can't use the left hand side to infer a type. THAT, however, could very well be some kind of logical error, or incorrect assumption since the ternary operator is just syntactic sugar. ;) – J. Steen Jul 22 '11 at 12:16
  • The compiler can't infer the type of the left hand side (IMHO due to the answer I gave above) and thus fails. – Henning Krause Jul 22 '11 at 12:20
  • @leppie, @J. Steen: Delegate inference is only used when you're directly assigning a method group to a delegate type (for example, `Func> gen = OddNumbers;`). When you're not *directly* assigning a method group then delegate inference doesn't happen, the compiler tries to evaluate the conditional operator first, and fails. – LukeH Jul 22 '11 at 12:22
  • @Henning Krause: Then `Func> gen = OddNumbers;` would fail too. Which it does not. – leppie Jul 22 '11 at 12:22
  • @LukeH, Ah! Of course! Evaluating the conditional operator first. Makes sense. – J. Steen Jul 22 '11 at 12:23
  • @LukeH: That makes sense, but do you have a reference in the spec to this? – leppie Jul 22 '11 at 12:26
  • @leppie: Just digging through the spec now trying to find it. No luck so far, but I know it's in there somewhere... – LukeH Jul 22 '11 at 12:27
  • @LukeH: Thanks :) I dont recall reading this in the C# 2.0 spec , but then again, method group descriptions are a labyrinth of their own :) – leppie Jul 22 '11 at 12:30
  • @leppie: I suspect this is all buried in the stuff relating to implicit conversions. Even though method groups (and anon functions) have no type, they are implicitly convertible to any compatible delegate type, so `Func> f = OddNumbers;` or `Func> g = x => new[] { x };` are both ok. – LukeH Jul 22 '11 at 12:59
  • But when testing if there's an implicit conversion between the conditional expression and a delegate type then you need to evalute the type of the conditional expression first, and the rules for evaluating conditional expressions will fail: there won't be *either* an implicit conversion from M1 to M2 but not from M2 to M1 *or* an implicit conversion from M2 to M1 but not from M1 to M2. – LukeH Jul 22 '11 at 13:01
0

A method (method group) does not have an intrinsic type, only delegates do. Which is why the ternary operator cannot infer a type to return, and thus is why you have to cast one or the other return value as the type you want to return.

J. Steen
  • 15,470
  • 15
  • 56
  • 63