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
Related
Given the following class
public class Component<TValue>
{
public TValue? MaxValue { get; set; }
public TValue? MinValue { get; set; }
public TValue Value { get; set; } = default!;
public override string ToString() =>
$"MinValue = {MinValue}, MaxValue = {MaxValue}, Value = {Value}";
}
Why is it that when I create an instance of Component<int> the MinValue and MaxValue properties are int instead of Nullable<int>?
static void Main(string[] args)
{
var instance = new Component<int>();
instance.Value = 42;
Console.WriteLine(instance.ToString());
}
I could get it to behave how I want to by adding where TValue: struct, but I am writing a Blazor component that needs to work with any numeric type (including nullables) so I can't add that constraint.
I'd like to know the logic behind why TValue? for an int should not be compiled as Nullable<Int32>.
You did not constrain your class's type parameter neither to struct nor to class.
When you create new instance of Component with generic type parameter which is not nullable (in your case 'int') all of the properties using this generic type, will become int.
If you want your TValue to be nullable, create instance providing 'int?' as a generic type parameter.
When the type parameter T in syntax T? is not constrained to a struct it takes on the nullable reference type meaning of T?. In the cases where T is instantiated with a reference or interface type then the result will continue to be a nullable reference type. For instance if it's instantiated with string the value is treated as string?. In the case T is instantiated with a value type the ? is effectively dropped as it has no meaning.
If the desire is to have T? mean a nullable value type then T must be constrained to struct
If the desire is to have T? mean Nullable<T> when instantiated with a value type and T? when instiated with a reference type, then unfortunately there is no such syntax for that. It would require significant runtime work to provide
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
}
Rephrased question:
I have this equalitycomparer with Generic type constrained to a class
public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
{
public static ReferenceEqualityComparer<T> Default => new();
public bool Equals(T? x, T? y) => ReferenceEquals(x, y);
public int GetHashCode(T? obj) => RuntimeHelpers.GetHashCode(obj);
}
when a class let's say
public class A
{
public string? P1{get; set;}
}
is consumed by a code generator
[Generator]
public class MyCodeGenerator : ISourceGenerator
{
}
the NetAnalyzer says that string? has a TypeKind of class
but when I do this,
#nullable enable
[TestMethod]
public void RefTest()
{
string? s1 = "adsad";
string? s3 = s1;
Assert.IsTrue(ReferenceEqualityComparer<string?>.Default.Equals(s1, s3));
}
#nullable restore
it says that string?does not match 'class' constraint. Even though the analyzer is telling me that its a class, am I missing something here? or have I misunderstood the concept?
Original question: According to the description of Nullable from Microsoft Documentation, they are classified as structs, but why is it that the CodeAnalyzer is telling me that the TypeKind of string? is a TypeKind.Class?
Here's some context, in a library I'm writing, classes are analyzed for Source Generation (C# 9 Source Generator) which is essentially using .NetAnalyzer. Each of the properties of the class will be checked whether their type is considered as a class. It turns out string? is considered as a Class.
There is a specific constraint for nullable classes in C#, so from constraint standpoint in compile time SomeRefType? does not match T:class, but it will match T:class?.
where T : class The type argument must be a reference type. This constraint applies also to any class, interface, delegate, or array type. In a nullable context in C# 8.0 or later, T must be a non-nullable reference type.
where T : class? The type argument must be a reference type, either nullable or non-nullable. This constraint applies also to any class, interface, delegate, or array type.
So possibly in nullable context you will want to use the second one:
public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class?
Which will not give any warnings neither for string? nor for string.
strings are classes and cannot be used as generic type argument for Nullable because of the generic constraint where T : struct. In your context string? is a nullable reference type, which can be used to incdicate to the compiler that a reference type should not have null as value (variables still can have null as value and should be checked for null values in public APIs, but the compiler will warn you when you use null in a non-nullable context)
I want to write a generic extension method that throws an error if there is no value. SO I want something like:
public static T GetValueOrThrow(this T? candidate) where T : class
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
C# is not recognizing the T: The type or namespace name "T" cannot be found
C# is not recognizing where : Constraints are not allowed on non generic declarations
Any idea if this works? What am I missing?
I also come up with:
public static T GetValueOrThrow<T>(this T? candidate) where T : class
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
Now C# complains about the candidate: The type T must be a non nullable value type in order to use it as parameter T in the generic type or method Nullable
This is not related to comparing.
public static T GetValueOrThrow<T>(this Nullable<T> candidate) where T : struct // can be this T? as well, but I think with explicit type is easier to understand
{
if (candidate.HasValue == false)
{
throw new ArgumentNullException(nameof(candidate));
}
return candidate.Value;
}
where T : class constrains to reference types, which can be null, but HasValue is property of Nullable type(which is value type as well as T).
I want to create a class like Nullable except for the moment that it could work with classes and structures:
public class MyWrapper<T>
{
public MyWrapper(T value)
{
Value = value;
}
public T Value { get; set; }
}
Then I want to add implicit conversion of Nullable to MyWrapper:
public static implicit operator MyWrapper<T> (Nullable<T> value)
{
return new MyWrapper<T>(value);
}
And of course, it fails because of Nullable restrictions:
Only non-nullable value type could be underlying of 'System.Nullable'
It is a pretty understandable error but in theory, I could convert any Nullable to MyWrapper, because of restrictions of Nullable harder than MyWrapper's restrictions.
So it there any workaround for Nullable to MyWrapper implicit conversion?
Why do I need to use MyWrapper for classes?
We use some sort of bad GraphQl on the backend and sent updation objects like this:
class UpdateProductRequest
{
public MyWrapper<string> Country {get;set;}
public MyWrapper<string> Title {get;set;}
}
So
new UpdateProductRequest
{
Title = "new title"
}
update the title, but not the country.
You can define implicit casting from underlying type to MyWrapper:
public static implicit operator MyWrapper<T> (T value)
{
return new MyWrapper<T>(value);
}
Now with such operator you can do this:
MyWrapper<int> w = new int?(5);
MyWrapper<int> w2 = (int?)null; //here casting method is not called
So initially the Nullable is casted down to the underlying type and then to MyWrapper. If it's null there is just null assignment to variable of typeMyWrapper which is class so it's valid. Nullable has special treatment by the compiler so it may look like a magic but it works.