Unlike Java, C# does not let you access static
members via an instance reference. For example, C# does not let you do this:
public class A { public static String Hello() { return "Hello"; } } [Test] public void Call_static_from_instance() { A a = new A(); Assert.AreEqual("Hello", a.Hello()); //<<< error CS0176 /* error CS0176: Member 'A.Hello()' cannot be accessed with an instance reference... */ }
At first glance this seems an unreasonable restriction. The compiler knows that Hello()
is static, and failing the lack of an identical instance method, this should be right to call. This might be true for this simple example, but what should the following case do? (This one obviously won’t compile either.)
public class A { public static String Hello() { return "Hello"; } } public class B : A { public String Hello() { return "Hi"; } } [Test] public void Call_static_from_instance_with_inheritance() { A b = new B(); b.Hello(); //Calling via ref to A? Call static A or instance B? b = null; b.Hello(); //Call static based on type A? We don't have a B anymore! }
Now imagine adding this (remember, Java is virtual by default, and you can "override" statics):
public class C : B { public static String Hello() { return "Howdy"; } }
So what initially sounds reasonable actually gets hairy pretty quickly. While you could come up with rules as to how each case should behave, it would be near impossible to make these rules obvious to programmer’s that don’t study the language spec every night before bed. Dominik Gruntz published a great explanation of this issue (search for "6 STATIC MEMBERS"), and the reasons behind some other C# departures from Java conventions.
Most of the times I come across something like this that initially seems simple I find that digging into it shows a well-considered approach taken by Anders and the language design team (compare C# generics to the Java implementation for example). Even if you disagree with some of the decisions, such as non-virtual by default, at least a lot of thought has gone into trying to make things behave sensibly across a broad range of scenarios. It’s a fine line between mollycoddling developers and being a rope closet.
What about extension methods?
As an aside, it’s interesting to think about this issue in the context of C# 3.0 extension methods. They appear to let you call static methods from an instance (although in reality they compile to standard static calls).
A lot of problems are avoided by forcing them to be declared within a static class (which can’t inherit or be inherited from, nor can they be instantiated, avoiding the tangle from the example above). But to really understand how they behave in different circumstances you still need to mentally translate them as the compiler does (i.e. from s.SomeExt(args)
to MyExtensions.SomeExt(s, args)
) and know the resolution rules (including precedence given to instance methods). Which is probably why the spec has this disclaimer:
"Extension methods are less discoverable and more limited in functionality than instance methods. For those reasons, it is recommended that extension methods be used sparingly and only in situations where instance methods are not feasible or possible."