Extension method Binding and Error reporting

Overview:

Extension methods are static methods that are bound with instance semantics. In this article i will give a brief overview of the various steps involved in binding a extension methods. Finally this will prepare the way to discuss the error reporting for extension methods and how these error messages can be used to diagnose the problem at hand.

All methods calls bound in the compiler go though a 3 phase,

  1. The methods to be bound are defined in source or imported,
  2. A lookup for their name finds them and
  3. The method passes applicability for the given arguments.
  • Lookup: This is the phase where the method name is searched on the type and its base classes. In case of an instance method the object on the lhs provides the type,

eg object.MethodName( arg1, arg2 ) becomes

      lhs = object ,

      rhs = MethodName

whereas for static method the type is explicitly specified. In either case the compiler looks for a method of the given name(identifier), on finding one it checks for access from the location of invocation, arity count (number of type parameters) etc. On finding a method that match, it is added to a method group. This method group (roughly collection of methods) is the result of a lookup. Therefore lookup error in C# read roughly like "Type blaa does not contain method blaa".

  • Applicability: This is the phase where the given arguments are matched with the parameters of the method. This can result in 0 , 1 or more methods being found applicable. 0 is a binding failure and the closest match is provided for the error message. If there is exactly 1 then we can bind the call and if more than 1 then we try a best-ness algorithm. If none of the methods are better than other we have an ambiguous call else we have a successful binding. Therefore applicability errors in C# read like "best overload for method blaa has some invalid arguments".

Enter Extension methods

Definition

Extension methods can be defined in source or imported from external assemblies. If imported from external assemblies they are recognized as extension methods based on the extension attribute that decorates them.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Assembly)]

public sealed class ExtensionAttribute : Attribute { }

This attribute is placed on the Extension Method, type and assembly by C# and VB compiler. This allows extension methods define in C# to be found and bound in VB and vice versa. Anyone developing a compiler for a language what supports extension method should make sure they decorate the extension methods with this attribute for inter-operability. The Attribute on the assembly and type are useful for tools like the object browser that must scan the .net framework assemblies looking for extension methods. (this considerabally reduced the complexity of their search). C# will not support the explicit usage of the Extension attribute in code. On finding the extension attribute on the method, type or assembly the compiler will throw an error and no IL will be generated (CS1112).

Once imported or defined in source the extension method is added to a cache. This cache will be used by lookup in compiler, language service (Intellisense) or the C# debugging components (watch/locals) for binding.

Lookup

When binding instance methods or delegate invocation the lookup can include extension methods in the results. On not finding any instance methods with the matching name, the lookup turns to extension methods. Extension Methods are first searched for in

1.     The innermost namespace where the call is to be bound,

2.      Then the namespaces imported by the "using clauses”.

3.     This process continues moving outwards until we reach the topmost namespace.

Therefore lookup create a list of extension methods for the innermost namespace and this will be the first list on which applicability will be tried. Then on the list for all the extensions imported by the using clauses in the namespace. So on ... Extension method lookup therefore creates an ordered list of lists containing methods.

Applicability 

Applicability will consider extension methods for binding in either of these 2 cases

1.     Only extension methods were returned by lookup.

2.     Instance methods returned by lookup were not applicable.

In case 2 applicability will actually call lookup and being the search for extension methods. This delays the potentially costly search for extension methods to when it is actually required. Applicability for extension methods is calculated by taking the instance object for the call and using it as the first argument to bind the methods. The first parameter and the instance object has a special conversion rule (covered in the previous post). If the arguments match the parameters on the extension method the applicability continues the binding process for all the methods that belong to the current Namespace list. On reaching the end of the list we try and see if we have one method that can be considered best if so the binding is successful else ambiguous.  

Error Reporting

Now that we know how the compiler finds and binds the instance & extension methods calls written by the user, let’s see what happens when things go wrong. For me good error messages are one of the cool features of C# and keeping the quality high is always an strong motivation. Since extension method binding might do things differently than instance methods it is specially important that the user be able to diagnose what the source of the problem is. To this end many of the old error message like CS1061 have been modified to mention extension methods, new messages have been added and at a few places the type of error message thrown is changed. I will try and mention a example for each of these cases and illustrate my point. In each of the examples below show the actual error in the green, the new and improved error message in blue and the old and confusing error message in red. Go ahead read though and be the judge….

Lookup Error

Whenever extensions methods are applicable for a member lookup (using instance method semantics), on not finding any members that matched the given name we give the new error message.

"'%1!ls!' does not contain a definition for '%2!ls!' and no extension method '%2!ls!' accepting a first argument of type '%1!ls!' could be found (are you missing a using directive or an assembly reference?)"

Applicability Errors

   

1.       The Error reporting for extension methods has been improved to take into account the conversion of the instance argument to the first param of the extension method. If this fails then the extension method is not present on the type in question and we give a error message that looks like a lookup error. This error is also used by the Query Error reporting to give the error lile " Could not find an implementation of the query pattern for source type %1 ... “

E.g.

var list = new ArrayList { 0, 1, 2, 3, 4, 5 };

list.Select((x) => x + 1).Where((x) => x > 5); // remember no extension methods work for arraylist try list<T> instead

Results in

error CS1061: 'System.Collections.ArrayList' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'System.Collections.ArrayList' could be found (are you missing a using directive or an assembly reference?)

And not (Beta1)

error CS0411: The type arguments for method 'System.Linq.Queryable.Select<TSource,TResult>(System.Linq.IQueryable<TSource>, System.Linq.Expressions.Expression<System.Linq.Func<TSource,TResult>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

   

IEnumerable i;// no extension methods define for Enumerable only Enumerable<T>

i.Select((x) => x);

Results in

error CS1061: 'System.Collections.IEnumerable' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'System.Collections.IEnumerable' could be found (are you missing a using directive or an assembly reference?) and not binding error.

And not(Beta1)

error CS0411: The type arguments for method 'System.Linq.Queryable.Select<TSource,TResult>(System.Linq.IQueryable<TSource>, System.Linq.Expressions.Expression<System.Linq.Func<TSource,TResult>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

   

2.       There is a new error messages for when the arguments binding fails for Extension method's Instance arguments. Such an extension method is not considered for error reporting if one with a match on the instance parameter can be found.

Consider

Test t = new Test();

double doub = 2.4;

t.Calc(doub); //can’t convert from Test to the derived type DTest

Where

public class DTest: Test

{}

And

static class Extension

{

   public static double Calc(this DTest obj, double i)

   { return i;}

}

   

Result in error

error CS12533: 'Test' does not contain a definition for 'Calc' and the best extension method overload 'Extension.Calc(DTest, double)' has some invalid arguments

error CS12534: Instance argument: cannot convert from 'Test' to 'DTest'

And not (Beta1)

error CS1502: The best overloaded method match for 'TestLinq.Extension.Calc(TestLinq.DTest, double)' has some invalid arguments

error CS1503: Argument '1': cannot convert from 'TestLinq.Test' to 'TestLinq.DTest'

For Type Inference Failures

 Extension methods are unique in the way that the Applicability test determines if the Extension method is defined for the type. That is lookup only determines if we found an extension method with the given name and not if the receiver can be converted to the instance prams of the extension method. Therefore when the type inference for an extension method fails on the instance argument, it means that the extension method is actually not defined for the type and a lookup error should be given. And similarly when the type inference fails arguments other than the receiver we should only show the extension methods that match the receiver’s type with the instance prams.

   

 

   var list1 = new List<int> { 1, 2, 3, 4 };

   q = from x in list1

        from y in 5 //we need a collection here something like new int[]{5}

        select x;

Or

list1.SelectMany(y => 5, (x, y) => x);

   

Will result in error

Error CS0411: The type arguments for method 'System.Linq.Enumerable.SelectMany <TSource,TCollection,TResult> (System.collections.Generic.IEnumerable<TSource>,System.Func TSource, System.Collections.Generic.IEnumerable<TCollection>> System.Func<TSource,TCollection,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

And not( Beta1)

Error CS0411: The type arguments for method 'System.Linq.Enumerable.SelectMany <TSource,TCollection,TResult> (System.collections.Generic.IQueryable<TSource>,System.Func TSource, System.Collections.Generic.IQueryable<TCollection>> System.Func<TSource,TCollection,TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

 I hope this helps improve the understanding of error reporting for extension methods and give insights into diagnosing the real problem when you see a error message that says "method does not exist on type" or some such thing...:)

kick it on DotNetKicks.com