Click here to Skip to main content
15,881,516 members
Articles / DevOps / Testing

C# Generic Type Generator

Rate me:
Please Sign up or sign in to vote.
4.56/5 (10 votes)
31 May 2012CPOL6 min read 62.2K   837   37   10
Combinatorial generation of C# generic types.

Introduction

I'm posting some C# code that builds all possible combinations of a generic type. The user specifies a generic type with unresolved generic parameters (e.g., Tuple< , , >), and also specifies available types to build with (e.g., int, string). The GenericTypeGenerator class then returns all possible types that can be built.

In the example I've just given, GenericTypeGenerator will return types for the following:

C#
Tuple<int, int, int>
Tuple<int, int, string>
Tuple<int, string, int>
Tuple<string, int, int>
Tuple<string, string, string>
Tuple<string, string, int>
Tuple<string, int, string>
Tuple<int, string, string>

Background

Although the introductory example was fairly trivial, the code necessary to solve the general problem is not. However, anyone with a basic understanding of .NET Generics should be able to examine and modify the code.

Motivation

My motivation to create this utility was improved testing. While working on a C# application, I decided to use the Policy design pattern. Although I could have used the Strategy design pattern, I felt policies were the best approach at the time. Strategies are typically constructor arguments passed to a strategy host. In C++, policies are typically template arguments applied to a policy host; in C#, I, of course, use the analog to templates, which are Generics.

Here are a few reasons you might favor policies (via generic arguments) over strategies (via constructor arguments):

  • You want to preserve the policies' actual types at compile-time. This could make the code more usable and efficient. This is also necessary if you want to support "enriched policies". Enriched policies are regular policies exposed publically by the policy host. They can contain additional functionality the client will want but the host has no awareness of.

  • You want to leverage generic constraints. Multiple constraints per policy and inter-policy naked constraints, each arbitrarily specified per policy host, are best expressed through Generics.

  • You want to formally delegate lifetime management concerns for each policy to the policy host.

  • You don't want to accept the risk of null constructor parameters.

  • The Policy design pattern can be applied just as easily to static classes.  Because a static class's constructor is not manually invoked, the Strategy design pattern is somewhat awkward when applied to a static class.

If none of these reasons seem relevant to your application, and they probably won't most of the time, strategies may be a superior choice. One specific reason you might favor strategies over policies is the ugly look and somewhat laborious typing involved in supporting multiple generic parameters on a class. In fact, FxCop even has a rule for this!

Getting back to the point, imagine a C# policy host containing four policy types:

C#
public interface IMyGame 
{ 
    int MyTestableMethod(); 
}
 
public class MyGame<TDisplayPolicy, TAudioPolicy, 
             TMemoryPolicy, TDebugPolicy> : IMyGame
    where TDisplayPolicy : IDisplayPolicy
    where TAudioPolicy : IAudioPolicy
    where TMemoryPolicy : IMemoryPolicy
    where TDebugPolicy : IDebugPolicy 
{ 
    public int MyTestableMethod() 
    {
        // Assume the functionality of this method is dependent on one or more 
        // of the policy types. Assume it may even be dependent on the 
        // *interaction* between the policy types.  (Note that this scenario is only
        // realistic if the policies have class or interface constraints that we can
        // work with here in this method.)
        return 0; 
    }
}

As part of my test plan, I would like to run tests on each version of MyGame, where MyGame would vary based on the different policies passed to it as generic arguments. In this particular example, I would specifically like to test MyTestableMethod. However, if each of the policy types had just four variants (e.g., MyDisplayPolicy1, MyDisplayPolicy2, MyDisplayPolicy3, MyDisplayPolicy4), the number of possible MyGame classes would be 256! Testing 256 classes is unmanageable through manual coding. We would need a combinatorial testing approach.

My overall goal would be to somehow create all 256 types, make instances of those types through Reflection, and then cast those instances to a simple, yet testable, interface that doesn't vary based on the generic parameters in question (in our example, this interface is IMyGame). I could then run a series of uniform tests on that interface.

At first glance, it appears I can just set up four nested for loops and create the 256 types without much thought. But remember - policies may also be generic types, and they can be arbitrarily composable with each other:

C#
public class MyDisplayPolicy1<TMemoryPolicy, TDebugPolicy> { } 
public class MyAudioPolicy1<TMemoryPolicy, TDebugPolicy> { } 
public class MyMemoryPolicy1<TDebugPolicy> { }

The complexity doesn't end there. Consider arbitrary generic constraints:

C#
public class MyDisplayPolicy2<TMemoryPolicy, TDebugPolicy>
    where TMemoryPolicy : IMemoryPolicy<TDebugPolicy>, 
                          IDisposable, 
                          IMyInterface1<TDebugPolicy, string>,
                          IList<IMyInterface2>
{
}

We won't be able to solve this problem with four for loops after all.

This problem can be presented very similarly either in terms of generic arguments or constructor arguments. Regardless of which way it's presented, it has plenty of complexity (with similarities to the Knapsack problem). When presented in terms of generic arguments, there are unique concerns that must be addressed. This code sets out to address those concerns while solving the general problem.

Using the Code

The code uses C# 4.0 and .NET 4.0 libraries. To date, I've only used and tested it in a Windows environment within Visual Studio 2010.

I'm also including some auxiliary code that may be of use to anyone experimenting with GenericTypeGenerator.

There is a very useful test suite called TestApi. It has an excellent combinatorial test library, but, as of yet, it does not support the combinatorial testing of generic types. I recommend using TestApi for combinatorial testing in general. In fact, I use it to test certain aspects of this code (this dependency is very light, and can be easily severed if desired). In other applications where there are generic and non-generic combinatorial testing requirements, I use GenericTypeGenerator in conjunction with TestApi; they work together easily.

In terms of the public interface, here are the simplest methods:

C#
static public class GenericTypeGenerator
{
    static public IEnumerable<Type> BuildTypes(
        Type typeToBuildFrom,
        IEnumerable<Type> availableTypes,
        ParallelOptions parallelOptions = null);

    static public IEnumerable<Type> BuildTypes(
        Type typeToBuildFrom,
        IDictionary<Type, int> availableTypeToTimesUsableMap,
        ParallelOptions parallelOptions = null)
}

Using the simplest method would look something like this:

C#
foreach(Type builtType in GenericTypeGenerator.BuildTypes(typeof(MyClass<,,>),
                                                        new Type[]
                                                        {
                                                                typeof(int),
                                                                typeof(float),
                                                                typeof(string),
                                                                typeof(MyClass1),
                                                                typeof(MyClass2),
                                                                typeof(MyClass3<double>),
                                                                typeof(MyClass4<,>)
                                                        }))
{
    // Do something with builtType.  For example, create an instance of it with
    // reflection and cast to a testable interface…
    
    // Reflection methods will return an object.  It won't be possible to cast this
    // object to an instance of its concrete type since that type's generic arguments
    // will vary.
    object myClassObj = Activator.CreateInstance(builtType);

    // However, we can cast this object to a relatively simple interface.
    IMyClass myClassInterface = (IMyClass)myClassObj;

    // We can now run tests on this interface.
    Assert.IsTrue(myClassInterface.MyMethodThatShouldReturnTrue());
    Assert.AreEqual(myClassInterface.MyMethodThatShouldReturn11(), 11); 
}

By default, non-generic types and generic types with resolved generic parameters can be used any number of times within a single built type. However, generic types with unresolved generic parameters can only be used once (or as many times as specified in the enumerable of available types). Without this default rule, it would be too easy to have infinite recursion.

As you can see, BuildTypes has an overload. With it, the client can specify how many times an available type is usable in a single built type. -1 indicates no limit. Additional methods in the public interface return built types along with details on what types were used to build them.

An easy way to see how GenericTypeGenerator works is by looking at the code that tests it. Here is what one test looks like:

C#
[TestMethod]
public void Test_CyclicalNakedConstraints()
{
    VerifyBuiltTypes(
        typeof(CyclicalNakedConstraints<,>),
        new Type[] 
        { 
            typeof(CyclicalNakedConstraints_TImpl),
            typeof(CyclicalNakedConstraints_UImpl),
            typeof(CyclicalNakedConstraints_TAndUImpl)
        },
        new Type[] 
        {
            typeof(CyclicalNakedConstraints<CyclicalNakedConstraints_TImpl, 
                                            CyclicalNakedConstraints_UImpl>),
            typeof(CyclicalNakedConstraints<CyclicalNakedConstraints_UImpl, 
                                            CyclicalNakedConstraints_UImpl>),
            typeof(CyclicalNakedConstraints<CyclicalNakedConstraints_TAndUImpl, 
                                            CyclicalNakedConstraints_TAndUImpl>)
        });
}

The first parameter is the type to build, the second parameter is the available types, and the third parameter is the expected built types. Included are plenty of tests that illustrate what results to expect and what issues to watch out for.

Points of Interest

Here's a portion of a test that accompanies the code. Besides being part of a test, it also serves to illustrate a particular point.

C#
private class T_U_tripleNestedIDictionary<T, U> 
        where U : IDictionary<T, IDictionary<IDictionary<T, object>, T>> 
{ 
} 

IEnumerable<Type> builtTypes = 
    GenericTypeGenerator.BuildTypes( 
        typeof(T_U_tripleNestedIDictionary<,>), 
        new Type[] 
        { 
            typeof(Func<>), 
            typeof(IDictionary<,>), 
            typeof(IDictionary<lt;,>), 
            typeof(IDictionary<,>), 
            typeof(object), 
            typeof(float) 
        });

Take a rough guess at how many types this produces, as well as how many unique types have to be evaluated by the underlying algorithm.

Returned: 2
Evaluated: 750+

It wouldn't take much more for the evaluated types to top 10,000. Know that it can be costly to specify available types that are composable with each other.

History

  • 19 June, 2010
    • Initial post.
  • 9 July, 2010
    • Added more tests.
    • Refactored some existing tests to achieve finer test granularity.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States

Comments and Discussions

 
QuestionNice but memory hungry Pin
Alois Kraus31-May-12 10:50
Alois Kraus31-May-12 10:50 
GeneralUse cases Pin
Dmitri Nеstеruk25-Jun-10 11:27
Dmitri Nеstеruk25-Jun-10 11:27 
GeneralRe: Use cases Pin
Alexander Van Berg26-Jun-10 4:35
Alexander Van Berg26-Jun-10 4:35 
QuestionHow can we contact you Pin
Vince Ricci23-Jun-10 4:57
Vince Ricci23-Jun-10 4:57 
AnswerRe: How can we contact you Pin
Alexander Van Berg23-Jun-10 17:20
Alexander Van Berg23-Jun-10 17:20 
GeneralAmazing Piece of Work Pin
santosh poojari23-Jun-10 2:17
santosh poojari23-Jun-10 2:17 
GeneralRe: Amazing Piece of Work Pin
Alexander Van Berg23-Jun-10 17:19
Alexander Van Berg23-Jun-10 17:19 
QuestionForest through the trees [modified] Pin
Josh Fischer21-Jun-10 4:52
Josh Fischer21-Jun-10 4:52 
AnswerRe: Forest through the trees Pin
Alexander Van Berg21-Jun-10 9:11
Alexander Van Berg21-Jun-10 9:11 
GeneralRe: Forest through the trees Pin
Josh Fischer21-Jun-10 12:11
Josh Fischer21-Jun-10 12:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.