Color Color

Color Color

Rate This
  • Comments 24

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.

  • 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.

  • 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.

  • (Just some constructive (!) feedback.)

    I had a bit of trouble reading this one. C and c were very similar in the font used for the code samples, with the capital being just a pixel taller and wider. When both were on the same line, I could see which one was which, but on its own, I had to read carefully to see if I was looking at the class name or the instance name.

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

  • 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.

  • 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

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

  • 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.

  • I can only guess, that that a copy cat from Java'a syntax, where you do just the same thing, and the only difference is whever you do have an object ref, or a class name to the left side of the dot-operator.

    Btw. I *hate* the feature of calling statics via object reference. It's sick. You should never be able to directly call static method X having an object on the left side. It breaks the barrier with object methods and free functions [which the statics are really are]. Pah, if you allow to call My::StaticFunc via myobj.StaticFunc, why not allow the static to be VIRTUAL at all??? as the actual class of myobj changes, runtime could pick the actual member override, or the static (override) from actual class instead of hardwiring the call to the static taken from the declared class of the variable 'myobj'.

    And to mention that if you change the type of 'myobj' variable, the call gets instaneously wrong, misleading or simply uncompilable.

    The problem may apply also to one of the latest addition, the 'extension methods' that are in fact statics but (via language syntax sugar) are behaving like methods. Issues with them are however much lighter cases, as the extensions *require* supplying of the 'this' parameter, and thus logically are miiight beee seen as methods, not functions, but without private/protected access granted. Personally, I'd still argue that this is breaching the barrier of clear scoping and produced ambiguous code. Ambiguous for people, not the compiler [try to find where the extension method is defined, when someone else forgot to provide correct using clause, doh!], but unfortunatelly they'are all too useful due to sealed/internal/nomultiinheritance/nomixins features of the language :(

    Yep, I'm from C++ tribe, my point of view is further bended due to not so small experience in creating compilers and VMs/runtimes, but I love loose coupling and dynamism like in Ruby. and I'm strict about few things. But even in Ruby trying to call static method [there - called 'class method' opposed to 'instance method'] on an object that is NOT class [in Ruby classes are mutable object], results in crash/error/exception. Simply: object x of class X doesnt HAVE method X.do. Class owns it.

Page 2 of 2 (24 items) 12