30

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.

joce
  • 9,624
  • 19
  • 56
  • 74
  • 4
    It's worth noting that `int? foo = null;` isn't *really* null. It's just sugar to let you pretend like it is. – Anthony Pegram Apr 20 '11 at 23:59
  • 2
    "I'd like to restrict a generic I'm coding to anything that can be " - can you give an example of why you want to do that? – Mitch Wheat Apr 21 '11 at 00:00
  • 1
    Hum. That then brings the question, what would `default(T)` be for `int?`. I was assuming it'd be `null`. – joce Apr 21 '11 at 00:00
  • 2
    Constraints are anded together; there is way to "or" them. I dont think you can do this. – Richard Schneider Apr 21 '11 at 00:04
  • 2
    @Joce, Being that `int?` is shorthand for the struct `Nullable`, you'd get a `new Nullable()` with the `HasValue` member set to false. Special semantics in place allow you to effectively treat this as null in your code. It even boxes to null when applicable. – Anthony Pegram Apr 21 '11 at 00:04
  • @Mitch Wheat: There you go. I've added an usage that works with strings. Doesn't work with `int?`, though. – joce Apr 21 '11 at 00:14
  • @Anthony Pegram: Okay. That's neat! But... I still don't know if I can restrict a generic to it! :-) – joce Apr 21 '11 at 00:17
  • @Joce: that an example of usage, but why do you want to do that? How will you iterate over a collection with nulls in it? – Mitch Wheat Apr 21 '11 at 00:17
  • @Mitch, here's an example of such an iteration in the example. `foreach (var v in vals.Where(v => v != default(T))) {}` – joce Apr 21 '11 at 00:19
  • @Mitch Clear code? How is this not clear? And what does clarity have to do with the question at hand? My example is clean, concise and to the point and demonstrate a use case. I don't know what more you want. – joce Apr 21 '11 at 00:23
  • @Joce : because it goes against the well known convention of how a foreach runs. Plus it's ugly! – Mitch Wheat Apr 21 '11 at 00:24
  • @Mitch Riiiiiiiiiiiiiiiiiiiight... Keep telling yourself that. – joce Apr 21 '11 at 00:25
  • 1
    @Joce : Have you considered that the fact you want to do something that noone else needs to do might be a sign you are doing it wrong? – Mitch Wheat Apr 21 '11 at 00:26

4 Answers4

15

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)));
        }
    }
}
user541686
  • 205,094
  • 128
  • 528
  • 886
  • Throwing exceptions in a static constructor is bad practice, no? Can have a difficult to debug ripple effect. – DuckMaestro Apr 21 '11 at 02:40
  • 5
    Assuming that you appropriately document the fact that `T` has to be nullable, and given that there's no way to force this at compile-time, then I don't see why it's bad practice. Why would it be hard to debug? It would be thrown *as soon as the class is used*, before any methods are called. I mean, I'd love to tell you if there's a better way, but there's simply no way to do this at compile-time, and I don't see how checking it anywhere other than the static constructor can be any better. – user541686 Apr 21 '11 at 02:42
  • 1
    @Mehrdad i think this question is worth read once as @DuckMastro has said... http://stackoverflow.com/questions/4579016/static-constructor-and-exceptions – Harsh Baid Apr 21 '11 at 04:34
  • 1
    @Harsh: Isn't killing the app domain the entire point? That way you won't continue running in an undetermined state, and it's about as noticeable as the error will get. I'm not sure what's so bad about it? – user541686 Apr 21 '11 at 10:03
  • 1
    Btw, I feel like if you make such a grave mistake that you instantiate an entire *class* in an invalid way *and* you fail to look at the docs, killing the application is a very just and useful punishment. >:] – user541686 Apr 21 '11 at 10:05
  • I think the "there's no way" answer is the right one at this point. However, like the others, I don't like the idea of run time checks. I'll just restrict the usage to `class` and encapsulate any PODs in light wrapper classes if needs be. – joce Apr 21 '11 at 16:58
2

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");
        }
    }
}
Community
  • 1
  • 1
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • Yeah, but that doesn't work as for the algorithm I'm using, I need `default(T)` to be `null`. – joce Apr 21 '11 at 02:13
  • @Joce: Sorry, I don't understand your problem. If you just never use non-nullable types as `T`, then `default(T)` will always be `null`. Are you just trying to prevent people from accidentally using the class improperly? – Gabe Apr 21 '11 at 02:45
  • Sorry, I misunderstood the nature of the change you made where you did "OfType" instead of "Where". Hum... I think this could indeed work nicely for my needs!!! Thanks! – joce Apr 21 '11 at 05:41
  • But it doesn't answer the question: Is it even possible to restrict a generic to anything that can be `null`, and of so, how? – joce Apr 21 '11 at 05:54
1

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.

Craig Suchanec
  • 10,474
  • 3
  • 31
  • 39
  • I agree that based on my example, that could be a valid solution. However, it fails if you need to store a `List` inside the class. I'll update my example to reflect this. – joce Apr 21 '11 at 05:29
0

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
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • Yeah, I tried something similar, but then you can't compare variables of type `T` with `default(T)` afterwards. try it with my example for fun. I get: *error CS0019: Operator '!=' cannot be applied to operands of type 'T' and 'T'*, because `=` isn't guaranteed to exist for all types. :-/ – joce Apr 21 '11 at 02:42
  • Note that `default(T)` will be null, and you can always compare objects against null. So simply do `if (t == null)` as opposed to `if (t == default(T))`. Once the null check is performed, you can utilize the `.Equals` virtual method to compare objects for equality. – Anthony Pegram Apr 21 '11 at 02:48