次の方法で共有


Color Color

Pop quiz: What does the following code do when compiled and run?

class C
{
public static void M(string x)
{
System.Console.WriteLine("static M(string)");
}
public void M(object s)
{
System.Console.WriteLine("M(object)");
}
}
class Program
{
static void Main()
{
C c = new C();
c.M("hello");
}
}

(1) writes static M(string)
(2) writes M(object)
(3) uh, dude, this code doesn’t even compile much less run
(4) something else

Think about that for a bit and then try it and see if you were right.

.

.

.

.

.

.

.

In option (1), the compiler could decide that the best match is the static one and let you call it through the instance, ignoring the instance value. In option (2), the compiler could decide that the object version is the best match and ignore the static one, even though the argument match is better. But neither of those actually happens; the rules of C# say that the compiler should pick the best match based on the arguments, and then disallow static calls that are through an instance! The actual result here is therefore (3):

error CS0176: Member 'C.M(string)' cannot be accessed with an instance reference; qualify it with a type name instead

What is up with this craziness? If you believe that the rule “static methods should never be accessed through instances” is a good rule – and it seems reasonable – then why doesn’t overload resolution remove the static methods from the candidate set when called through an instance? Why does it even allow the “string” version to be an applicable candidate in the first place, if the compiler knows that it can never possibly work?

I agree that this seems really goofy at first.

To explain why this is not quite as dumb as it seems, consider the following variation on the problem. Class C stays the same.

class B
{
public C C = new C();
public void N()
{
C.M("hello");
}
}

What does a call to N on an instance of B do?

(1) writes static M(string)
(2) writes M(object)
(3) compilation error
(4) something else

.

.

.

.

.

.

A bit trickier now, isn’t it? Does C.M mean “call instance method M on the instance stored in this.C?” or does it mean “call static method M on type C”? Both are applicable!

Because of our goofy rule we do the right thing in this case. We first resolve the call based solely on the arguments and determine that the static method is the best match. Then we check to see if the “receiver” at the call site can be interpreted as being through the type. It can. So we make the static call and write “static M(string)”. If the instance version had been the best match then we would successfully call the instance method through the property.

So the reason that the compiler does not remove static methods when calling through an instance is because the compiler does not necessarily know that you are calling through an instance. Because there are situations where it is ambiguous whether you’re calling through an instance or a type, we defer deciding which you meant until the best method has been selected.

Now, one could make the argument that in our first case, the receiver cannot possibly be a type. We could have further complicated the language semantics by having three overload resolution rules, one for when the receiver is known to be a type, one for when the receiver is known to not be a type, and one for when the receiver might or might not be a type. But rather than complicate the language further, we made the pragmatic choice of simply deferring the staticness check until after the bestness check. That’s not perfect but it is good enough for most cases and it solves the common problem.

The common problem is the situation that arises when you have a property of a type with the same name, and then try to call either a static method on the type, or an instance method on the property. (This can also happen with locals and fields, but local and field names typically differ in case from the names of their types.) The canonical motivating example is a public property named Color of type Color, so we call this “the Color Color problem”.

In real situations the instance and static methods typically have different names, so in the typical scenario you never end up calling an instance method when you expect to call a static method, or vice versa. The case I presented today with a name collision between a static and an instance method is relatively rare.

If you’re interested in reading the exact rules for how the compiler deals with the Color Color problem, see section 7.5.4.1 of the C# 3.0 specification.

Comments

  • Anonymous
    July 06, 2009
    I remember been quite surprised when I first found out that "Color Color" is a grammar corner case that is specifically covered by the spec in quite a lot of detail and extra special wording. Of course, in retrospect, it makes perfect sense, and I appreciate the fact that it's there (it does make life easier when coming up with fitting member names). It would be interesting to know the design history behind this. Is it something that was considered from the very beginning of C#? Or rather something that only came up when frameworks (I would guess WinForms) ran into the problem? This issue first appears in the design notes on June 22nd, 1999. Of course, by that point, people already had started writing the FCL in pre-beta versions of the language, it is entirely possible that this was driven by someone running into the problem in the framework. The notes do not say. -- Eric

  • Anonymous
    July 06, 2009
    Eric, Can you explain why in the following code: static void M(object[] array) {...}
    static void M(object obj) { ... }
    ...
    M(null); M(object[] array) is called instead of M(object obj)? Well, suppose it had been static void M(Animal x ) {...}
    static void M(object x) { ... }
    ...
    M(new Giraffe());

    Which would you expect to be called? When given the choice between "Animal" and "object", clearly "Animal" is more specific. We assume you want to call the more specific match when there are two possible matches. All Animals are objects, but not all objects are Animals, so Animal is more specific. In your example, null matches both. All arrays are objects but not all objects are arrays, therefore the array one is more specific. The more specific one wins. -- Eric

  • Anonymous
    July 06, 2009
    I've gotta confess that I got this wrong (thought option 2 was correct).  I made the erroneous inference from disallowed behavior (can't access a static member through an instance reference).  Thinking like a human so often leads one (me?) astray! Thanks for illuminating the distinction and giving us a little insight into the order in which these operations (method resolution vs static access) are examined.

  • Anonymous
    July 06, 2009
    Unfortunately this also means that in order to call the instance method (in either example), the argument has to be cast to object. Upcasting always leaves a bad taste in my mouth; I would have preferred a solution that required the developer to explicitly remove the ambiguity in either the static case or the instance case by explicitly qualifying the receiver (i.e. global::C or this.C), rather than implicitly selecting an overload by upcasting an argument; the resulting code would be cleaner IMHO.

  • Anonymous
    July 06, 2009
    On second thought, there is nothing stopping you from explicitly qualifying the receiver; for some reason the first "solution" I thought of was upcasting, despite that being my least preferred solution.

  • Anonymous
    July 06, 2009
    commongenius: And what about locals? void N() {    C C = new C();    C.M("?"); } How would you explicitly qualify the receiver?

  • Anonymous
    July 06, 2009
    Will the same resolution rules be retained when using dynamic types in C# 4.0? In other words:   dynamic C = new C();   C.M(...);  // what gets called here and why? I've often wondered whether typing something as dynamic will cause overload resolution to behave differently than it would if they type were known at compile time. There's already one potential case you can read about here ... http://stackoverflow.com/questions/987176/overload-resolution-in-c-4-0-using-dynamic-types

  • Anonymous
    July 06, 2009
    Having static and instance methods with the same name doesn't seem terribly useful, it's not obvious what the compiler will do with them and even when it's explained people don't necessarily agree that the compiler is doing the right thing.  If you were starting over would you retain this feature?

  • Anonymous
    July 06, 2009
    That's why it's better if you live in Australia. We have: class MyClass {   public Color Colour { get; set; } } and all is well :-)

  • Anonymous
    July 06, 2009
    @Dean: As a New Zealander, I believe I can safely say AAARRRGH! :)

  • Anonymous
    July 06, 2009
    The comment has been removed

  • Anonymous
    July 06, 2009
    The comment has been removed

  • Anonymous
    July 07, 2009
    @Luke: It was pointed out to me by a colleague that namespaces which are named after the sorts of things they contain could often be better named with a plural. E.g.: namespace Animals { ____class Animal {} ____class Giraffe : Animal {} } This is mentioned here in the Microsoft Design Guidelines for Developing Class Libraries: http://msdn.microsoft.com/en-us/library/ms229026.aspx "Consider using plural namespace names where appropriate. For example, use System.Collections instead of System.Collection." As an example, there's System.Net.Sockets.Socket.

  • Anonymous
    July 07, 2009
    I think "Color Color" should have been disallowed in the runtime; types should have been named differently than members in the guidelines. Of course, much of my experience is writing tools for C#, and this would have made my life easier.  Perhaps allowing this really is better for users of the language, but I am doubtful. I think we'd be better of with a 'C' prefix on classes (and an 'S' prefix on structs, protecting us from a set of bugs when we handle structs in a way that only works for reference types).  You may cry "Hungarian", but I point out that we already name interfaces with an 'I' prefix, so this change would improve consistency.

  • Anonymous
    July 07, 2009
    The compiler error should be on the second declaration of method c.M with an error "Cannot define second function with the same name as a static function." The error on the call to the function is misdirection and should be avoided. Improving the .NET compiler to produce much better diagnosis of these conditions would be great.  Putting significant effort into improved static analysis of C# code would greatly help (e.g., much improved FxCop).   Tools for embedded C++/C have been around doing this in great detail for 20+ years.

  • Anonymous
    July 07, 2009
    Greg.... WGreg.... WHY do you think it should generate an error within the class implementation. There is NOTHING wrong with the class definition. Having Static and Memeber methods with the same name is 100% legal.

  • Anonymous
    July 07, 2009
    Greg, While it may not completely conform to all "best practices", I have used a number of design patterns that have static and instance methods with the same name and different parameters to very good effect. It tends to occur when one adopts a "pure factory" approach (i.e. "new" is ONLY used inside of a factory method of a class, and never directly in the application). This has a nice "advantage" of being able to support many forms of type substitution (including DI) without ever having to modify the calling code. Since "Create" is a logical name for such a function, you get the static/instance overload: one version of Create that is the factory method, and one for creating a new element within the context of an instance.

  • Anonymous
    July 09, 2009
    The comment has been removed

  • Anonymous
    July 13, 2009
    I thought this was an interesting corner case:  http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/1511bee3-2a1e-49ba-b1a1-f3d54ff7ec0f

  • Anonymous
    July 14, 2009
    Great post Eric as always, thanks. But what about this code?    class Bar    {        public string Type { get; set; }        private static Type GetType(string typeName)        {            Type type = Type.GetType(typeName, false, true); //<-compile error            return type;        }    } This code failed to compile with error CS0120: An object reference is required for the non-static field, method, or property 'Test.Bar.Type.get' at Type.GetType(typeName, false, true) method call. Of course if you renamed "Bar.Type" property to something else it fixed the problem.

  • Anonymous
    July 14, 2009
    Holatom, Eric can confirm, but I believe it is because the original item was about overloading multiple methods and resolution based upon signature while your example is about resolution of a Type (no pun intended) and a Property; and I believe (don’t have the spec handy) a Property will always “trump” (not Donald) a Type in this condition. David

  • Anonymous
    July 15, 2009
    Thanks, you are most likely right, because in this case is problem with "Type" identifier not in actual GetType() method call. It seems that this is "only" inconsistency between C# compiler and VS 2008, because VS 2008 colours "Type" identifier in code above as class (not as normal identifier) and intelliSense is not working either (neither as string reference or Type class).

  • Anonymous
    July 27, 2009
    I'm curious as to why during the design of C# the call to static methods was done through . just like a call to instance methods, instead of through say :: Which then clearly separates what's static and what's not.  Because as reading the second code option, it's quite ambiguous.   What's done is done and can't be changed, but there must have been a reason or it was under a particular context.

  • Anonymous
    August 24, 2009
    The comment has been removed