Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# type arguments cannot be inferred from usage in Select with multiple returns

I don't think I'm doing anything too esoteric, but I don't see any other questions about this.

The following code (I've reduced it to the essentials) generates a compiler error in C# 4. However, it should be obvious what the type argument is - there's a greatest common denominator ("class A") that is also explicitly defined in the return type of the method "Frob". Shouldn't the compiler make a list of all the return types in the lambda expression, create an ancestry tree to find their common ancestors, and then reconcile that with the expected return type of the containing method?

The type arguments for method 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Sample
{
    public abstract class A
    {
        private A(int index) { /* ... */ }

        public sealed class A1 : A
        {
            public A1(string text, int index)
                : base(index)
            { /* ... */ }
        }

        public sealed class A2 : A
        {
            public A2(int index)
                : base(index)
            { /* ... */ }
        }

        private static Regex _regex = new Regex(@"(to be)|(not to be)");
        public static IEnumerable<A> Frob(string frobbable)
        {
            return _regex.Matches(frobbable)
                .Cast<Match>()
                .Select((match, i) =>
                {
                    if (match.Groups[1].Success)
                    {
                        return new A1(match.Groups[1].Value, i);
                    }
                    else
                    {
                        return new A2(i);
                    }
                });
        }
    }
}
like image 975
Lars Kemmann Avatar asked Jul 20 '12 22:07

Lars Kemmann


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

Is C language easy?

C is a general-purpose language that most programmers learn before moving on to more complex languages. From Unix and Windows to Tic Tac Toe and Photoshop, several of the most commonly used applications today have been built on C. It is easy to learn because: A simple syntax with only 32 keywords.


2 Answers

It's section 7.5.2.12 of the C# 4 spec:

The inferred return type of an anonymous function F is used during type inference and overload resolution. The inferred return type can only be determined for an anonymous function where all parameter types are known, either because they are explicitly given, provided through an anonymous function conversion or inferred during type inference on an enclosing generic method invocation. The inferred return type is determined as follows:

  • If the body of F is an expression, then the inferred return type of F is the type of that expression.
  • If the body of F is a block and the set of expressions in the block’s return statements has a best common type T (§7.5.2.14), then the inferred return type of F is T.
  • Otherwise, a return type cannot be inferred for E.

Section 7.5.2.14 is this:

In some cases, a common type needs to be inferred for a set of expressions. In particular, the element types of implicitly typed arrays and the return types of anonymous functions with block bodies are found in this way.

Intuitively, given a set of expressions E1…Em this inference should be equivalent to calling a method

Tr M<X>(X x1 … X xm)

with the Ei as arguments.

More precisely, the inference starts out with an unfixed type variable X. Output type inferences are then made from each Ei to X. Finally, X is fixed and, if successful, the resulting type S is the resulting best common type for the expressions. If no such S exists, the expressions have no best common type.

So, suppose we have:

void M<X>(X x1, X x2) {}

A1 a1 = new A1();
A2 a2 = new A2();
M(a1, a2);

... that would fail to determine a type argument for X, so return value inference fails in the same way.

I suspect that if you cast either of your return values to A, it will work.

like image 187
Jon Skeet Avatar answered Sep 18 '22 12:09

Jon Skeet


I'm guessing there's a specific C# specification clause somewhere that dictates this. (EDIT: Jon Skeet found it and posted them in his answer)

Usually, such lambdas (or ternary operations, etc.) require to have the same exact return type at each stage to avoid ambiguity. For example, in your case, did you want to return type A or Object? Even more fun when you throw interfaces or multiple levels of inheritance into the mix.

Best bet in this case is to simply cast each of your return statements to type A or store it in a temporary variable:

if (match.Groups[1].Success)
    return (A)(new A1(match.Groups[1].Value, i));
else
    return (A)(new A2(i));

or

A returnValue;

if (match.Groups[1].Success)
    returnValue = new A1(match.Groups[1].Value, i);
else
    returnValue = new A2(i);

return returnValue;

EDIT: If you're ok without the inferred type, you can explicitly call the Select query with:

.Cast<Match>()
.Select<Match, A>((match, i) =>
{
    if (match.Groups[1].Success)
        return new A1(match.Groups[1].Value, i);
    else
        return new A2(i);
});

Then the compiler will just make sure your return types are implicitly compatible with A (which they are)

like image 20
Chris Sinclair Avatar answered Sep 20 '22 12:09

Chris Sinclair