2

Well, it's clear for me that the title of my question is too complicated. I just tried to make it as specific as possible. So, I'll try to explain the problem better.

Problem context

Let's assume we have three .NET projects in a solution. The main project is a simple console application ApplicationAssembly. This project has a reference to another managed assembly library DirectlyReferencedLibrary. At the same time DirectlyReferencedLibrary refers to IndirectlyUsedLibrary.

So, the project usages looks like that: ApplicationAssembly --> DirectlyReferencedLibrary --> IndirectlyUsedLibrary.

Notice that ApplicationAssembly doesn't use directly any type declared IndirectlyUsedLibrary. Let's also assume that all types declared in these assemblies reside in the same namespace.

This solution compiles and works fine.

Weird problem

The problem occurs when I have together the following conditions:

  1. the ApplicationAssembly project has usages of LINQ expressions. For example, if there is the invocation of Select() on any object of enumerable type.
  2. The DirectlyReferencedLibrary declares a class which has a generic extension method with a type restriction. The type restriction says that the generic type must be a descendant of a class from the IndirectlyUsedLibrary.

Here is the example of a such class.

using System;

namespace Test
{
    public static class UnUsedType
    {
        // It's a generic extension method with a type restriction.
        public static void Magic<T>(this T @this)
            // It's a type restriction that uses a type from the IndirectlyUsedLibrary.
            where T : ProblemType
        {
            Console.WriteLine("I do nothing actually.");
        }
    }
}

When I try to compile this project, I get the following error:

Error The type 'Test.ProblemType' is defined in an assembly that is not referenced. You must add a reference to assembly 'IndirectlyUsedLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'. C:\Projects\Test\ApplicationAssembly\Program.cs 22 13 ApplicationAssembly

Question

Can anyone help me to understand why is it so?

P.S.

I've made a tiny solution for investigation. If you are so kind to help me, you will be able to take an archived solution here

P.P.S.

Sorry for my poor English.

UPD1

Another strange thing is that different invocations of the LINQ method may or may not produce the compile time error:

// Ok. Let's do some work using LINQ we love so much!
var strings = new[] { "aaa", "bbb", "ccc" };
Func<string, object> converter = item => (object) item;

// The following line makes problems.
var asObjects1 = strings.Select(converter);

// Everything is OK if we use the following line:
var asObjects2 = Enumerable.Select(strings, converter);
Igor Soloydenko
  • 11,067
  • 11
  • 47
  • 90
  • 1
    Why don't you add a reference to `IndirectlyUsedLibrary` in `ApplicationAssembly` project – L.B Dec 21 '11 at 23:06
  • 2
    It's simple. I don't want to do it. There is no sense in it in my real project. I don't know how to express it polite in English... Don't try to change the definition of problem. I want to understand, why the compiler asks me for that reference. – Igor Soloydenko Dec 21 '11 at 23:10
  • It appears it won't complain if the class that contains the extension method is in an unreferenced namespace like `Test.Magic`. – BrainStorm.exe May 23 '16 at 19:50

3 Answers3

14

Can anyone help me to understand why is it so?

The C# compiler has the reasonable expectation that the transitive closure of the referenced assemblies is available at compile time. It does not have any kind of advanced logic that reasons about what it definitely needs, might need, might not need, or definitely does not need to know in order to solve all the problems in type analysis that your program is going to throw at it. If you reference an assembly directly or indirectly, the compiler assumes that there might be type information in there that it needs.

Also, if you don't have the set of referenced assemblies at compile time then what expectation is there that users will have them at runtime? It seems reasonable to expect that the compile time environment has at least the set of assemblies that are going to be required at runtime.

I don't want to do it.

We all have to do things we don't want to do in life.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • The only reason why I don't want to refer to the assembly is that these assemblies don't have the relation if we look on them from the 'business model' point of view. Maybe it was a mistake but this approach is used in our project. It's silly, but I can't explain why we use it. I think it just seems naturally to us. Is it better to think about assemblies as of types deployment units? In this case I might feel more freedom in referring required assemblies. – Igor Soloydenko Dec 21 '11 at 23:53
  • 2
    Other compilers for other languages (e.g. ghc) have a similar restriction and I assumed it was actually for the benefit of the developer. The compiler could resolve the reference without grumbling, but forcing the developer to provide explicit references makes deployment easier. – arx Dec 22 '11 at 00:12
  • And once again. It's very strange for me that the compiler is silent if I invoke LINQ methods in a static method style. The problem appears only when I use the 'instance method' invocation style. – Igor Soloydenko Dec 22 '11 at 00:12
  • 1
    @keykeeper: Is it *better* to think of assemblies as units of deployment of types? I don't know -- better than *what*? Is it a *good idea* to think of them that way? Well since *that is what they are*, yes, it is a good idea to think of them that way! An assembly is *by definition* the smallest *versionable, self describing unit of deployment of types* in .NET. That's what assemblies are for; what else would they be for if not deploying types? – Eric Lippert Dec 22 '11 at 00:35
  • 1
    @keykeeper: I do not know why the compiler complains one way and not the other but I can guess. Likely what is happening is that your use of fluent syntax and the compiler's translation are subtly different. The compiler may therefore be loading different methods of different types. The compiler is not *aggressive* about verifying that it has the transitive closure of assemblies. – Eric Lippert Dec 22 '11 at 00:37
  • @EricLippert I read something similar recently, here, but can't find the question to cite it. The explanation IIRC was that the compiler needs information in the indirectly referenced assembly when doing overload resolution on the extension method call, but when you call the static method with traditional syntax, overload resolution is much simpler and doesn't require the indirectly referenced metadata. Does that make sense? – phoog Dec 23 '11 at 04:04
2

I think you knew this, but because the type ProblemType is defined in the "IndirectlyUsedLibrary" but is a required definition for the Magic extension method it must be referenced to be available at compile time.

As to "why"... Well, the compiler needs to know details about what it's compiling doesn't it? It makes sense to me that the compiler require the same minimum set of compile time references that it requires at run time...

Reddog
  • 15,219
  • 3
  • 51
  • 63
  • Well, what is strange is not that the compiler asks me for the reference, but that it asks me only when I use anything related to LINQ using extension methods. If I do the same things using static method invocation style the compiler is silent. You can experiment with that: just comment var asObjects1 = strings.Select(converter); – Igor Soloydenko Dec 22 '11 at 00:00
  • Notice one more thing. I don't use the Magic method in the ApplicationAssembly. That's why I can't understand, why the compiler forces me to add a reference to IndirectlyUsedAssembly. – Igor Soloydenko Dec 22 '11 at 00:02
1

You'll not have an error if you use different namespaces for libraries. It's really wierd to use same namespace across different libraries.

Looks like compiler starts scanning your Test namespace for extensions once you first time use any. Hence the reference is required.

Sergei B.
  • 3,227
  • 19
  • 18
  • Thank you for a constructive reply! Actually, it's up to programmers to decide which namespaces to use across the projects. Let's assume that in my real project there is a good reason for it. At least I and my colleagues have made that decision already. Your idea about scanning for extensions is VERY interesting but I don't understand how to prove that. – Igor Soloydenko Dec 21 '11 at 23:21