Saturday, January 30, 2010

Type Inference

When a generic method is called without specifying type arguments, a type inference process attempts to infer type arguments for the call. Lambda expressions passed as arguments to the generic method participate in this type inference process.
As described in §20.6.4, type inference first occurs independently for each argument. In this initial phase, nothing is inferred from arguments that are lambda expressions. However, following the initial phase, additional inferences are made from lambda expressions using an iterative process. Specifically, inferences are made as long as one or more arguments exist for which all of the following are true:
  • The argument is a lambda expression, in the following called L, from which no inferences have yet been made.
  • The corresponding parameter's type, in the following called P, is a delegate type with a return type that involves one or more method type parameters.
  • P and L have the same number of parameters, and each parameter in P has the same modifiers as the corresponding parameter in L, or no modifiers if L has an implicitly typed parameter list.
  • P's parameter types involve no method type parameters or involve only method type parameters for which a consistent set of inferences have already been made.
  • If L has an explicitly typed parameter list, when inferred types are substituted for method type parameters in P, each parameter in P has the same type as the corresponding parameter in L.
  • If L has an implicitly typed parameter list, when inferred types are substituted for method type parameters in P and the resulting parameter types are given to the parameters of L, the body of L is a valid expression or statement block.
  • A return type can be inferred for L, as described below.
For each such argument, inferences are made from that argument by relating the return type of P with the inferred return type of L and the new inferences are added to the accumulated set of inferences. This process is repeated until no further inferences can be made.
For purposes of type inference and overload resolution, the inferred return type of a lambda expression L is determined as follows:
  • If the body of L is an expression, the type of that expression is the inferred return type of L.
  • If the body of L is a statement block, if the set formed by the types of the expressions in the block's return statements contains exactly one type to which each type in the set is implicitly convertible, and if that type is not the null type, then that type is the inferred return type of L.
  • Otherwise, a return type cannot be inferred for L.
As an example of type inference involving lambda expressions, consider the Select extension method declared in the System.Query.Sequence class:
namespace System.Query
{
   public static class Sequence
   {
      public static IEnumerable Select(
         this IEnumerable source,
         Func selector)
      {
         foreach (T element in source) yield return selector(element);
      }
   }
}
Assuming the System.Query namespace was imported with a using clause, and given a class Customer with a Name property of type string, the Select method can be used to select the names of a list of customers:
List customers = GetCustomerList();
IEnumerable names = customers.Select(c => c.Name);
The extension method invocation (§26.2.3) of Select is processed by rewriting the invocation to a static method invocation:
IEnumerable names = Sequence.Select(customers, c => c.Name);
Since type arguments were not explicitly specified, type inference is used to infer the type arguments. First, the customers argument is related to the source parameter, inferring T to be Customer. Then, using the lambda expression type inference process described above, c is given type Customer, and the expression c.Name is related to the return type of the selector parameter, inferring S to be string. Thus, the invocation is equivalent to
Sequence.Select(customers, (Customer c) => c.Name)
and the result is of type IEnumerable.
The following example demonstrates how lambda expression type inference allows type information to "flow" between arguments in a generic method invocation. Given the method
static Z F(X value, Func f1, Func f2) {
   return f2(f1(value));
}
type inference for the invocation
double seconds = F("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);
proceeds as follows: First, the argument "1:15:30" is related to the value parameter, inferring X to be string. Then, the parameter of the first lambda expression, s, is given the inferred type string, and the expression TimeSpan.Parse(s) is related to the return type of f1, inferring Y to be System.TimeSpan. Finally, the parameter of the second lambda expression, t, is given the inferred type System.TimeSpan, and the expression t.TotalSeconds is related to the return type of f2, inferring Z to be double. Thus, the result of the invocation is of type double.

No comments:

Post a Comment