Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Curious ambiguity in attribute specification (two using directives)

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".)

like image 840
Jeppe Stig Nielsen Avatar asked Aug 30 '19 08:08

Jeppe Stig Nielsen


1 Answers

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.

like image 69
Jodrell Avatar answered Nov 10 '22 20:11

Jodrell