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<T>
'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<T>
example I give here does indeed generate a compiler error. But I can't see how Nullable<T>
restricts the T
to non-Nullable<T>
types - since Nullable<T>
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<T>
:
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<T>
:
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?
About Nullable<T>
: The constraint T : struct
is the reason for the compiler error, because a variable of a struct can never be null, whereas a variable of type Nullable<T>
can be null.
So, it is a compiler hack but only in that way, that Nullable<T>
is treated like a nullable value type instead of a non-nullable value type.
public struct Nullable2<T> where T: struct
{
}
// Both lines will generate the same error
Nullable2<Nullable<int>> v;
Nullable<Nullable<int>> v2;
What I want to say is the following:Nullable<T>
doesn't do a "'negative assertion' generic constraints".
If 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