Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent a particular hierarchy of types being used for a generic parameter

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?

like image 994
Andras Zoltan Avatar asked Oct 12 '22 00:10

Andras Zoltan


1 Answers

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".

like image 75
Daniel Hilgarth Avatar answered Oct 15 '22 10:10

Daniel Hilgarth