Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constrain generic to be a nullable type

Tags:

c#

.net

generics

I'm looking for sample usage something like this:

Foo<string> stringFoo = new Foo<string>("The answer is");
Foo<int> intFoo = new Foo<int>(42);
// The Value of intFoo & stringFoo are strongly typed
stringFoo.Nullify();
intFoo.Nullify();
if (stringFoo == null && intFoo == null)
   MessageBox.Show("Both are null);

Given this class Foo, I can auto-wrap T into a nullable:

public class Foo1<T>
   where T : struct
{
   private T? _value;

   public Foo(T? initValue)
   {
      _value = initValue;
   }

   public T? Value { get { return _value; } }

   public void Nullify { _value = null; }

}

This works for primitives, but not with String or other classes.

Next flavor works for strings, but not primitives:

public class Foo2<T>
{
   private T _value;

   public Foo(T initValue)
   {
      _value = initValue;
   }

   public T Value { get { return _value; } }

   public void Nullify { _value = default(T); }

}

I could use Nullable<int> for Foo2 and the code would work like this:

Foo2<int?> intFoo = new Foo<int?>(42);

But this is error prone because it fails for Foo2. If I could constrain T to be types that support nullability then that would be fine.

So after all of that, is there any way to constrain T to be a nullable type?

Some additional notes: .NET 4.0, VS2010. And I did find one similar question to this on here, but without a succesful answer.

like image 564
tcarvin Avatar asked Feb 15 '13 13:02

tcarvin


People also ask

How do I restrict a generic class in C#?

In c#, constraints are used to restrict generics to accept only the particular type of placeholders. By using where keyword, we can apply constraints on generics. In c#, you can apply multiple constraints on generic classes or methods based on your requirements.

What is generic type constraint?

A type constraint on a generic type parameter indicates a requirement that a type must fulfill in order to be accepted as a type argument for that type parameter. (For example, it might have to be a given class type or a subtype of that class type, or it might have to implement a given interface.)

What is unbound generic type?

An unbound generic type is not itself a type, and it cannot be used as the type of a variable, argument, or return value, or as a base type. The only construct in which an unbound generic type can be referenced is the typeof expression (§7.6. 11).

How do you indicate that a class has a generic type parameter?

A generic type is declared by specifying a type parameter in an angle brackets after a type name, e.g. TypeName<T> where T is a type parameter.


2 Answers

There's no constraint you can apply for this, but you can test for it at execution time:

if (default(T) != null)
{
    throw new SomeAppropriateException(typeof(T) + " is not a nullable type");
}

You could even put that into a static constructor, which would make sure it only executed once per constructed type - and anyone trying to use Foo<int> anywhere would have a hard time ignoring the TypeInitializerException. That's not terribly friendly for a public API, but I think it's reasonable for an internal one.

EDIT: There is one horrible way of making it harder to create instances of Foo<int>... you could use the ghastly code in this blog post (using overload resolution rules along with default parameters and a couple of constrained generic types) and mark the overload which aims at a non-nullable value type as obsolete with an error. That way, Foo<int> would still be a valid type, but you'd be hard-pressed to create an instance of it. I'm not going to recommend that you do this though...

like image 95
Jon Skeet Avatar answered Oct 23 '22 21:10

Jon Skeet


You might be able to make the constructor of Foo<T> internal, and require that new instances can only be created through a factory class:

public class Foo<T>
{
    private T value;
    internal Foo(T value)
    {
        this.value = value;
    }

    public void Nullify()
    {
        this.value = default(T);
    }

    public T Value { get { return this.value; } }
}

public class Foo
{
    public static Foo<T> Create<T>(T value) where T : class
    {
        return new Foo<T>(value);
    }

    public static Foo<T?> Create<T>(T? value) where T : struct
    {
        return new Foo<T?>(value);
    }
}
like image 21
Lee Avatar answered Oct 23 '22 21:10

Lee