5

There is library A calling library B using a C# extension method.

I got a weird error from the C# compiler:

The type 'System.Windows.Forms.Control' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Windows.Forms, Version=4.0.0.0

None of the libraries A or B are dependent on System.Windows.Forms.Control, nor has any dependency of A a dependency on System.Windows.Forms.Control. System.Windows.Forms.Control is only referenced from another project in the same solution.

The weird thing is that if I change the call syntax to static method, it compiles successfully.

//static method syntax works fine
var leads = SourceLeadConfigurationHelper.GetLeads(enLeadSystem);

//extension method syntax cause error
//error The type 'System.Windows.Forms.Control' is defined in an assembly that is not referenced. 
var leads = enLeadSystem.GetLeads();

The extension method looks like:

public static class SourceLeadConfigurationHelper
{      
    public static IList<ChannelID> GetLeads(this LeadSystem leadSystem);
    public static IList<ChannelID> GetLeads(this SourceLeadConfiguration slc);
    public static IList<ChannelID> GetLeads(LeadSystem leadSystem, bool throwException);
}

So you see there is no problem with detecting which extension to use. LeadSystem is an enum, SourceLeadConfiguration is a class.

I have Visual Studio 2013 update 5, .NET Framework 4.0.

Palec
  • 12,743
  • 8
  • 69
  • 138
Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148

1 Answers1

8

This is a pretty consistent complaint about C# compiler behavior, drives programmers pretty nutty. Unfortunately there is no canonical Q+A for it, every case is different. The first reports of it started showing up around the VS2012/.NET 4.5 time frame. The compiler did not used to behave this way, it smells like a bug fix that had a bigger impact than anticipated. We also don't hear about it often enough, most programmers just follow the guidance in the error message and add the assembly reference. So should you, it trivially solves the problem.

Trying to characterize it a bit here. It doesn't directly have anything to do with extension methods, this behavior is specific to method overload resolution. Extension methods are just a special case of it, a tricky case for sure.

Finding a method overload match is not particularly tricky, it is generating a decent error message when the overload is ambiguous that is the problem. One thing that's quite clear about the changed behavior is that the compiler is now more thorough. It insists on knowing everything about the type of an argument. Even if it is crystal clear to programmer's eyes that the passed argument cannot possibly match a type in another unreferenced assembly. But the compiler is pig-headed about it, if it does not know the type then it insists you add the reference.

Extension methods are tricky because there might be many, not just the SourceLeadConfigurationHelper.GetLeads() one you (obviously) expected to be picked. An assembly might define other extension methods, and they just might add more extension methods to the SourceLeadConfiguration type. If the compiler knows the assembly exists, but does not know what it looks like, and thus can't know what extension methods it may contain, then it will complain. Note how this explains why you don't get the error when you call the static method directly, there is no need for the compiler to go look for extensions.

You can surely guess how the compiler got to find out about the System.Windows.Forms assembly. It got introduced by the other assembly, the one you mentioned but did not describe. The diagnostic is that the System.Windows.Forms.Control type leaked out in the metadata of that assembly. Usually as an argument or return type of a public method. You'll have to do some digging in the source code to find it, not otherwise a slamdunk that you can eliminate it.


One more gritty detail about extension methods that might be relevant here, the ExtensionAttribute type was moved in .NET 4.5 from System.Core.dll to mscorlib.dll. A rather heavy change and one that has caused lots of problems when programmers don't build their assemblies correctly. If you target .NET 4.0 then you'll want to double-check this, details are here.


Hopefully you can chase it down from the assembly that references Winforms. If not, or you can't eliminate the exposure, then adding the reference is really all it takes to keep the compiler happy.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I expected something like that. But I cannot still find out how the compiler behave like this. I have searched for all expressions "this SourceLeadConfiguration", but all find results are in libraries without any reference to Winforms (not even subreference). I suppose it is enough to look for "this SourceLeadConfiguration". Isn't it? – Tomas Kubes Mar 11 '16 at 14:42
  • No, you didn't get it. You missed the explanation that the compiler wants to know about all assemblies it encounters. Those assemblies *may* contain extension methods. So it has nothing to do with SourceLeadConfiguration, you have to chase "Control". – Hans Passant Mar 11 '16 at 14:47
  • Hopefully I get it. The compiler assumes that System.Windows.Forms.dll could contain another extension of this type, but the compiler cannot find the System.Windows.Forms.dll to check it. Am I right? – Tomas Kubes Mar 11 '16 at 15:37