Non-nullable enum as generic parameter [duplicate] - c#

This question already has an answer here:
Why doesn't return default(T?) give a null when T is constrained to enum?
(1 answer)
Closed 9 months ago.
I am trying to implement this helper function in C# 7.3:
public static T? ToEnum<T>(this string enumName)
where T : Enum
=> Enum.TryParse<T>(enumName, out T val) ? val : null;
But got a compiler error:
Error CS8370 Feature 'nullable reference types' is not available in C# 7.3
Why does the compiler think I am using a nullable reference type while T is constrained to be an Enum which is a value type already?

This problem is not specific to nullable enums. Even if you use a non-nullable enum, it won't work. That's because while specific enum types are value types, System.Enum is a reference type as explained here. So, you need to add a struct constraint in addition to the Enum constraint.
The following should work:
public static T? ToEnum<T>(this string enumName)
where T : struct, Enum
=> Enum.TryParse<T>(enumName, out T val) ? val : default(T?);
Also, note that you can't use null in the second branch of the ternary operator because the compiler can't infer the type. Use default(T?) instead.
Example of usage:
enum MyEnum { a, b, c }
static void Main(string[] args)
{
var e1 = "b".ToEnum<MyEnum>();
var e2 = "d".ToEnum<MyEnum>();
Console.WriteLine("e1 = " + (e1?.ToString() ?? "NULL")); // e1 = b
Console.WriteLine("e2 = " + (e2?.ToString() ?? "NULL")); // e2 = NULL
}

Related

defining a generic implicit conversion to nullable [duplicate]

This question already has answers here:
Nullable reference types: How to specify "T?" type without constraining to class or struct
(3 answers)
T?, nullable type parameter in C# 9
(2 answers)
Closed 8 months ago.
I have the following struct:
struct Generic<T>
{
public T Property { get; init; }
public Generic(T property) { Property = property; }
public static implicit operator T?(Generic<T> x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
The compiler underlines the null in the implicit conversion and reports error CS0403:
Cannot convert null to type parameter T because it could be a non-nullable value type. Consider using default(T) instead.
But I do not define a conversion to T, I define it to T?! Why does the compiler not see this? And what can I do to get rid of the compiler error?
Strangely, these two nongeneric versions - one with T being a struct, one with T being a class - do not give any compiler errors:
struct NongenericStruct
{
public int Property { get; init; }
public NongenericStruct(int property) { Property = property; }
public static implicit operator int?(NongenericStruct x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
struct NongenericClass
{
public string Property { get; init; }
public NongenericClass(string property) { Property = property; }
public static implicit operator string?(NongenericClass x)
=> x.GetHashCode() == 42 ? null : x.Property;
}
A nullable reference type is not the same as a nullable value type.
As the generic type T has no constraint, the compiler cannot explicitly convert null into either a Nullable<T> (struct) or a T? (class).
In order to fix this you have to explicitly tell the compiler if it's a reference or a value type with a generic type constraint.
// reference type
where T : class
// value type
where T : struct

Why is "default" different from "null" when assigning to a nullable type constrained by notnull?

Consider this minimal-ish example:
class A<T> where T : notnull
{
public T? Item; // OK!
public T? Something() => default; // OK!
public void Meow(ref T? thing1, out T? thing2) { thing2 = default; } // OK!
void Something(A<T>? other)
{
// OK!
Item = other is null ? default : other.Item;
// Error CS0403 : Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
Item = other is null ? null : other.Item;
// Error CS8978 : 'T' cannot be made nullable.
Item = other?.Item;
Item = other?.Item ?? default;
var x = other?.Item;
}
void Weird<U>(A<U>? other) where U : unmanaged
{
// OK!
var x = other?.Item;
}
void Weird2<U>(A<U>? other) where U : class
{
// OK!
var x = other?.Item;
}
void Weird3<U>(A<U>? other) where U : struct
{
// OK!
var x = other?.Item;
}
}
T is notnull
T? is either T or null ... isn't it? In what case would that NOT be true?
If T is struct then Item is Nullable<T>, which can be assigned by null
If T is class then 'Item' is simply T? which also can be assigned by null
I even made T unmanaged just to double check (it should be a subset of struct), and that can be assigned by null
...
So what type T exists such that T? could not hold the value null?
... I'm also not quite sure I'm even asking the right question ... My real question is "Why doesn't it work!!?!?!" :)
Note: A similar question was properly answered here: C#'s can't make `notnull` type nullable ... but that answer is applicable for C# 8 ... as of C# 9 and above, it's no longer the case
I'll also point out docs here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters ... which basically say that notnull means class/struct
...
Edit (afterthought): If it's an IL issue (that the IL needs to know whether it's a a nullable reference type or Nullable) ... Then why does default work and null doesn't? Clearly SOME version of IL works, so it's just a language deficiency/quirk?
Welp, I'm posting an answer (since nobody else did), after posting this as a bug here: https://github.com/dotnet/roslyn/issues/59805
The answer is that T? is not int? when T is an int. T? is simply int.
Which is nuts :)
It's even weirder because if you tell the compiler that T is a struct, then suddenly T? is int? when T is an int.
Weird. But okay. So be it.

Nullability mismatch in generic type parameter between two method arguments

I've written the following extension method:
// using System.Collections.Generic;
internal static class TExtensions {
internal static bool In<T>(this T val, HashSet<T> hs) => hs.Contains(val);
}
and am trying to consume it as follows:
var s = DateTime.Now.Hour < 15 ? "abcd" : null;
var hs = new HashSet<string>();
Console.WriteLine(s.In(hs));
The compiler gives me a warning on the last line:
CS8620 Argument of type 'HashSet' cannot be used for parameter 'hs' of type 'HashSet' in 'bool TExtensions.In(string? val, HashSet? hs)' due to differences in the nullability of reference types.
because the compiler is resolving the T type parameter as the type of s, or string?; but the hashset is not a HashSet<T> which would be a hashset of nullable string (HashSet<string?>), rather it is a hashset of non-nullable string (HashSet<string>).
I could resolve this by either wrapping in a null check:
if (s is { }) {
var result = s.In(hs);
}
or explicitly typing the hashset as having nullable elements:
var hs = new HashSet<string?>();
but is there some way to use the nullable attributes to allow this scenario? Or is there something else I could change in the In extension method?
You can use the [AllowNull] attribute for the first parameter of the In method:
internal static bool In<T>([AllowNull] this T val, HashSet<T> hs) => hs.Contains(val);
However as you described in your question the actual problem here is that the inferred generic type parameter is determined by s. The In method will be called as In<string?> which requires the hashset to be HashSet<string?>. You must tell the compiler to use string instead of string?: s.In<string>(hs).
If you can constraint to non-nullable reference types you can use this:
internal static bool In<T>(this T? val, HashSet<T> hs) where T : class => val != null && hs.Contains(val);
For value types the implementation would be:
internal static bool In<T>(this T? val, HashSet<T> hs) where T : struct => val.HasValue && hs.Contains(val.Value);
Note that only the first version (with the [AllowNull] attribute) works if the type parameter itself is nullable.
Instead of changing the extension method you can of course just use the null-forgiving-operator to shut up the compiler: s.In(hs!) or s!.In(hs).

How to initialize generic parameter type T?

Simple question:
If you have a string x, to initialize it you simple do one of the following:
string x = String.Empty;
or
string x = null;
What about Generic parameter T?
I've tried doing:
void someMethod<T>(T y)
{
T x = new T();
...
}
Generate error :
Cannot create an instance of the variable type 'T' because it does not have the new() constraint
You have two options:
You can constrain T: you do this by adding: where T : new() to your method. Now you can only use the someMethod with a type that has a parameterless, default constructor (see Constraints on Type Parameters).
Or you use default(T). For a reference type, this will give null. But for example, for an integer value this will give 0 (see default Keyword in Generic Code).
Here is a basic console application that demonstrates the difference:
using System;
namespace Stackoverflow
{
class Program
{
public static T SomeNewMethod<T>()
where T : new()
{
return new T();
}
public static T SomeDefaultMethod<T>()
where T : new()
{
return default(T);
}
struct MyStruct { }
class MyClass { }
static void Main(string[] args)
{
RunWithNew();
RunWithDefault();
}
private static void RunWithDefault()
{
MyStruct s = SomeDefaultMethod<MyStruct>();
MyClass c = SomeDefaultMethod<MyClass>();
int i = SomeDefaultMethod<int>();
bool b = SomeDefaultMethod<bool>();
Console.WriteLine("Default");
Output(s, c, i, b);
}
private static void RunWithNew()
{
MyStruct s = SomeNewMethod<MyStruct>();
MyClass c = SomeNewMethod<MyClass>();
int i = SomeNewMethod<int>();
bool b = SomeNewMethod<bool>();
Console.WriteLine("New");
Output(s, c, i, b);
}
private static void Output(MyStruct s, MyClass c, int i, bool b)
{
Console.WriteLine("s: " + s);
Console.WriteLine("c: " + c);
Console.WriteLine("i: " + i);
Console.WriteLine("b: " + b);
}
}
}
It produces the following output:
New
s: Stackoverflow.Program+MyStruct
c: Stackoverflow.Program+MyClass
i: 0
b: False
Default
s: Stackoverflow.Program+MyStruct
c:
i: 0
b: False
use default keyword.
T x = default(T);
See: default Keyword in Generic Code (C# Programming Guide)
Given a variable t of a parameterized type T, the statement t = null
is only valid if T is a reference type and t = 0 will only work for
numeric value types but not for structs. The solution is to use the
default keyword, which will return null for reference types and zero
for numeric value types. For structs, it will return each member of
the struct initialized to zero or null depending on whether they are
value or reference types.
You need to add a new constraint for the type parameter T.
void someMethod<T>(T y) where T : new()
{
T x = new T();
...
}
This will only be valid for types with a default constructor however.
The where clause for T is a generic type constraint. In this case, it requires that any type T this method is applied to must have a public parameterless constructor.
If you really need an instance of T and not a default null value for reference types, use:
Activator.CreateInstance()
You may use default construct to set it to whatever that Type's default is.
The default keyword allows you to tell the compiler that at compile time the default value of this variable should be used. If the type argument supplied is a numeric value (e.g., int, long, decimal), then the default value is zero. If the type argument supplied is a reference type, then the default value is null. If the type argument supplied is a struct, then the default value of the struct is determined by initializing each member field of the struct to zero for numeric types or null for reference types.
Use something like :
T data = default(T);
For details, read : Initializing Generic Variables to Their Default Values

Type checking on Nullable<int>

If have the following method:
static void DoSomethingWithTwoNullables(Nullable<int> a, Nullable<int> b)
{
Console.WriteLine("Param a is Nullable<int>: " + (a is Nullable<int>));
Console.WriteLine("Param a is int : " + (a is int));
Console.WriteLine("Param b is Nullable<int>: " + (b is Nullable<int>));
Console.WriteLine("Param b is int : " + (b is int));
}
When i call this method with null as a parameter, the type check returns false for this parameter. For example this code
DoSomethingWithTwoNullables(5, new Nullable<int>());
results in this output:
Param a is Nullable<int>: True
Param a is int : True
Param b is Nullable<int>: False
Param b is int : False
Is there any way to preserve the type information when using a Nullable and passing null? I know that checking the type in this example is useless, but it illustrates the problem. In my project, I pass the parameters to another method that takes a params object[] array and tries to identify the type of the objects. This method needs to do different things for Nullable<int> and Nullable<long>.
Going straight to the underlying problem, no, you can't do this. A null Nullable<int> has exactly the same boxed representation as a null Nullable<long>, or indeed a 'normal' null. There is no way to tell what 'type' of null it is, since its underlying representation is simply all-zeros. See Boxing Nullable Types for more details.
conceptually, new Nullable<int> is null.
If we generalise, forgetting about Nullable<T>:
string s = null;
bool b = s is string;
we get false. false is the expected value for a type-check on a null value.
You can try using Reflection to achieve this. Relevant article here.
Unfortunately, null does not point to any specific memory location, and thus there is no metadata that you can associate with it to lookup the type. Thus, you cannot gain any additional information about the variable.
Unless I'm misunderstanding the question, you can get the type using GetType(). For example,
int? myNullableInt = null;
Console.WriteLine(myNullableInt.GetValueOrDefault().GetType());
If myNullableInt is null, a default value will be returned. Check the type of this returned value and, in this case, it will return System.Int32. You can do an If..else/Switch check on the returned type to perform the relevant action.
(int? is the same as Nullable<int>)
You can't do this, nor should you want to. Since Nullable<T> is a struct, value-type variables of this type have all the type information you need at compile time. Just use the typeof operator.
On the other hand, you might have a Nullable instance whose type you don't know at compile time. That would have to be a variable whose static type is object or some other reference type. Howver, because a Nullable<T> value boxes to a boxed T value, there's no such thing as a boxed Nullable<T>. That instance whose type your checking will just be a T.
This is why you get the same result for is int and is Nullable<int>. There's no way to distinguish between a boxed int and a boxed int?, because there is no boxed int?.
See Nulls not missing anymore for details.
As it has already been pointed out null has no type. To figure out if something is int? vs long? you need to use reflection to get information about something storing the type. Here is some code that you may be able to use as inspiration (not knowing exactly what you try to achieve the code is a bit weird):
class Pair<T> where T : struct {
public Pair(T? a, T? b) {
A = a;
B = b;
}
public T? A { get; private set; }
public T? B { get; private set; }
}
void DoSomething<T>(Pair<T> pair) where T : struct {
DoMore(pair);
}
void DoMore(params object[] args) {
Console.WriteLine("Do more");
var nullableIntPairs = args.Where(IsNullableIntPair);
foreach (Pair<int> pair in nullableIntPairs) {
Console.WriteLine(pair.A);
Console.WriteLine(pair.B);
}
}
bool IsNullableIntPair(object arg) {
var type = arg.GetType();
return type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(Pair<>)
&& type.GetGenericArguments()[0] == typeof(int);
}
If you execute the following code
DoSomething(new Pair<int>(5, new int?()));
DoSomething(new Pair<long>(new long?(), 6L));
you get the following output:
Do more
5
null
Do more
You can use typeof :
a == typeof(Nullable<int>) //true
a == typeof(int) //false

Categories

Resources