Go Inside C# 3.0’s Type Inference Process

Lambda expressions in C# 3.0, which are a simpler syntax for representing anonymous methods, can have either explicitly typed parameters (where the type of each parameter is explicitly stated) or implicitly typed parameters (where the type of each parameter is inferred from the context in which the lambda expression occurs). In the case of implicitly typed parameters, when the lambda expression is converted to a compatible delegate type, it is this delegate type that provides the parameter types.

In a scenario when a generic method is called without specifying type arguments, a type inference process attempts to infer the arguments. The lambda expressions passed as arguments to the generic method participate in this inference task. Initially, the type inference occurs independently for each argument. It takes place in two stages:

  1. It infers nothing from arguments that are lambda expressions.
  2. It uses an iterative process to makes inferences from lambda expressions.

The iterative process continues until all the following conditions are valid for any given argument:

  • The argument is a lambda expression (L), from which no inferences have been made.
  • The corresponding parameter’s type (P) is a delegate type, which has a return type that involves one or more method type parameters.
  • Both L and P have the same number of parameters, and the same modifiers if they’re explicit 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 has 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 then can be inferred for L, as described.

For each such argument, the return type of P is related to the inferred return type of L, and the inferred type derived is added to the list of inferences. This process repeats until no further inferences can be made.

This article demonstrates how type inference works in C# 3.0.

Type Inference Example

To understand how the type inference process works, consider the Select extension method declared in the following System.Query.Sequence class:

namespace System.Query
{
   public static class Sequence
   {
      public static IEnumerable<S> Select<T,S>(
         this IEnumerable<T> source,
         Func<T,S> selector)
      {
         foreach (T element in source) yield return selector(element);
      }
   }
}

Now, create a new Visual C# LINQ Console project and type in the following code:

//Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;

namespace TypeInference
{
   class Program
   {
      static void Main(string[] args)
      {
         List<int> intList = new List<int>();
         intList.Add(1);
         intList.Add(2);
         intList.Add(3);
         intList.Add(4);
         intList.Add(5);
         intList.Add(6);
         intList.Add(7);
         intList.Add(8);
         intList.Add(9);
         intList.Add(10);
         IEnumerable<int> remainderList = intList.Select(c => c%2 );
         foreach(int i in remainderList)
         {
            Console.WriteLine(i);
         }
         Console.ReadLine();
      }
   }
}

To compile the code, you need the LINQ Preview released at PDC 2005. If you do not have Visual Studio 2005, you can compile it with the console by using the following command:

C:Program FilesLINQ PreviewBinCsc.exe
   /reference:"C:Program FilesLINQ PreviewBin
                  System.Data.DLinq.dll"
   /reference:C:WINDOWSMicrosoft.NETFrameworkv2.0.50727
                 System.Data.dll
   /reference:C:WINDOWSMicrosoft.NETFrameworkv2.0.50727
                 System.dll
   /reference:"C:Program FilesLINQ PreviewBinSystem.Query.dll"
   /reference:C:WINDOWSMicrosoft.NETFrameworkv2.0.50727
                 System.Xml.dll
   /reference:"C:Program FilesLINQ PreviewBinSystem.Xml.XLinq.dll"
   /out:objDebugTypeInference.exe /target:exe Program.cs

The above code prints the remainder of the integer list when each integer in the list is divided by two.

When the C# compiler encounters the following snippet:

IEnumerable<int> remainderList = intList.Select(c => c%2 );

It converts it to the following:

IEnumerable<int> remainderList = Sequence.Select(intList, c => c%2);

Because type arguments were not explicitly specified, type inference came into play. First, the intList argument is related to the source parameter, inferring T to be int. Then, by using the lambda expression type inference process, the code gave c the type int. Thus, the invocation was equivalent to the following:

Sequence.Select<int, int>(intList, (int c) => c%2

And the result is of type IEnumerable<int>.

About the Author

Vipul Patel is a Microsoft MVP (two years in a row) in Visual C# and currently works at Microsoft through Volt Information Sciences. He specializes in C# and deployment issues. You can reach him at Vipul_d_patel@hotmail.com.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read