Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to constrain a C# generic method type parameter as "assignable from" the containing class' type parameter?

I suspect the answer is no, but I want to know if it is possible to do something like this:

public class MyGenericClass<TSomeClass> {
    public void MyGenericMethod<TSomeInterface>() 
        // This doesn't compile.
        where TSomeClass : TSomeInterface 
    {
        //...
    }
}

What I mean to indicate in the above (non-working) example is to constrain TSomeInterface such that it can be any base class, implemented interface, or (if you really want to get fancy) implicit conversion of MyGenericClass.

NOTE: I suspect that the reason why this was never implemented in C# is that generic constraints are not really meant to be code contracts, which is how I am trying to use them here. I really don't care what type TSomeInterface is, so long as it is implemented by TSomeClass.

So far, I have hacked this together:

public class MyGenericClass<TSomeClass> {
    public void MyGenericMethod<TIntermediateType, TSomeInterface>() 
        where TIntermediateType : TSomeClass, TSomeInterface 
    {
        //...
    }
}

This more or less enforces the constraint that I want (that TSomeClass must inherit from, or in the case of an interface, implement, TSomeInterface), but calling it is very clumsy, because I have to specify TIntermediateType (even though I really want it to evaluate against TSomeClass):

var myGenericInstance = new MyGenericClass<TSomeClass>();
myGenericInstance.MyGenericMethod(TSomeClass, TSomeInterface);

Additionally, the above hack is broken because a caller could in theory specify a subclass of TSomeClass as the first type parameter, where only the subclass implements TSomeInterface.

The reason that I want to do this is that I am writing a fluent factory pattern for a WCF service, and I would like to prevent the caller (at compile time) from trying to create an endpoint with a contract that the service class doesn't implement. I can obviously check this at runtime (WCF in fact does this for me), but I am a big fan of compile-time checking.

Is there a better/more elegant way to achieve what I am after here?

like image 400
Chris Shain Avatar asked Jun 29 '12 03:06

Chris Shain


2 Answers

The way I was able to wrap my head around the reason why this doesn't compile is the following:

Consider this program compiles:

class Program {
    class Class1 { }
    class Class2 { }
    public class MyGenericClass<TSomeClass> {
        public void MyGenericMethod<TSomeInterface>() where TSomeClass : TSomeInterface {
        }
    }
    static void Main(string[] args) {
        var inst = new MyGenericClass<Class1>();
    }
}

Everything is good. The compiler is happy. Now consider I change the Main method:

static void Main(string[] args) {
    var inst = new MyGenericClass<Class1>();
    inst.MyGenericMethod<Class2>();
}

The compiler will complain that Class1 does not implement Class2. But which line is wrong? The constraint is on the call to MyGenericMethod, but the offending line of code is the creation of MyGenericClass.

In other words, which one gets the red squiggly line?

like image 124
ken Avatar answered Sep 29 '22 12:09

ken


As discussed in this linked question, you can't use a type parameter that isn't from the current declaration, on the left side of a where clause.

So as suggested by w0lf in that other question, what you can do is provide both types in your interface (rather than method) declaration:

public class MyGenericClass<TSomeClass, TSomeInterface> {
    where TSomeClass : TSomeInterface 
    public void MyGenericMethod() // not so generic anymore :( 
    {
        //...
    }
}

That, however, greatly limits your MyGenericMethod and forces your class to declare before-hand what base interface you with to allow.

So another option is to use a static method with more type parameters:

public class MyGenericClass<TSomeClass> {
    public static void MyGenericMethod<TSomeClass, TSomeInterface>
                                         (MyGenericClass<TSomeClass> that) 
        where TSomeClass : TSomeInterface 
    {
        // use "that" instead of this
    }
}

Possibly you could make it an extension method to make it appear to the user like an actual method.

Neither of these is exactly what you wanted, but probably better than the intermediate type solution.

As for the reason for why not?, my guess is that it would complicate the compiler without adding enough value. Here's a discussion by Angelika Langer of the same subject but about Java. Although there are significant differences between C# and Java, I think her conclusion might apply here as well:

The bottom line is that the usefulness of lower bounds on type parameters is somewhat debatable. They would be confusing and perhaps even misleading when used as type parameters of a generic class. On the other hand, generic methods would occasionally profit from a type parameter with a lower bound. For methods, a work-around for the lack of a lower bound type parameter can often be found. Such a work-around typically involves a static generic method or a lower bound wildcard.

She also gives a nice use case, see the link above.

like image 36
sinelaw Avatar answered Sep 29 '22 12:09

sinelaw