Freigeben über


Calling static methods on type parameters is illegal, part two

Last time I pointed out that static methods are always determined exactly at compile time, and used that fact to justify why static methods cannot be called on type parameter types. But aren’t the type arguments to generics actually determined at compile time?

On the caller side they are, sure. But on the callee side, the code emitted at compile time for a generic method is entirely generic. It is not until the jitter encounters the code at runtime that the substitution of type arguments for type parameters is done.

Consider a generic type:

public class C<T> { public string M() { return typeof(T).ToString(); } }

When you compile that, the compiler emits a generic class definition which says just that – that we have a class, it has a type parameter, and a method which calls ToString(). That is all the code which is emitted for this class at compile time.

Let me make sure that is clear. When you say

void N() { C<int> c = new C<int>(); string s = c.M(); //...

the compiler does not emit a copy of the class’s IL with int substituted for T.

Rather, what happens is when your method N is jitted, the jitter says hey, I need to jit up C<int>.M. At that point the jitter consumes the generic IL emitted for C<T>.M and creates brand new x86 (or whatever) code with int substituted for T.

Contrast this with C++ templates. C++ templates do not define generic types. Rather, C++ templates are basically a clever compile-time syntax for some complex search-and-replace macros. If you say C<int> in C++, then at compile time the C++ compiler textually substitutes int for T and emits code as if that was how you had written it in the first place.

If C# had templates instead of macros then a static method called on a template parameter really would be determined at compile time, because the entire constructed class would be resolved at compile time. In this sense templates are a more powerful mechanism than generics – you can do crazy things with templates because there is no type safety imposed upon the template as a whole. Rather, the type safety is only checked for every construction of the template actually in the program.

But C# generic types are not templates; they must be typesafe given any possible construction which satisfies the constraints, not just under the set that they are actually constructed from in a particular program.

Next time I'll consider what impact these design decisions have on non-virtual instance methods called from within generic types.

Comments

  • Anonymous
    June 18, 2007
    The comment has been removed

  • Anonymous
    June 18, 2007
    Eric, this is only true for value types; if you wrote C<string> instead, the call to M() could not be resolved statically even at JIT time, right? Did you read my comments on your part 1 post? Commenting would be so much more fun if they'd be answered ;-)

  • Anonymous
    June 18, 2007
    BTW, while symbols in C++ templates are resolved at template instantiation time, the AST is built from the template AFAIK. Hence the need for the "typename" keyword to identify nested types. So there's a bit more to it than text replacement. (I guess when the AST is built depends on the compiler implementation, but syntax errors should be generated from the template definition, and precompiled headers wouldn't be too usefule either without any preliminary parsing...)

  • Anonymous
    June 18, 2007
    I don't seem to unserstand the part that you want to ensure the staticness of a method when/after running csc.ese. Why does that matter? The code is never executed until after the JIT runs and the jit "instantiates" generics for each type separately (that is why it blows up the size of the code). And when you agree that 3.) would be possible after running the JIT, what is the reason for saying that 3.) technically is not possible? I don't get the argumentation. secondly: How many people actually asked for this feature? Seems pretty useless to me ...

  • Anonymous
    June 18, 2007
    OK, everybody is probably going to ignore this too, but this is authorative: http://blogs.msdn.com/joelpob/archive/2004/11/17/259224.aspx I think we should be able to agree (1) that the static method cannot be resolved at either C# compile time nor at JIT time, and (2) that the hidden argument (TypeHandle, directly or via MethodDesc) could technically be used to build a vtable for overridden class methods by either the CLR or the C# compiler.

  • Anonymous
    June 18, 2007
    Thanks for the explanation. Is there a recommended idiom to get behavior more like C++ templates?  I've been working with a library that has classes with identical method signatures, but which are not part of an interface.  I tried writing C# generic code to call these methods, but ran into the problem you describe here.  I ended up using reflection, but was wondering if there's a more sophisticated solution.

  • Anonymous
    June 18, 2007
    The comment has been removed

  • Anonymous
    June 19, 2007
    Stefan: Thanks for the dynamic interface idea.  I don't really have a problem with reflection, I just was wondering if there was a more elegant solution to the problem.

  • Anonymous
    June 20, 2007
    I've been wondering about some possibilities of implementing a VS designer which allows mixin support via code generation, attributes and partial classes. It occurs to me that the basis for this could be mixin code units (.cs) which define classes which can themselves be parsed and validated (everything but actual IL generation) and that these mixin classes could define constraints via the base classes and interfaces which it inherits and could make downstream calls on subclasses via the virtual/abstract methods it defines. The class's which are to use the mixins would have attributes which define the actual base class for each mixin and the interfaces.  The designer would then generate a code unit which combines the user's class with the mixin via generation of a new mixin class (probably following some naming convention like classname_mixinclassname) which inherits the actual class specified by the attribute and additionally the generation of a partial class definition which declares the user-defined class to derive from the generated mixin class. Anyone any thoughts on this? The thing I kind of like about this unlike most code generators I've seen is that it has the advantage of compiler validation of the mixin class and the ability to enforce inheritance constraints. It does of course have limitations, as you cannot do string substitution.  However, there may even be ways of doing some limited substitutions via the addition of more attributes.

  • Anonymous
    June 20, 2007
    Welcome to the XXVIII Community Convergence. In these posts I try to wrap up events that have occurred

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