I started this question with a load of background of the types in question; the interfaces and rationale behind the architecture.
Then I realised - 'This is SO - keep it simple and get to the point'.
So here goes.
I have a class like this:
public class AGenericType<T> : AGenericTypeBase
{
T Value { get; set; }
}
.Net then, of course, allows me to do this:
AGenericType<AGenericType<int>> v;
However, in the context of AGenericType
's usage, it is a nonsense to do this in the same way that it's a nonsense to do this:
Nullable<Nullable<double>> v;
What I want to be able to do is restrict this generic type so that it becomes impossible to create such an instance or even declare a reference to the type when it's T
is derived from AGenericTypeBase
- preferably at compile-time.
Now an interesting thing here is that the Nullable
example I give here does indeed generate a compiler error. But I can't see how Nullable
restricts the T
to non-Nullable
types - since Nullable
is a struct, and the only generic constraint I can find (even in the IL, which often yields compiler secrets, like those with delegates) is where T:struct
. So I'm thinking that one must be a compiler hack (EDIT: See @Daniel Hilgarth's answer + comments below for a bit of an exploration of this). A compiler hack I can't repeat of course!
For my own scenario, IL and C# don't allow a negative-assert constraint like this:
public class AGenericType<T> : where !T:AGenericTypeBase
(note the '!' in the constraint)
But what alternative can I use?
I've thought of two:
1) Runtime exception generated in the constructor of an AGenericType
:
public AGenericType(){
if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
throw new InvalidOperationException();
}
That doesn't really reflect the nature of the error, though - because the problem is the generic parameter and therefore the whole type; not just that instance.
2) So, instead, the same runtime exception, but generated in a static initializer for AGenericType
:
static AGenericType(){
if(typeof(AGenericBase).IsAssignableFrom(typeof(T)))
throw new InvalidOperationException();
}
But then I'm faced with the problem that this exception will be wrapped inside a TypeInitializationException
and could potentially cause confusion (in my experience developers that actually read a whole exception hierarchy are thin on the ground).
To me, this is a clear case for 'negative assertion' generic constraints in IL and C# - but since that's unlikely to happen, what would you do?