Okay, so the C# Language Specification has a special section (old version linked) on the Color Color
rule where a member and its type has the same name. Well-known
I am sure it has something to do with the fact that the value of the constant must be deterministic at compile time, but the value of the (static) property will be determined at run-time.
It is a bug. I am unable to reproduce the problem in CTP 5 of VS 2015, and I think this one should have been fixed as part of the Roslyn rewrite. However, a commenter below notes that they can reproduce it in CTP 6. So I'm not sure what is going on here as far as whether this bug has been fixed or not.
On a personal note: I do not specifically recall if I was tasked with looking into this one when it was first reported back in 2010 but since I did quite a bit of work on the circularity detectors around then, odds are pretty good.
This is far from the only bug there was in the circularity detectors; it would get quite confused if there were nested generic types which in turn had generic base types whose type arguments involved the nested types.
I am not at all surprised that Alex "won't fix"ed this one; I spent quite a long time rewriting the code that did class circularity detection and the change was deemed too risky. All that work was punted to Roslyn.
If you're interested to see how the Color Color binding code works in Roslyn, take a look at the aptly-named method BindLeftOfPotentialColorColorMemberAccess -- I love me some descriptive method names -- in Binder_Expressions.cs
.
1) It doesn't work with a const
because it's trying to allow both definitions (enum type and class member) at the same time, and so it tries to define itself as a function on itself.
2) Is it unintended? sort of. It's an unintended consequence to an intended behavior.
Basically, this is a bug that Microsoft acknowledges but has filed as 'Won't Fix', documented on Connect here.
I can't find the 5.0 language spec online (in article or blog form) anywhere but if you're interested, you can download it here. We're interested in page 161, section 7.6.4, Member Access, and it's first section 7.6.4.1, which is the same section the OP linked to (it was 7.5.4.1 then).
The fact that you can name a member and a type the exact same name (e.g., Color) is something that was explicitly allowed, even though your identifier now has two separate meanings. Here is the spec's language:
7.6.4.1 Identical simple names and type names In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.6.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example:
struct Color {
public static readonly Color White = new Color(...);
public static readonly Color Black = new Color(...);
public Color Complement() {...}
}
class A {
public Color Color; // Field Color of type Color
void F() {
Color = Color.Black; // References Color.Black static member
Color = Color.Complement(); // Invokes Complement() on Color field
}
static void G() {
Color c = Color.White; // References Color.White static member
}
}
Here's the key part:
both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.
When you define Color Color = Color.Brown
, something changes. Since I (Brown) must be a member of E (Color) in both cases (static and not-static), this rule allows you access to both, instead of restricting one due to the current (non-static) context. However, now you've made one of the contexts (your non-static one) a constant. Since it's allowing both, it's trying to define Color.Brown
as both the enum and the class member, but there's a problem with it depending on it's own value (you can't have const I = I + 1
for example).