Background: In an attribute specification, there is sometimes two valid ways to write the applied attribute. For example, if an attribute class has the name HorseAttribute
, you can apply the attribute as either [HorseAttribute]
or just [Horse]
. Ambiguities can be resolved with a @
, for example [@Horse]
.
The following is a valid program:
using System;
using Alpha;
using Beta;
namespace N
{
[Horse]
class C
{
}
}
namespace Alpha
{
// valid non-abstract attribute type with accessible constructor
class HorseAttribute : Attribute
{
}
}
namespace Beta
{
// any non-attribute type with that name
enum Horse
{
}
}
The C# compiler is able to pick Alpha.HorseAttribute
when I write just [Horse]
. And after all, the type Beta.Horse
is entirely unsuitable for use in an attribute specification.
Even if I swap the names, the C# compiler will know what to do:
using System;
using Alpha;
using Beta;
namespace N
{
[Horse]
class C
{
}
}
namespace Alpha
{
// valid non-abstract attribute type with accessible constructor
class Horse : Attribute
{
}
}
namespace Beta
{
// any non-attribute type with that name
enum HorseAttribute
{
}
}
Again, the compiler knows I want Alpha.Horse
.
And now for the code I want to ask about. It is identical to the above, except that the two types now have the same name:
using System;
using Alpha;
using Beta;
namespace N
{
[Horse]
class C
{
}
}
namespace Alpha
{
// valid non-abstract attribute type with accessible constructor
class Horse : Attribute
{
}
}
namespace Beta
{
// any non-attribute type with that name
enum Horse
{
}
}
Now, the C#-compiler refuses to build, saying:
error CS0104: 'Horse' is an ambiguous reference between 'Alpha.Horse' and 'Beta.Horse'
Try it online!
My question is, why can the compiler not pick the right one in this case, when it did it nicely in the two examples earlier?
Is this behavior in accordance with the C# Language Specification? Is it actually required that the C# compiler issues an error here?
(Of course I know I can resolve it by saying [Alpha.Horse]
explicitly, so I am not asking for that "solution".)
What we have here are two concepts conflated.
1. How the compiler knows what class implements an attribute
There is a simple convention in place that attributes may be referred to by either the class name or the class name less an attribute suffix. So when you add the [Horse]
annotation to someIdentifier
like this,
[Horse]
someIdentifier
the implementation of [Horse]
must be a class that inherits Attribute
that is called either HorseAttribute
or Horse
.
Note: There is a widely accepted convention that all classes that implement attributes should have "Attribute" suffixed to the type name.
2. How the compiler know's which type code is referring to
When we refer to a type, in code, the compiler looks for a definition of that type that has been loaded into the namespace. If there are multiple definitions for that type in the namespace the compiler does nothing to resolve that ambiguity, it is up to the developer to improve the code. The compiler can't choose so raises error CS1040.
The compiler does not do any semantic or static analysis to divine the coders intent. It would be hard to define, costly to perform and prone to error.
This error is not thrown solely when finding implementations for attributes.
In your compiling examples there is no ambiguity around point 2, so the code compiles.
If the resolution of point 1 leads to a type name that is ambiguous be it, Horse
or HorseAttribute
, then errors will come from point 2.
The compiler does not make special allowances, e.g I'm performing point 2 in response to point 1 so, if I have an ambiguity in this case is there a special fallback position for point 2s performed for point 1s?
If you consider the level of additional complexity and time that special provisions introduce you may accept that it would be better to require a level of stringency from code authors.
In my opinion and, that of others, requiring code that avoids this kind of ambiguity leads to code that is easier to understand by others and one's future self. This makes the discussion of why somewhat moot, as we could argue effort applied here by the compiler team would have enabled "smellier", harder to maintain code.
NOTE: Further to the answer
When you consider behaviour exhibited by the example from the Langauge specification
using System;
[AttributeUsage(AttributeTargets.All)]
public class X: Attribute
{}
[AttributeUsage(AttributeTargets.All)]
public class XAttribute: Attribute
{}
[X] // Error: ambiguity
class Class1 {}
[XAttribute] // Refers to XAttribute
class Class2 {}
[@X] // Refers to X
class Class3 {}
[@XAttribute] // Refers to XAttribute
class Class4 {}
Try here
I would agree there is a confusion and indeed, an inconsistency in the way the compiler treats definitions from one namespace and those imported from different namespaces.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With