简体   繁体   中英

Implicit Type Inference only for lambda expressions ? Why ? Confused !

I have the following sample code (used for C# 3.5 study purpose only !).

I am making a call to the Sort function that accepts an IEnumerable and a sort function. If I call it using a lambda expression (Case A) the compiler can derive the return type TResult, however when I pass the func SortInt (Case B) the compiler throws an error !

I am unable to understand why the compiler cannot derive TResult in the second case ! I seem to be passing exactly the same information. Or is that not accurate ?

Please Help !

int[] intArray = { 1, 3, 2, 5, 1 };

IEnumerable<int> intArray2 = Sort(intArray, x => SortInt(x)); // <= CASE A - OK !

IEnumerable<int> nextIntArray = Sort(intArray, SortInt); // <= CASE B - Compile Error: Cannot Infer Type !

public static IEnumerable<TResult> Sort<T, TResult>(IEnumerable<T> toBeSorted,    
                              Func<IEnumerable<T>, IEnumerable<TResult>> sortFunc)
{
    return sortFunc(toBeSorted);
}

public static IEnumerable<int> SortInt(IEnumerable<int> array)
{
    return array.OrderByDescending(x => x);
}

I agree that it's annoying that type inference doesn't work well for method group conversions. I would like the C# compiler to be smarter in the common case where there's only one applicable method in the group. In complex situations, however, you can end up with multiple applicable overloads which would lead to different types being inferred.

Options:

  • Call the method from a lambda expression, as you've shown
  • Specify the type arguments explicitly instead of relying on inference
  • Cast the method group to the specific type involved (worse than the second option)
  • Use a separate local variable and specify its type explicitly

Basically I would stick with either of the first two options, annoying as they both are. Note that there'll be a slight performance loss with the first option as it's an extra level of indirection, but that usually won't be significant.

It appears as if the inference fails on your second example because the compiler cannot perform overload resolution on SortInt.

This may be useful as a more thorough explanation:

http://blogs.msdn.com/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

IEnumerable<int> intArray = Sort(intArray, x => SortInt(x)); // <= CASE A - OK !

In this statement, the x => SortInt(x) is a delegate representation as below:

delegate(IEnumerable<int> x){
    return SortInt(x);
}

Technically you are not passing any reference to SortInt anywhere in the statement.

Func, IEnumerable> is a delegate type, it means that it needs a "pointer to function", but not the function. Where else SortInt is a method.

If you even dig deeper, you will see that x => SortInt(x) 's actual compiletime representation is as below:

private static IEnumerable<int> __XYZ(IEnumerable<int> x){
   return SortInt(x);
}

private delegate IEnumerable<int> __XYZHANDLER(IEnumerable<int> x);

and your line 1 will be

IEnumerable<int> intArray = Sort(intArray, new __XYZHANDLER(__XYZ) );

now looking at your line 2, SortInt is nothing, its a method name, it isn't any type or instance. Only thing a method parameter can have something is some instance of some type. new Delegate is an instance of method pointer, not any method.

x => SortInt(x) is a shortform of all above explained, lambda is typically a smaller form of writing anonymous delegates, compiler will not infer SortInt to x => SortInt(x) .

Your code had a few issues that was preventing it from working.

First of all, your types didn't all match up. You were passing an int[] as an IEnumerable, but you can't do that without calling .AsEnumerable().

Second, T and TResult, although being the same in usage, (int), are not the same to the compiler, but you are passing in an array of ints and expecting an IEnumerable without saying what the type of the result is. So you would have to pass in the type of TResult with your version (like Sort(intArray.AsEnumerable(), SortInt)), which works, but you don't need TResult since you are just ordered the same type, T.

So, I got rid of TResult and fixed the types:

void Main() 
{
  var intArray = new [] { 1, 3, 2, 5, 1 };
  var ints = Sort(intArray.AsEnumerable(), x => SortInt(x)); 
  var nextInts = Sort(intArray.AsEnumerable(), SortInt); 
}

public static IEnumerable<T> Sort<T>(
              IEnumerable<T> toBeSorted, 
              Func<IEnumerable<T>, IEnumerable<T>> sortFunc) 
{        
   return sortFunc(toBeSorted);
}

public static IOrderedEnumerable<T> SortInt<T>(IEnumerable<T> array) 
{    
    return array.OrderByDescending(x => x);
}

I prefer the above, however, this is the closest I can get to your sample and it works:

  void Main() {
    var intArray = new [] { 1, 3, 2, 5, 1 };
    var ints = Sort(intArray.AsEnumerable(), x => SortInt(x)); 
    var nextInts = Sort<int, int>(intArray.AsEnumerable(), SortInt); 
   }

   public static IEnumerable<TResult> Sort<T, TResult>(
                 IEnumerable<T> toBeSorted, 
                 Func<IEnumerable<T>, IEnumerable<TResult>> sortFunc)
   {    
      return sortFunc(toBeSorted);
   }
   public static IEnumerable<int> SortInt(IEnumerable<int> array){    
      return array.OrderByDescending(x => x);
   }

SortInt may also be:

public static IOrderedEnumerable<T> SortInt<T>(IEnumerable<T> array){    
   return array.OrderByDescending(x => x);
}

Akash gives the explanation for why there's a difference when you use lambda expressions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM