Calling static methods on type parameters is illegal, part one

Calling static methods on type parameters is illegal, part one

Rate This
  • Comments 25

A developer passed along a question from a customer to our internal mailing list for C# questions the other day. Consider the following:

public class C { public static void M() { /*whatever*/ } }
public class D : C { public new static void M() { /*whatever*/ } }
public class E<T> where T : C { public static void N() { T.M(); } }

That's illegal. We do not allow you to call a static method on a type parameter. The question is, why? We know that T must be a C, and C has a static method M, so why shouldn't this be legal?

I hope you agree that in a sensibly designed language exactly one of the following statements has to be true:

1) This is illegal.
2) E<T>.N() calls C.M() no matter what T is.
3) E<C>.N() calls C.M() but E<D>.N() calls D.M().

(If there is a fourth possible sensible behaviour which I have missed, I am happy to consider its merits.)

If we pick (2) then this is both potentially misleading and totally pointless. The user will reasonably expect D.M() to be called if T is a D. Why else would they go to the trouble of saying T.M() instead of C.M() if they mean "always call C.M()"?

If we pick (3) then we have violated the core design principle of static methods, the principle that gives them their name. Static methods are called “static” because it can always be determined exactly, at compile time, what method will be called. That is, the method can be resolved solely by static analysis of the code.

That leaves (1).

Related questions come up frequently, in various forms. Usually people phrase it by asking me why C# does not support “virtual static” methods. I am always at a loss to understand what they could possibly mean, since “virtual” and “static” are opposites! “virtual” means “determine the method to be called based on run time type information”, and “static” means “determine the method to be called solely based on compile time static analysis”.

(Then again, people occasionally accuse me of “dogmatic skepticism”. Since “dogmatic” and “skeptical” are opposites, I am never sure quite what they mean either. My conclusion: people sometimes say strange things.)

Really what people want I think is yet another kind of method, which would be none of static, instance or virtual. We could come up with a kind of method which behaved like our option (3) above. That is, a method associated with a type (like a static), which does not take a non-nullable “this” argument (unlike an instance or virtual), but one where the method called would depend on the constructed type of T (unlike a static, which must be determinable at compile time).

I’m not sure whether the CLR generic system supports codegenning such a beast, but other than that, I don’t see any in-principle reason why such a thing would be difficult to do. But we do not add language features just because we can; we add them when the compelling benefit outweighs the costs. No one has yet made the case to me that this kind of method would be really useful.

Next time I'll answer the follow-up question: why isn't this determined at compile time? That will take us into the subtle differences between templates and generic types. They are often confused.

  • Interestingly, some languages have "class methods" instead of "static methods" which have exactly property 3.  Smalltalk is the most notable language with this feature, but you can do it with CLOS and a few other class-and-object-oriented environments too.  It relies on the class itself also being an object upon which virtual dispatch can be performed, and you don't get the static compile-time method resolution (since it's just virtual dispatch on a different object), but it enables some powerful tricks, like eliminating the need for public constructors.

    I've always been sad that Java (my poison of choice) doesn't have class methods.  Any chance we will ever see those in .Net?

  • I can imagine that this might be useful not only for code generation, although I can't think of an example right now. But if it's only a rare case anyway, why not just call that method via reflection?

    public class E<T>

    where T : C

    {

     static s_M = typeof(T).GetMethod ("M", Public|Static, Type.EmptyArray);

     public static void N() { s_M(); }

    }

    (If your only problem were that the CLR can't do that trick, you could have the compiler generate this code just as well, but it seems that's not the case here.)

    The overhead from reflection is usually neglectible, as this is only called once. It's of course weakly bound, but so is the original code you've been given. Since there is no explicit override, a single type paramter mismatch would create the much more subtle bug of silently calling the wrong method. Anyway, it's probably good enough for something I might use once a year, and definately good enough for code generators.

    I think you're right, there are more needed features for C# waiting to be justified against their cost (e.g, crowd pleasers like "infoof", complex attribute arguments, ordered and generic attributes, or boo-ish duck typing/DLR integration? How about those btw.? Ah well, just give us some AST meta programming model in the compiler pipe ;-) )

    May I ask something off-topic? What do you think of the rule that unlike Java, methods are not automatically virtual in C#?

    The answer is usually, because declaring a method virtual is part of a class's contract, and means that you have to support a certain behavior in future versions. Agreed.

    But shouldn't generated code be able to override non-virtual methods? Why shouldn't I be able to create IL code (eg via Reflection.Emit) that does override them? It is allowed to access private members using Reflection because if you determine your target at runtime, your code is probably able to adapt to future changes automatically, so you don't create a contract obligation. The same would be true for generating overrides of methods on the fly. This would make support for AOP-like features (using sth like subclass proxies) much better.

    This would of course have some important implications. But as more and more frameworks (including EntLib) are going in this direction, I think supporting this is becoming increasingly important. Would it be sensible to abandon the performance advantage of call instead of callvirt in all other cases? It might be preferrable to have this applied only to selected classes, and within C# and comparable languages, overrides should still be impossible. What do you think?

  • To add to Angstrom above, we can add our favorite language, JavaScript, to that list...

    How would JScript.NET handle such a situation anyways?

  • angstrom, harmony - does it really make sense to point at features from dynlangs and ask, why is this not in c#? why not ask for completely name-based dynamic dispatching, or double/multi-dispatch too? i think it would be really interesting to make C# interoperate with the DLR (like in boo, where you can define variables of type "duck" and get duck typing on them, but on them only), but should this really influence the normal way in which C# resolves and calls methods? Or should we just accept that C# is static and use other languages if we want completely dynamic dispatching?

  • As a C++ programmer, I expect (3). I don't see "static" and think "this has to be resolved at compile time." To me, "static" is a highly-overloaded keyword that, in this case, means "the method doesn't have a this pointer." That's all. The time where the call is resolved doesn't enter into it. "static" applied to a class and "static" applied to a local variable have yet other meanings. Also, a static local variable isn't initialized at compile time - the ctor doesn't execute until runtime, or might not run at all, so the explanation of "static == compile time" breaks down there.

    Since T.M() is illegal, how do you write generic classes with a policy parameter? (for example, the Copy parameter to ATL::CComEnumImpl<>)

  • I don't seem to get the logic of allowing "new static" in a derived class but not allowing 3.). If overwriting of static methods using new is allowed, 3.) is the behaviour I would expect, not 1.). Otherwise it seems to be inconsistent to me. I mean, E<C> is a different type than E<D>, isn't it?

  • What is the "compilation" we are talking about? is it running csc.exe or is it running the JIT? Does this make a difference? At the time of jitting, isn't 3.) properly defined?

  • Can you explain to me why 3.) would violate the "because it can always be determined exactly, at compile time, what method will be called" principle? Why can't that be determined exactly at the time of JIT-ing?

  • There are already some CLR languages around that support class methods, also virtual ones. I recall Delphi.NET and Chrome are two examples of such languages. These are both statically typed languages.

  • >> Usually people phrase it by asking me why C# does not support “virtual static” methods. I am always at a loss to understand what they could possibly mean, since “virtual” and “static” are opposites!

    I think what's going on here is that a lot of people (yes, I'm projecting here :)) *think of* static methods (and other members) as 'class' members as defined by commenters above. Indeed, the help for static says:

    >> Use the static modifier to declare a static member, which belongs to the type itself rather than to a specific object.

    Given this alone, from a didactic perspective it's hard to see how developers new to C# are supposed to work out that 'static' is more than just a piece of historical syntactic cruft, but is actually *means something* - “determine the method to be called solely based on compile time *static* analysis”.

    The explanation in the post makes perfect sense given that static means what I now know it does. However, I now have to change my mental model, which is always interesting...

    ps: I've just searched through the C# spec for 'static'. There's a deal of stuff about '... belong to [the] class ...' and nothing at all about 'must be determinable by compile time static analysis'. I can't be along in my mental model confusion...

  • I agree with Mike. This is especially true since we have some methods (non-virtual ones) that are resolved at compile time but are not decorated with the static keyword. Intuitively, "static" on class methods means "there is no this pointer", if it meant "resolved at compile time" we should really be adding this keyword to every non-virtual method. This means that "virtual static" really does make sense, and would have the behaviour of 3 above.

  • Your definition of 'static' seems very strange to me. If 'static' truly means 'determine which function to call based on static analysis' then why is it applied to a function definition rather than to a function call site? How to determine which function to call is really up to the caller (and even if in some cases it's not, it should be, since they're the ones who know what they want to do!) -- even though usually they'll say either 'call a function with this name and signature' or 'call a function with this name and signature which is a member of the class corresponding to the dynamic type of this object', it's still the caller's decision, and they could instead write a big nested if statement dispatching on the type of some objects.

    Also, your definition of 'static' does not imply 'called without an object' -- it sounds very much like how I might define 'final' in Java, for instance.

    The meaning of 'static' in C++ class member functions is much more sensible in my opinion -- there it only really means 'this class member function can be called without a corresponding object'. Arguably, with this interpretation, 'virtual static' does make sense and would in practice occasionally be useful -- it would mean 'this class member function can be called without a corresponding object, but if it's called with an object, the member which gets called is determined by the dynamic type of that object'. This is a pattern which I've seen used several times in real-world C++ when providing metadata for types (see for instance QObject::metaObject()).

  • You can do this:

       public class C { public static void M() { /*whatever*/ } }

       public class D : C { public new static void M() { /*whatever*/ } }

       public delegate void Action();

       public class E<T> where T : C { private static Action A = (Action)Delegate.CreateDelegate(typeof(Action), typeof(T).GetMethod("M")); public static void N { A();} }

  • I'm not completely sure I understand why it can't be deduced at compile time, through static analysis only, which method will be called in option (3).  Isn't T known at compile time, and doesn't it logically follow that if T has a new method M, this would be known at compile time as well?

    Or is there maybe some subtlety in the Generics implementation that I'm missing here?

  • I was going to leave a comment about my apparent confusion of class entities vs static entities, but a lot of folks have left more eloquent responses to that than I would have.  Instead I'll just say that, from my own C++ background, I'd also have found (3) to be the intuitive resolution.  I'm looking forward to the followup post that will clarify the differences between generics and templates so that I can try to correct my mental model of the language feature.

Page 1 of 2 (25 items) 12