joce
joce

Reputation: 9892

Restricting a generic to things that can be null

I'd like to restrict a generic I'm coding to anything that can be null. That's basically any class + System.Nullable (e.g. int? and such).

For the class part, it's rather easy:

public class MyGeneric<T> where T : class {}

But then, this doesn't allow me to do this:

var myGeneric = new MyGeneric<int?>();

or this:

var myGeneric = new MyGeneric<Nullable<int>>();

The compiler complains with:
error CS0452: The type 'int?' must be a reference type in order to use it as parameter 'T' in the generic type or method 'Test.MyGeneric'

So I tried addind System.Nullable as accepted types for T:

public class MyGeneric<T> where T : class, System.Nullable {}

But it won't do. The compiler returns the following error:
error CS0717: 'System.Nullable': static classes cannot be used as constraints

I then tried

public class MyGeneric<T> where T : class, INullable {}

It does compile, but then when I do:

var myGeneric = new MyGeneric<string>();

The compiler returns this error:
error CS0311: The type 'string' cannot be used as type parameter 'T' in the generic type or method 'Test.MyGeneric'. There is no implicit reference conversion from 'string' to 'System.Data.SqlTypes.INullable'.

So, the question is: Is it even possible to restrict a generic to anything that can be null, and of so, how?

For reference, I'm using VS2010 / C# 4.0

edit
I was asked what I want to do with it. Here's an example:

namespace Test
{
    public class MyGeneric<T> where T : class
    {
        private IEnumerable<T> Vals { get; set; }

        public MyGeneric(params T[] vals)
        {
            Vals = (IEnumerable<T>)vals;
        }

        public void Print()
        {
            foreach (var v in Vals.Where(v => v != default(T)))
            {
                Trace.Write(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = 
                new MyGeneric<string>("a", "b", "c", null, null, "g");
            foo.Print();
        }
    }
}

This program prints abcg in the debug console.

Upvotes: 30

Views: 10938

Answers (4)

Gabe
Gabe

Reputation: 86708

No, you cannot restrict a generic to only things that can be null. See How can I return NULL from a generic method in C#?, for instance. The only solutions presented were either to use default(T) instead or to use a class restriction because there's no way to restrict to only nullable types.

It's not clear what your purpose is. Changing just a couple lines of your example code makes it work with any type, not just nullable ones, so I don't see why you're trying to restrict it.

This example will work with MyGeneric<int?> or MyGeneric<int> too:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Test
{
    public class MyGeneric<T> // removed "where T : class"
    {
        public void Print(params T[] vals)
        {
            Print((IEnumerable<T>) vals);
        }

        public void Print(IEnumerable<T> vals)
        {
            foreach (var v in vals.OfType<T>()) // use "OfType" instead of "Where"
            {
                Trace.WriteLine(v.ToString());
            }
            Trace.WriteLine(string.Empty);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyGeneric<string> foo = new MyGeneric<string>();
            foo.Print("a", "b", "c", null, null, "g");
        }
    }
}

Upvotes: 2

Craig Suchanec
Craig Suchanec

Reputation: 10804

The one thing that is unclear from your example is why you want to do this with a class level generic rather than a method level generic. Based upon your example you could do the following:

public class NonGenericClass
{
    public void Print<T>(IEnumerable<T?> vals) where T : struct
    {
        PrintRestricted(vals.Where(v =>  v.HasValue));
    }

    public void Print<U>(IEnumerable<T> vals) where T : class
    {
        PrintRestricted(vals.Where(v => v != default(T)));
    }

    private void PrintRestricted<U>(IEnumerable<T> vals)
    {
        foreach (var v in vals)
        {
            Trace.WriteLine(v.ToString());
        }
        Trace.WriteLine(string.Empty);
    }
}

For the cost of writing a wrapping method that does the restriction you can get the same functionality.

Upvotes: 1

user541686
user541686

Reputation: 210402

No, there is no way to do this at compile-time.

Personally, I'd just let T be anything, then I'd check its validity in a static constructor:

public class MyGeneric<T>
{
    static MyGeneric()
    {
        var def = default(T); 
        if (def is ValueType && Nullable.GetUnderlyingType(typeof(T)) == null)
        {
            throw new InvalidOperationException(
                string.Format("Cannot instantiate with non-nullable type: {0}",
                    typeof(T)));
        }
    }
}

Upvotes: 15

Anthony Pegram
Anthony Pegram

Reputation: 126814

Sometimes the type system simply cannot do what you want. In these cases, you either change what you want, or you work around it.

Consider the example of the Tuple<> class. The "largest" version of it looks like Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>, where TRest must be a Tuple<>. This is not a compile time restriction, it is strictly a runtime verification. You may have to resort to something similar if you want to enforce a nullability requirement of the T in Foo<T>, supporting both typical classes and nullable structs.

/// <summary>
/// Class Foo requires T to be type that can be null. E.g., a class or a Nullable&lt;T&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
class Foo<T>
{
    public Foo()
    {
        if (default(T) != null)
            throw new InvalidOperationException(string.Format("Type {0} is not valid", typeof(T)));
    }

    // other members here
}

In doing this, I am documenting that the class will require T to be compatible with nullable types and throw in the constructor if it is not.

Foo<string> foo = new Foo<string>(); // OK
Foo<int?> works = new Foo<int?>(); // also OK
Foo<int> broken = new Foo<int>(); // not OK

Upvotes: 0

Related Questions