Calling static methods on type parameters is illegal, part two

Calling static methods on type parameters is illegal, part two

  • Comments 11

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.

  • Interesting exposé on the implementation of generics -- although in my mind you didn't actually explain why option 1 from last time was chosen until the end. How the JIT works is an implementation detail -- if you'd wanted option 3, you could have implemented the JIT differently. The key seems to me to be type safety. Clearly this should be illegal under option 3, if E<D> is used:

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

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

    public class E<T> where T : C { public static void N() { T.M(); } }

    Based on the type-safety argument, any user of E<D> should be diagnosed at compile-time rather than at JIT-time, so the above must be rejected (I assume that it's unreasonable for the JIT to fail due to type-safety issues). And line 2 may be in someone else's code, so line 1 + line 3 must be rejected.

    In C++0x there will be 'concepts' which are similar in spirit to 'where' clauses in C#, and introduce the type-safety property for templates you describe. I've not checked in detail in the working paper, but I believe this allows one to do this sort of thing:

    auto concept Mable<T> { T::M(); };

    struct C { static void M() { /*whatever*/ } };

    struct D { static void M(int i) { /*whatever*/ } };

    template<Mable T> class E { public: static void N() { T::M(); } };

    Then writing E<D> is a compile-time error, diagnosed immediately and not at template-instantiation-time. C# could presumably do the same thing.

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

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

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

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

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

  • bmm60 - it depends on what problem you have with reflection. if it's performance, you can often just create delegates and make sure you only have to create them once per type.

    note that you can create a "static" delegate for instance methods by putting the hidden "this" parameter into the signature:

    myDelegate = CreateDelegate (typeof (Func<MyType,string>), typeof (MyType), "ToString")

    you can call it like that:

    MyType obj = ...

    string result = myDelegate (obj)

    so you avoid the overhead of creating the delegate for each instance of MyType

    If you use this a lot in your code, that might get a bit hard to read and maintain. You might want to use dynamic interfaces in this case (the feature that was recently taken out of vb 9, but it's quite easy to build it yourself if you're comfortabel with reflection.emit)

    interface IMyInterface {

     void foo();

    }

    class MyClass { // does not implement IMyInterface!!

     public void foo();

    }

    MyClass obj;

    IMyInterface iface = CreateDynamicInterfaceProxy<IMyInterface> (obj);

    iface.foo();

    CreateDynamicInterfaceProxy would actually have to

    - create (and cache) a class that implements IMyInterface and forwards all calls to a wrapped instance of MyClass

    - instantiate the wrapper class with the passed object

    it's not hard to do, but it's a bit of work if you want to support not only methods, but also properties and events. drop me a mail at stefan dot wenig at rubicon-it dot com if you actually need this, I have sth like that around here somewhere...

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

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

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

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

Page 1 of 1 (11 items)