How do I constraint a generic type parameter to only accept nullable value types in C#?
Asked Answered
H

3

7

The where T : struct constraint lets one to limit the domain of acceptable type parameters to the set of value types (as compared to the superset of types including both value and reference types) only but also seems to forbid nullable types altogether although nullable doesn't necessarily mean a reference type in modern versions of C#.

What if I'd like to accept value types with added nullability like int?, DateTime? etc while rejecting natively-nullable reference types like string, IList etc? Is it possible to define the constraints this way? How if it is?

I am actually curious to learn to implement both the scenarios: when the type used as the parameter must be both value and nullable and when a nullable value type is to be accepted though as well as a non-nullable value type and I consider these related closely enough to excuse mentioning both so I'd appreciate a humble commentary about the second case and choose an answer including it as a better one (given another one is not going to be really better in other ways) if more than one answer will be submitted and I'll have to choose, but what I actually need right now is the first case (to always require a type that is both nullable and is a value type at the same time) and I also believe the second case is going to be pretty straightforward given the knowledge of the first, not to mention it is not a good manner to insist on gluing 2 questions into one so I will absolutely appreciate and accept an answer dealing with the first case only too.

Heisler answered 26/3, 2017 at 2:27 Comment(4)
Have you tried using System.Nullable<T> as the constraint? msdn.microsoft.com/en-us/library/b3h38hb0.aspxCheer
unfortunutely generic constraints on C# are limited and no one can provide an answer with a feature that doesn't exist, not even Eric Lippert :)Letendre
@SelmanGenç D'oh! that's right. I keep forgetting that limitation. I didn't have VS in front of me so I could test :)Cheer
@Cheer I actually wrote that to OP but yeah, that was the first thing I tried as well and failed :)Letendre
A
9

You can't. Nullable<T> are not valid constraint for generics in C# language versions 7 and lower (see this answer for how to do it in C# 8+).

When you try something like class X<T,U> where T : Nullable<U> you get following error:

'U?' is not a valid constraint. A type used as a constraint must be an interface, a non-sealed class or a type parameter.

If you need to accept both T and Nullable<T> as method parameters you can just provide overrides:

class X<T>  where T : struct
{
   public void R(T arg){ Console.WriteLine("Non nullable: {0}", arg);}
   public void R(Nullable<T> arg){Console.WriteLine("Nullable: {0}", arg);}
}

And then you can call either version:

X<int> r = new X<int>();
r.R((int?)4);
r.R(4);

In your type deals with just Nullable values you can simply constraint to T:struct but everywhere inside your class use Nullable<T> for parameters and fields.

More discussions on particular aspects - C# generic type constraint for everything nullable and related questions.

Acidulate answered 26/3, 2017 at 3:11 Comment(0)
D
5

It's not exactly what you want, but maybe you could use a type constraint of IConvertible? As an interface, it is nullable, and is implemented by Boolean, SByte, Byte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, Decimal, DateTime, Char, and String.

class MyClass<T> where T : IConvertible
{
    //Etc
}
Disseminule answered 26/3, 2017 at 3:13 Comment(3)
Though I don't feel I can accept this as it is not strictly an answer to the question specified, let me express my sincere thanks for an interesting contribution and make an upvote.Heisler
Wonderful solution for a specific use case I recently ran into when dealing with various APIs for a specific service. They all returned their error codes as different types, but they were primitives. With one of the providers, the error code would be null if there was no error. Constraining to "IConvertible?" solved my problem. Thank you.Throstle
This is not correct: Error CS0313 The type 'int?' cannot be used as type parameter 'T' in the generic type or method 'Range<T>'. The nullable type 'int?' does not satisfy the constraint of 'System.IConvertible'. Nullable types can not satisfy any interface constraints.Lampley
E
-1

The answer to this, in modern C# (valid since C# language version 8, introduced in late 2019), is to use class? as your type constraint, instead of just class, like so:

public interface ISomeGenericInterface<T> : where T : class?
{
    public T? Value { get; set; }
}

To make the compiler happy, you still need to declare fields and properties of type T as explicitly T or T?, as appropriate, or it'll still complain about, for example, not assigning a non-null value to a field of type T from a constructor, because that nullability constraint doesn't propagate and make T itself mean T?.

Be aware, though, that if your class or interface also inherits from another interface that forces non-nullability, your class? constraint will be overridden by that interface's nullability requirements. Most of the time, you can fix that by just propagating the ? all the way through all of the base types. For example, compare these two classes, which have just one difference other than their names:

public class SomeClassWithNullableValue<T> : where T : class?, IComparable<T?>?
{
    public T? Value { get; set; }
}

public class SomeClassWith_Non_NullableValue<T> : where T : class?, IComparable<T>
{
    public T? Value { get; set; }
}

Without those ?s in the IComparable constraint, T is forced to a non-nullable context. It'll compile without problem, if you don't treat warnings as errors, but you've completely side-stepped the point of the nullability context indicators at that point and made consumers unable to trust the nullability of the type. The compiler will warn you, in the second class, with CS8766, which is this, in the MS Documentation:

"Nullability of reference types in return type of doesn't match implicitly implemented member (possibly because of nullability attributes)".

As of 2023-05-22, this link to Microsoft documentation has the proof of this: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

Earthling answered 23/5, 2023 at 5:30 Comment(4)
The question is how to contraint the type to be a nullable VALUE TYPE. A class is not a value type.Lampley
@Lampley Right you are. This particular problem, is nasty, too, if implemented interfaces such as IComparable<T> use an open type parameter, as boxing will occur on calls to Equals, even if you implement overloads with explicit types. For that example, the compiler can't guarantee it ahead of time, so it targets the Equals method with the object? parameter. I found this out in an application I'm working on that has a generic struct type, when I was profiling it. The only way to avoid it is inheriting closed generic interfaces and writing explicit overloads for each expected type.Earthling
Yeah, I had a similar issue and after hours of research have admitted defeat to the C# language and made a nullable version of my generic class.Lampley
Ha. Thankfully, keeping it non-nullable and implementing all the specific typed interfaces was MOSTLY just a bunch of copy-paste work, with replacing the types one the method parameters, so it wasn't actually THAT much work - just tedious. But it did resolve the problem completely, in this case, and even resulted in a smaller executable, even though it added many lines of code. I suppose that's because the compiler didn't need to generate as much code to handle all possible generic cases for an open generic.Earthling

© 2022 - 2024 — McMap. All rights reserved.