Overview
Debugging an interesting bug, I stripped down a solution to its bare bones in an attempt to isolate it. The issue manifests itself with a classic build error (assembly not referenced).
What's interesting is that no types from the assembly in question are used in the assembly that cannot be built. Moving using
statements and removing CLS compliance attributes make the error disappear, implying that an obscure symbol visibility rule is in effect (probably).
Code
Mandelbug/
|-- Mandelbug.sln
`-- src
|-- Bar
| |-- Bar.csproj
| `-- Beta.cs
|-- Baz
| |-- Baz.csproj
| |-- Gamma.cs
| `-- Properties
| `-- AssemblyInfo.cs
`-- Foo
|-- Alpha.cs
`-- Foo.csproj
Except for the project (csproj
) files, the entire codebase is shown here, line-by-line.
Foo/Alpha.cs
namespace Foo {
public class Alpha : Bar.Beta { }
}
Bar/Beta.cs
namespace Bar {
public class Beta { }
}
Baz/Gamma.cs
namespace Foo {
using System;
public class Gamma { }
}
Baz/Properties/AssemblyInfo.cs
[assembly: System.CLSCompliant(true)]
Dependencies
All projects reference System
and System.Core
.
Baz
references Foo
.
Foo
references Bar
.
Foo.Alpha
needs the dependency to inherit from Bar.Beta
.
Baz.Gamma
needs the dependency for other types in Foo
in the real solution (unrelated to Alpha
). None are used in this stripped down solution, yet the issue still appears.
Compilation error
Description: The type 'Bar.Beta' is defined in an assembly that is not referenced. You must add a reference to assembly 'Bar, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
File: src\Baz\Gamma.cs
Line: 1
Column: 11
Project: Baz
Behavior
The problem disappears when:
- Placing the
using
statement inGamma
outside theFoo
namespace block. - Removing the
CLSCompliant
attribute for theBaz
assembly. Alpha
does not inherit fromBeta
(even ifFoo
still referencesBar
andAlpha
still usesBeta
in other ways, for example by composing it).Foo
is out of reach fromGamma
(for example by changingGamma
's namespace toBaz
).- A reference to
Bar
is added inBaz
.
It doesn't matter if the other projects are CLS compliant or not. In the real solution they are.
The target framework doesn't seem to influence that behavior.
It doesn't matter what namespace we import in the Gamma.cs
namespace block, we're writing using System;
here, but it could be anything. Removing all using
statements (or placing them outside the namespace block) makes the problem go away, as mentioned above.
Questions
- What CLS rule affects that behavior?
- Why does placing the
using
statement inside or outside the namespace block affects that behavior? - This is not just a curiosity, we actually have a project with such a transitive dependency -- should we add the reference to
Bar
even though it doesn't make sense? (We're not using theAlpha
inherited type.)
Notes
I know the compiler is never wrong, but it still looks like some serious voodoo when you don't know what's going on (especially the using
statement placement thing). I've skimmed the I.11 section of ECMA-335 (CLI) which contains a reference of CLS rules, but I can't pinpoint which one is the one I should understand but obviously don't.
Should the issue not be obvious, you should be able to reproduce it consistently by simply copying the code verbatim in empty C# projects and adding the same references.