I'm wondering if C# has an equivalent to Java's <X super MySubClass>
generic constraint.
To specify an upper bound, one can use class Foo<T> where T : MySuperClass { ... }
, but how can we specify a lower bound on the generic parameter?
There are some ways to get similar results, but I haven't found something perfect yet:
Use a second generic parameter — but the caller could specify a subclass of the actual lower bound.
public class Foo<T, TLowerBound>
where TLowerBound : MySubClass
where TLowerBound : T
{
...
}
This is sometime used on extension methods so the extension method's parameter U
is constrained to be a superclass of the class' parameter T
.
public static class Extensions {
public static void Method<T, U>(this Foo<T> self, U someU) where T : U {
self.ValueOfTypeT = someU;
}
}
Use variance on interfaces, but I'm not sure if this can be used to specify a lower bound on a generic parameter.
I have just faced the same issue. Option 2 (using extension methods) works quite well until you need the method with the lower bound to be virtual (and thus dispatched based on the dynamic type of the object). If you need that, here is a viable solution using Option 3 (variance on interfaces, plus the well known visitor pattern).
In order to achieve an equivalent of
public class A<T> // an argument to the generic method
{
}
public class B<S>
{
public virtual R Fun<T>(A<T> arg) where S : T // illegal in C#/CLR
{
...
}
}
public class C<S> : B<S>
{
public override R Fun<T>(A<T> arg)
{
}
}
you do the following. First, you define an interface for the operation to be performed (we will be using visitor pattern here, and thus there has to be a separate method for each type overriding Fun
):
public interface IFun<in T>
{
R Fun<S>(B<S> self) where S : T;
R Fun<S>(C<S> self) where S : T;
}
Note, that the generic parameter T
is only used as a constraint, and therefore the interface can be contravariant with respect to it. We now use this, letting B
and C
be "visited" by the operation:
public class B<S>
{
public virtual R Perform(IFun<S> fun)
// contravariant, any IFun<T> with S : T will be accepted
{
return fun.Fun(this);
}
}
public class C<S> : B<S>
{
public override R Perform(IFun<S> fun)
{
return fun.Fun(this);
}
}
In order to actually perform the operation with an argument A<T>
, you wrap it in a struct/class implementing the interface:
public struct TheFun<T> : IFun<T>
{
public A<T> arg;
R IFun<T>.Fun<S>(B<S> self)
{
... body of B<S>.Fun(A<T> arg) ...
}
R IFun<T>.Fun<S>(C<S> self)
{
... body of C<S>.Fun(A<T> arg) ...
}
}
To close up, you introduce an extension method, like in Option 2:
public static class Extensions
{
public static R Fun<S,T>(this B<S> self, A<T> arg) where S : T
{
return self.Perform(new TheFun<T> { arg = arg });
}
}
Done. It works, without a single cast or type check. The main drawbacks are:
B
and C
to TheFun
, and thus any required members of B
and C
have to be made accessible thereIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With