Generics constraint for nullable or reference types

S

Sam Kong

Hello,

I want to define a generic class which should accept only nullable
types or reference types.
What's the best way to costrain it?

---------

class MyClass<T>{
...
}

---------

T must be either nullable type or reference type.

int? is ok.
string is ok.
int is not ok.

Thanks in advance.

Sam
 
M

Marc Gravell

Well, you can't do it easily - there is no single constraint that would
help here. Is there any way you can split the code out into 2 blocks,
one for class, one for Nullable<T>? You would still need to use
Default<T> for the "null", since the (class) null-reference and
Nullable<T> "null" are IIRC quite different with the compiler making
them look the same(Nullable<T> being a struct).

Marc
 
A

Andy Bates

You can constrain what types your class can be instantiated with by using
the where clause as in:

class MyClass<T> where T : class

I cannot see an obvious way to constrain the type to being a Nullable type
as int?, Nullable<int> etc. all appear to be invalid in the where clause
above!

- Andy
 
A

Andy Bates

I've done some more digging and the problem is that Nullable is actually a
struct. Accordingly templates can only be derived from interfaces,
non-sealed CLASSES and types. struct's cannot be used as base classes; full
stop...

You may be able to define your own class which implements the methods in
line with how the Nullable struct operates; it basically adds a boolean to
each type to indicate whether the value has been assigned or not... I cannot
see any reason why
this will not work but you may not be able to use the short hand form; i.e.
type? you may have to use MyType.Nullable<int> myInt... as I would assume
that the short hand form would be bound to System.Nullable (could be wrong
:).

I cannot think of any reason why this will not work but unless I actually
tried to write the code could not guarantee it would be trouble free. I also
cannot think of any reason why Nullable is implemented as a struct aside
from a value type performance angle.

If you wrote your own Nullable class then:

class MyClass<T> where T : class

Would obviously work with both reference types and your new
MyNameSpace.Nullable class (as it would be a class) the only problem would
be that calling the methods on it would need an is check (i.e. if (t is
MyNameSpace.Nullable)...).

To avoid this you would need to define two classes, one for reference types
and the other for your Nullable type:

class MyRefClass<T> where T : class
class MyNullableClass<T> where T : MyNameSpace.Nullable

HTH

- Andy
 
S

Sam Kong

Andy said:
I've done some more digging and the problem is that Nullable is actually a
struct. Accordingly templates can only be derived from interfaces,
non-sealed CLASSES and types. struct's cannot be used as base classes; full
stop...

You may be able to define your own class which implements the methods in
line with how the Nullable struct operates; it basically adds a boolean to
each type to indicate whether the value has been assigned or not... I cannot
see any reason why
this will not work but you may not be able to use the short hand form; i.e.
type? you may have to use MyType.Nullable<int> myInt... as I would assume
that the short hand form would be bound to System.Nullable (could be wrong
:).

I cannot think of any reason why this will not work but unless I actually
tried to write the code could not guarantee it would be trouble free. I also
cannot think of any reason why Nullable is implemented as a struct aside
from a value type performance angle.

If you wrote your own Nullable class then:

class MyClass<T> where T : class

Would obviously work with both reference types and your new
MyNameSpace.Nullable class (as it would be a class) the only problem would
be that calling the methods on it would need an is check (i.e. if (t is
MyNameSpace.Nullable)...).

To avoid this you would need to define two classes, one for reference types
and the other for your Nullable type:

class MyRefClass<T> where T : class
class MyNullableClass<T> where T : MyNameSpace.Nullable

HTH

- Andy

Thank you for your kind answer.

Actually I was trying to make a generic class like this.

class Board<T>{
T[,] cells;
...
}

Board instances holds some 2D array of a type.
You may think of it as Sudoku board.
But I wanted to make it more generic so that strings or numbers can be
contained (not the same time though).
So it will be like string[,] or int[,].
But for empty cells, I want to use null instead of 0.(0 doesn't always
mean empty)
So it will be string[,] or int?[,].
But I couldn't find a way to define that way.
If I don't define such constraint, I can't set null to a cell like
cells[0, 0] = null;.
The compiler would complain that cells[0,0] might be a value type.
For now, I just made it for int? only.

Thanks.

Sam
 
A

Andy Bates

Hi -

Okay; I'm not sure that you need to place a constraint on the generic as the
compiler will do all the work for you. Consider:

class MyBoard<T>
{
T[,] t;

public MyBoard(int r, int c)
{
this.t = new T[r, c];
}

public T this[int x, int y]
{
get { return this.t[x, y]; }
set { this.t[x, y] = value; }
}
}

I can use this class with any type (either value or reference) and as long
as I'm careful to copy reference values can do things such as:

MyBoard<int> mb = new MyBoard<int>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null; // *Compiler error*
int a = mb[0, 0]; // Should be 2.

MyBoard<int?> mb = new MyBoard<int?>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null;
int? a = mb[1, 1]; // Should be null.

MyBoard<string> mb = new MyBoard<string>(2, 2);
....

The indexer is the key to getting/setting items into the array as this only
works with the type the class is expecting.

- Andy

Sam Kong said:
Andy said:
I've done some more digging and the problem is that Nullable is actually
a
struct. Accordingly templates can only be derived from interfaces,
non-sealed CLASSES and types. struct's cannot be used as base classes;
full
stop...

You may be able to define your own class which implements the methods in
line with how the Nullable struct operates; it basically adds a boolean
to
each type to indicate whether the value has been assigned or not... I
cannot
see any reason why
this will not work but you may not be able to use the short hand form;
i.e.
type? you may have to use MyType.Nullable<int> myInt... as I would assume
that the short hand form would be bound to System.Nullable (could be
wrong
:).

I cannot think of any reason why this will not work but unless I actually
tried to write the code could not guarantee it would be trouble free. I
also
cannot think of any reason why Nullable is implemented as a struct aside
from a value type performance angle.

If you wrote your own Nullable class then:

class MyClass<T> where T : class

Would obviously work with both reference types and your new
MyNameSpace.Nullable class (as it would be a class) the only problem
would
be that calling the methods on it would need an is check (i.e. if (t is
MyNameSpace.Nullable)...).

To avoid this you would need to define two classes, one for reference
types
and the other for your Nullable type:

class MyRefClass<T> where T : class
class MyNullableClass<T> where T : MyNameSpace.Nullable

HTH

- Andy

Thank you for your kind answer.

Actually I was trying to make a generic class like this.

class Board<T>{
T[,] cells;
...
}

Board instances holds some 2D array of a type.
You may think of it as Sudoku board.
But I wanted to make it more generic so that strings or numbers can be
contained (not the same time though).
So it will be like string[,] or int[,].
But for empty cells, I want to use null instead of 0.(0 doesn't always
mean empty)
So it will be string[,] or int?[,].
But I couldn't find a way to define that way.
If I don't define such constraint, I can't set null to a cell like
cells[0, 0] = null;.
The compiler would complain that cells[0,0] might be a value type.
For now, I just made it for int? only.

Thanks.

Sam
 
S

Sam Kong

Hi Andy,

Andy said:
Hi -

Okay; I'm not sure that you need to place a constraint on the generic as the
compiler will do all the work for you. Consider:

class MyBoard<T>
{
T[,] t;

public MyBoard(int r, int c)
{
this.t = new T[r, c];
}

public T this[int x, int y]
{
get { return this.t[x, y]; }
set { this.t[x, y] = value; }
}
}

I can use this class with any type (either value or reference) and as long
as I'm careful to copy reference values can do things such as:

MyBoard<int> mb = new MyBoard<int>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null; // *Compiler error*
int a = mb[0, 0]; // Should be 2.

MyBoard<int?> mb = new MyBoard<int?>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null;
int? a = mb[1, 1]; // Should be null.

MyBoard<string> mb = new MyBoard<string>(2, 2);
...

The indexer is the key to getting/setting items into the array as this only
works with the type the class is expecting.

Yes, that works.
However, I can't assign null to the array in MyBoard class.
Let's say, you want to reset the board, you might do

public void Reset(){
for(int x = 0; x < t.GetLength(0); x++)
for(int y = 0; y < t.GetLength(1); y++)
t[x, y] = null; //Compilation Error!!!
}

Well, I can reset it outside the board class.
But that's not a very good design, IMHO.

Any idea?
Thanks.

Sam
 
A

Andy Bates

Easy to correct the compilation error, change the line:

t[x, y] = null; //Compilation Error!!!

to

t[x, y] = default(T);

This will create an appropriate empty value. For reference types and
nullable types it's null, for value types it's 0 and for structs it's a copy
of the struct with each field to to 0.

Information on this can be found here:

http://msdn2.microsoft.com/en-us/library/xwth0h0d.aspx

That keeps the class nicely encapsulated and resolves the problem that you
are getting instantiating your generic class with value types and structs.
We got there in the end!

- Andy

Sam Kong said:
Hi Andy,

Andy said:
Hi -

Okay; I'm not sure that you need to place a constraint on the generic as
the
compiler will do all the work for you. Consider:

class MyBoard<T>
{
T[,] t;

public MyBoard(int r, int c)
{
this.t = new T[r, c];
}

public T this[int x, int y]
{
get { return this.t[x, y]; }
set { this.t[x, y] = value; }
}
}

I can use this class with any type (either value or reference) and as
long
as I'm careful to copy reference values can do things such as:

MyBoard<int> mb = new MyBoard<int>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null; // *Compiler error*
int a = mb[0, 0]; // Should be 2.

MyBoard<int?> mb = new MyBoard<int?>(2, 2);
mb[0, 0] = 2;
mb[1, 1] = null;
int? a = mb[1, 1]; // Should be null.

MyBoard<string> mb = new MyBoard<string>(2, 2);
...

The indexer is the key to getting/setting items into the array as this
only
works with the type the class is expecting.

Yes, that works.
However, I can't assign null to the array in MyBoard class.
Let's say, you want to reset the board, you might do

public void Reset(){
for(int x = 0; x < t.GetLength(0); x++)
for(int y = 0; y < t.GetLength(1); y++)
t[x, y] = null; //Compilation Error!!!
}

Well, I can reset it outside the board class.
But that's not a very good design, IMHO.

Any idea?
Thanks.

Sam
 
S

Sam Kong

Andy said:
Easy to correct the compilation error, change the line:

t[x, y] = null; //Compilation Error!!!

to

t[x, y] = default(T);

Thank you very much Andy.
Now I will modify my Board class with that.

You're really helping.
Thanks again.

Sam
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top