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.

  • Aaron, Greg:

    I believe the subtlety you're missing is that C# generics are instantiated at runtime by the CLR.  Only the actual generic class definition itself is written into the assembly, so you cannot implement (3) without violating this 'static members must resolved during compilation' rule.

    C++ templates are instantiated during compilation, and each instance is written to the resulting .o file.  This makes them much more powerful than C# generics, at the cost of bloating the object file.

    For what it's worth, I agree with most posters in that my interpretation of 'static' in a class has always been 'one per class' rather than 'must be resolved at compilation time'.  So I don't see any issues at all with implementing (3) by resolving the method call when the generic is instantiated by the CLR, or even during runtime if necessary.

  • Thinking about it, I agree with those who say that the definition of static as "must be resolved at compile time" is neither intuitive nor very useful.

  • I expect option 2. That's because I usually use Delphi, which has the "class methods" that Angstrom mentioned in the first comment. In Delphi, you can store a class reference in a variable and call methods on it without needing an instance of the class. Such methods still receive a "this" parameter ("Self" in Delphi), but it is a reference to the class, not to an instance of the class. We Delphi users typically see methods marked as "static" in other languages and translate them into class methods in Delphi. The differences aren't usually important.

    Delphi class methods can be virtual because when calling them, the run-time value of the class-reference variable can vary. So, since C.M above is not declared virtual, any call to a method named "M" through a variable of type C must resolve to a call to C.M. The compiler doesn't know anything about D.M at the time it's compiling class E, so it certainly won't generate a non-virtual call to D.M. But if C.M were virtual (and I know that in C# it can't be), then class D could override that method, and then when E.N calls T.M, the run-time behavior would indeed depend on whether T was C or D.

    But that's with my Delphi glasses on. I said the differences between static methods and Delphi class methods aren't usually important, but this is a case where they are. Since static methods lack a "this" parameter, there is nothing on which to base a virtual call. I might expect option 2 instead, but with a warning diagnostic explaining that T.M always calls C.M. In that sense, option 1 and option 2 only differ by the severity of the diagnostic message.

  • Brian: no, it's not defined at JIT-time. the JIT produces exactly one implementation that is shared by all reference types, so this would only for work for value types. however, there seems to be some kind of this pointer for the type, since static methods in generic types do access their own static member fields. this could surely be used to implement a vtable for class methods - even by the compiler if the CLR won't do it.

    I think there are two possible ways to go ahead. If we think class methods are not so important, we can store delegates in the calling site like I sketched out in the slightly broken code snippet at the top (but you get the idea...)

    If they are important, it shoud be possible to use virtual/override for static methods, and a vtable lookup should be implemented.

    It doesn't make sense to use overloading for the code Eric postet. Implicit overriding is just to weak, so we'd be better off with using reflection manually - at least that way it's clear how weak it is.

    I would be more interested in the usage scenarios for class methods though. Like Eric said, any new feature should be evaluated for cost and usefulnes. However, I still believe that methods declared with the "static" keyword must be resolvable at compile time is neither intuitive nor useful.

  • Welcome to the XXVIII Community Convergence. In these posts I try to wrap up events that have occurred

  • Stefan,

    You can use this technique to make your example early bound and compiler checked:  http://dotnet.agilekiwi.com/blog/2007/04/symbols-part-2.html

  • John, this technique is useful for getting a MethodInfo of a method that the compiler would let you call anyway. All restrictions (like visibility and the one we're discussing here) apply. So no, you can't get a delegate for T.M for the same reason that you cannot call T.M. Even if you could, it would ruin the effect, since early binding is exactly what we do not want here. Since there are no static virtual methods, and overriding a static method using the "new" keyword, the same name and the same signature is an arbitrary concept of our code (not known to C# or the CLR), there is no way around dealing with strings.

  • There were lots of good comments on my previous entries in this series. I want to address some of them,

  • I have been crushed against the lack of “non-static class members” in .NET a few times. Let me pretend that I can make the case for such a creature to exist. If today wasn’t Sunday I could perhaps produce a good example based on a class hierarchy of my own. But let me try something easier…

    In the BCL there are multiple types that define static Parse and, from 2.0 and up, TryParse methods. The basic prototype for Parse is:

    public static T Parse(string s)

    And for TryParse is:

    public static bool TryParse(string s, out T result)

    My problem with those has always been that they are static (have to be resolved at compile time) and hence they cannot be treated with polymorphism. Also, the way things are in .NET, there is no way to define an IParseable or IParseable<T> interface for types that define those methods. Hence, there is no way you can build generic code over Parse or TryParse without using reflection. Also, IMHO, the way the BCL is build around this shortcoming is somewhat graceless (take for instance, the internal class Number).

    By the way, Visual Basic .NET define non-instance class members with the keyword “shared”, which has absolutely no “have to be resolved at compile time” connotation.

  • Last time I pointed out that static methods are always determined exactly at compile time, and used that

Page 2 of 2 (25 items) 12