Parametric polymorphism (aka Generics) is one of the most interesting new features added to .NET CLR and languages in version 2.0 (Whidbey). There are many good articles describing the feature and its’ implementation for the full .NET CLR.  For those who interested in the design fundamentals of generics I’d strongly recommend the article by Don Syme and Andrew Kennedy from Microsoft Research, who originally designed and implemented .NET generics.

 

Many people were asking what .NET Compact Framework generics story is.  Although generics were not included into our Whidbey Beta 1 release, I’m very excited to confirm that .NET Compact Framework version 2.0 will also include generics support. Some people may have already had a chance to play around with .NET Compact Framework generics (latest Visual Studio 2005 Community Technical Preview Releases are available to MSDN subscribers).

 

Here is a quick introduction into the feature from Seth Demsey.

 

As I was actively involved into generics work for a past year or so, I’d like to share some details about specifics of .NET Compact Framework approach to generics support, underline some limitations and differences from the full .NET CLR approach. 

 

What to expect?

First of all, it’s important to understand that generics implementation goes deep into the CLR. Unlike C++ templates, generics is not just a compile-time concept, it’s an integral part of the type system, reflection, debugging and so on.

Our main goal for this release is providing core language feature compatibility with C# and VB. Generics quickly gain popularity in the .Net community, customers love them. So, it became pretty clear that we couldn’t ship the product without generics support. As usual, we had to make certain trade-offs to minimize the overall impact on the rest of the runtime. We identified and targeted the subset of generics functionality which is absolutely necessary for C# and VB runtime support. We don’t position generics as a “performance feature” for this release, as typical usage patterns remains largely unknown. We’d definitely benefit from the customer feedback on generics usage in .NET Compact  Framework apps, which would allow us to introduce targeted optimizations.

 

That said (I hope I set everyone’s expectations accordingly), the results of our work look pretty promising. We were able to provide a fairly complete generics story which includes:

-          C#/VB language and runtime compatibility in regards to generics, with only a few limitations listed below. Both generic types and methods are supported. All kinds of constraints are accepted (see note on constraints in the next chapter).

-          Full port of Base Class Library generic collections classes, Nullable<T>, functional programming elements (delegate-based APIs), etc.

-          Limited reflection support. We only implemented a subset of reflection functionality for generics, which is needed by C#/VB runtime.

-          Full debugger support.

 

 

Limitations

-          Expansive generic recursion through fields is not supported (for both value types and reference types):

class Foo<T>

{

Foo<Foo<T>> field;

       }

As far as I can tell, this pattern does not have any practical use.  This will compile (if Foo is a reference type), but will result in a load time exception against .NET Compact Framework.

To disallow this pattern and avoid infinite loading sequence, we impose a limit of 1024 unique closed constructed types per generic type declaration. Other kinds of generic recursion, which are commonly used in practice, are fully supported by .NET Compact Framework, such as:

             class C : IComparable<C>

             class C<T> : IComparable<C<T>>

 

-          No support for variance modifiers. Though variance/co-variance forms part of the overall ECMA spec for generics, and is implemented for the full .NET CLR, it is not used in Base Class Library or by C# and VB.

 

-          .NET Compact Framework does not perform IL verification and metadata validation on device. Thus, we don’t validate constraints for normal execution (we assume constraints are relevant to verification). So, in the absence of verifier, constraints will not be validated by the loader, except for reflections scenarios. Reflection will still verify the constraints when binding to generic parameters. This means that all kinds of constraints can be used in the code; they will be validated at the compile time, but not at the runtime (except for reflection scenarios).

 

-         To minimize the working set impact, we may limit the number of type parameters for generic types/methods to 8. Please note that C# allows type parameters from an enclosing class to be used in a nested class, but compiles it away by adding extra type parameters to the nested class definition. For example the following C# generic type declaration:

      class Foo<T, U>

      {          

                  class Bar<V> {}

      }

      will be transformed by the compiler into:

       Foo`2<T,U>

       |     .class public auto ansi beforefieldinit

       |     Bar`1<T,U,V>

       |   |     .class nested public auto ansi beforefieldinit

       |   |___     

       |___

       Thus the number of type parameters of the nested type includes the numbers of type parameters of all enclosing types. This should not exceed the limit either.

 

-         Reflection functionality is limited for generics, in particular for generic type or method declarations, open (uninstantiated) generic types/methods and formal type parameters. Examples of unsupported functionality for open types:

Type.GetField, Type.GetMember  Type.GetMethod, Type.GetProperty , etc. But in any case, there is not much you can do with a member of an open type, even if you could reflect on it. Using reflection methods, which are presently in .NET CF Base Class Library, but don’t work with generics will result in NotSupportedException being thrown at the runtime.

 

.NET Compact Framework team strives to improve ECMA standard compliance of our product with every new release. As generics were introduced into the standard, we provided a comprehensive generics support in .NET Compact Framework v2 product. From the runtime perspective .NET Compact Framework supports most of the generics features, specified in the standard with only a few minor limitations listed above. Please note that these limitations were introduced only due to time and resource constraints for this particular release of the product, based on requirements prioritization. We’ll continue to work to fill these gaps in the future releases of our product.

 

Implementation insight

There are a few details about internal implementation of generics in .NET Compact Framework, which may be valuable to know to use generics more efficiently in your application.

 

There are a couple of approaches we could have taken to introduce generics into the product:

-          Representation and code sharing. This implies sharing of execution engine data structures and JITed code between different instantiations of generic types/methods. We were considering boxing all valuetype type arguments and thus uniformly sharing JITed code (generating only one copy of the code). However, this approach requires a fare amount of JIT work to transform IL-sequences and does not make life much easier after all. It also introduces a perf hit, due to boxing/unboxing overhead for valuetypes, which we are trying to avoid in the first place.  This approach would undermine one of the main competitive advantages of .Net generics against Java generics – the execution efficiency, as Java compiler substitutes type parameters with Object, resulting in boxing/unboxing overhead.

-          Representation and code specialization – each distinct instantiation of generic type results in a separate execution engine data representation and JITed code specific to that instantiation. The CLI does not support partial instantiation of generic types and generic types may not appear uninstantiated anywhere in metadata signature blobs. Considering this fact, fully instantiated (closed) generic types and methods, are not very different from concrete types/method and can be represented by existing internal data structures with minimal modifications. This implementation can be improved in the future to enable some degree of JITed code sharing.

Full CLR’s approach is a combination of sharing and specialization.  Data structures for different instantiations of generic types/methods are duplicated, but they share JITed code between compatible instantiations, where possible.

 

.NET Compact Framework implementation is going to be fully specialized for this release. What does this mean for developers? If I have a generic type declaration:

class Foo<T>

{

            public static T field;

            public void bar(){...};      

            public static void baz(T arg) {…};

}

and somewhere in my code I create 4 different instantiations of this generic type:

Foo<int> f1 = new Foo<int>();

Foo<double> f2 = new Foo<double>();

Foo<MyType>  f3 = new Foo<MyType>();

Foo<Object> f4 = new Foo<Object>();

4 different internal execution engine data structures will be created in memory. It’s the same as loading 4 completely different types from the working set perspective. Note that this has nothing to do with the number of objects created – only the number of different unique closed constructed types loaded matters. As with regular, non-generic types, I don’t have to create an object to trigger loading (creating the internal execution engine data structure of the generic type), it’s sufficient to reference one in the code, for example by accessing a static member (Foo<int>.baz(5) or Foo<int>.field = 5).

 

Now, if I invoked method bar() or baz() at least once for each closed constructed type, it would be JITed 4 times and 4 potentially different pieces of JITed code will live in memory – it doesn’t matter if type arguments are value types, reference types or would be considered compatible by full .NET CLR.

The same is true for generic methods:

class Foo

{          

            public void bar<T>(T arg){…};

            public static void baz<T>(T arg) {…};

}

The first time closed constructed generic method is referenced in the code, unique internal execution engine data structure is created to represent it. So, if I have code like this:

            Foo.baz<int>(5);

            Foo.baz<string>(“Hello”);

            Foo.baz<MyClass>(new MyClass());

3 internal data structures get created, method baz() will be JITed 3 times and 3 instances of the JITed code  will live in memory.

Please refer to Mike Zintel’s article for more details on .Net Compact Framework Memory Management. 

 

Thus, when using generics one needs to be aware of the potential JITed code size impact. If there is very large number of closed constructed types/methods per generics type/method definition, app may start experiencing a JITed code size pressure. On the positive side, specialized JITed code is typically more efficient as exact type parameter information is always easily accessible.

 

 

This posting is provided "AS IS" with no warranties, and confers no rights.