How to avoid nullability warning when using existing APIs - c#

I get the following compiler warning when implementing the frameworks IComparer interface. I understand the reason why, but in my case I ensure that I initialize all values of the collection with an instance. How can I avoid the warning? I tried the bang operator but it doesn't seem to be allowed here. Maybe there is an alternative for collections which are known to have no null elements?
warning CS8767: Nullability of reference types in type of parameter
'x' of 'int FooComparer .Compare(Foo x, Foo y)' doesn't match
implicitly implemented member 'int IComparer.Compare(Foo? x,
Foo? y)' (possibly because of nullability attributes).
class FooComparer : IComparer<Foo>
{
public int Compare(Foo x, Foo y)
{
return y.Bar() - x.Bar();
}
}

Ok I got it solved. Need to declare the arguments as nullable as the contract needs, but in implementation have to use bang to tell what we expect:
class FooComparer : IComparer<Foo>
{
public int Compare(Foo? x, Foo? y)
{
return y!.Bar() - x!.Bar();
}
}
I wished that I could declare the non-nullability already in the API.

you can disable the warning if you are sure about nullability.
#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
public int Compare(Foo x, Foo y)
#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes).
you can refer to this if you want to disable this on the project level.

Related

Why I can't return a generic nullable value type and nullable reference type?

I have a method that should return a nullable reference type or a nullable value type:
private static T? FooValueOrReferenceType<T>(string input)
{
return null;
// the code above show this error
// Cannot convert null to type parameter 'T' because it could be
// a non-nullable value type. Consider using 'default(T)' instead.
}
I don't understand why I cant return null, since I have enabled nullable value types in the project
I know that adding constraint where T : struct, class can resolve the problem, but I want to understand why.
I noticed that this method signature:
private static T? FooValueType<T>(string input) where T : struct
compiled to:
private static Nullable<T> FooValueType<T>([System.Runtime.CompilerServices.Nullable(1)] string input) where T : struct
and this:
private static T? FooReferenceType<T>(string input) where T : class
compiles to:
private static T FooReferenceType<T>(string input) where T : class
What's the reason behind all this?
There is some question similar to this one, but the answers are not clear for a noob like me.
Ultimately, nullability means two fundamentally different things for reference-types and value-types. For value-types, T? means Nullable<T> - a wrapper layer around a T, with some compiler voodoo - and for reference-types, T? means simply T, but with some additional compiler context (and attributes added, to convey that intent). There is simply no way in IL of expressing these two things at the same time, and Nullable<T> and T cannot be used interchangeably in IL (even if C# makes it look like you can). Ultimately, "you just can't, IL doesn't work that way".
Your two experiments into the : class and : struct show these two incompatible variants nicely.

C# Is there a way to put a Null-conditional operator (?) on T?

I have an interface<T>. This interface has a method Compare(T x, T y).
x can never be null.
y has a large chance of being null.
I want to make this clear by using the Null-conditional operator ? on y: Compare(T x, T? y).
Is this possible and from what version of C#?
EDIT:
T can be a reference type and a value type.
I found the answer in a document suggested by #PanagiotisKanavos:
https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#generics
In C# 8.0, using T? without constraining T to be a struct or a class did not compile. That enabled the compiler to interpret T? clearly. That restriction was removed in C# 9.0, by defining the following rules for an unconstrained type parameter T:
If the type argument for T is a reference type, T? references the corresponding nullable reference type. For example, if T is a string, then T? is a string?.
If the type argument for T is a value type, T? references the same value type, T. For example, if T is an int, the T? is also an int.
If the type argument for T is a nullable reference type, T? references that same nullable reference type. For example, if T is a string?, then T? is also a string?.
If the type argument for T is a nullable value type, T? references that same nullable value type. For example, if T is a int?, then T? is also a int?.
For my question, this means I need to constrain T to a class, since I am on C#8.
Given that till this answer is written, it's not mentioned that if there is a condition on T like this
public interface IDoWork<T> : where T: ???
{...}
we'll assume it to be a reference type
Now that it's assumed that T is a reference type then you should do this
public int Compare(T x, T y)
{
if (y == null)
//Take decision accordingly.
else
//Take decision accordingly.
}
Compare methods do not take parameters as nullable types. They take pure instances and then inside the method decide according to the expected behaviour.

CS8766: Nullability of reference types in return type doesn't match implicitly implemented member

I've enabled Nullable References in my C# 8.0 project. I've also set all warnings as errors to force myself to address all such warnings. But one of them is confusing me. It is a dependency property that returns an interface pointer that might validly be null. Here is the property (inside a control called "LayerView")
private static readonly DependencyProperty ShapeBeingDrawnProperty = DependencyProperty.Register(
nameof(ShapeBeingDrawn),
typeof(IShape),
typeof(LayerView),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public IShape ShapeBeingDrawn
{
get => GetValue(ShapeBeingDrawnProperty) as IShape; // RIGHT SIDE OF "=>" UNDERLINED IN RED
set => SetValue(ShapeBeingDrawnProperty, value);
}
With nullable references enabled, this understandably causes a warning on the getter. The entire right side of the get is underlined in red and I get this build error
1>C:\Users\jmole\Documents\Dev\Mobile\Core\Views\LayerView.xaml.cs(429,13,429,16): error CS8766:
Nullability of reference types in return type of 'IShape? LayerView.ShapeBeingDrawn.get' doesn't
match implicitly implemented member 'IShape ILayerView.ShapeBeingDrawn.get' (possibly because of
nullability attributes).
First of all, what "implicitly" implemented member are they talking about? This is an explicitly implemented member. I am literally implementing it right here. But that aside, I tried to fix this as I've fixed many other such validly nullable properties: Make the property return a nullable reference
public IShape? ShapeBeingDrawn // ONLY CHANGE IS HERE
{
get => GetValue(ShapeBeingDrawnProperty) as IShape; // NOW, JUST "get" IS UNDERLINED IN RED
set => SetValue(ShapeBeingDrawnProperty, value);
}
Unfortunately, this does not help. I still get the same error but this time the only part underlined in red is just the left side of the getter (the word "get").
Finally I was able to eliminate this error by changing the property back to a normal reference and using a hard cast in the getter
public IShape ShapeBeingDrawn
{
get => (IShape)GetValue(ShapeBeingDrawnProperty);
set => SetValue(ShapeBeingDrawnProperty, value);
}
But now, any code that tries to set this property to null (which is valid) gets a compiler error and I have to address that.
private void LayerView_OnKeyDown(object sender, KeyEventArgs e)
{
// Don't do anything if we've disabled shape mouse/touch input.
if (e.Key != Key.Escape)
return;
UnselectAll();
ShapeBeingDrawn = null; // COMPILER NO LIKEY
}
So what is the proper way to integrate nullable references and dependency properties? Should I just #pragma warning disable around such problems? I'd like to avoid that if I can.
TL;DR
The problem occurs because property ShapeBeingDrawn is declared as non-nullable reference type IShape in the interface ILayerView, but in the class LayerView it is implemented as nullable reference type IShape?. Therefore compiler complains that return type of the property in the class does not match the return type in the interface.
To fix the problem we should ensure that both the interface and the class declare the property as nullable or non-nullable. If in one of them the declaration differs then we will again get compiler error.
Explanation
Why does the problem occur?
error CS8766: Nullability of reference types in return type of
'IShape? LayerView.ShapeBeingDrawn.get' doesn't match implicitly
implemented member 'IShape ILayerView.ShapeBeingDrawn.get' (possibly
because of nullability attributes).
From the error message we can see that there is an interface ILayerView with a non-nullable property ShapeBeignDrawn:
interface ILayerView
{
IShape ShapeBeingDrawn { get; set; }
}
and a class LayerView that implements this interface but declares property ShapeBeignDrawn as nullable:
public class LayerView
{
public IShape? ShapeBeingDrawn
{
get => GetValue(ShapeBeingDrawnProperty) as IShape;
set => SetValue(ShapeBeingDrawnProperty, value);
}
}
So the interface declares property as non-nullable but the class implements it as nullable. Therefore compiler complains that return type of the getter of the property in the class does not match the return type in the interface.
Question:
First of all, what "implicitly" implemented member are they talking
about?
Answer:
When we declare a property compiler implicitly generates for it two methods: get_PropertyName and set_PropertyName. Therefore compiler complains for the return type of the implicitly implemented getter method.
How should we fix the problem?
To fix the problem we should ensure that both an interface and a class declare a property as nullable or non-nullable. If in one of them the declaration differs then we will again get compiler error CS8766.
Here is a code sample that reproduces the problem and gives a hint how to fix it.

A problem with Nullable types and Generics in C# 8

After adding <Nullable>enable</Nullable> or #nullable enable, I ran into the following problem with my Generic methods:
This does not work:
public T? GetDefault<T>()
{
return default;
}
This works with warning:
public T GetDefault<T>()
{
return default;
}
This works individually, but not together.
public T? GetDefault<T>() where T : class
{
return default;
}
public T? GetDefault<T>() where T : struct
{
return default;
}
Logically, the first method should work.
What is the correct way (in any framework) out of this situation without creating several methods and suppressing warnings?
[MaybeNull] attribute only works with .Net Core 3.0+.
Also, I asked this questions here
T? can only be used when the type parameter is known to be of a reference type or of a value type. Otherwise, we don't know whether to see it as a System.Nullable<T> or as a nullable reference type T.
Instead you can express this scenario in C# 8 by using the [MaybeNull] attribute.
#nullable enable
using System.Diagnostics.CodeAnalysis;
public class C
{
[return: MaybeNull]
public T GetDefault<T>()
{
return default!; // ! just removes warning
}
}
This attribute is only included in .NET Core 3.0+, but it is possible to declare and use the attribute internal to your project (although this is not officially supported, there's no reason to assume the behavior will break down the line). To do so, you can just add a namespace+class declaration to your code similar to the following:
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class MaybeNullAttribute : Attribute { }
}
Explanation of the problem
The problem in your first code sample occurs because compiler differently handles nullable value types and nullable reference types:
Nullable value type T? is represented by type Nullable<T>.
Nullable reference type T? is the same type T but with a compiler-generated attribute annotating it.
Compiler cannot generate code to cover this both cases at the same time, therefore a compilation error occurs. And this error forces us to specify class or struct constraint. This behavior is also stated in the C# specification:
For a type parameter T, T? is only allowed if T is known to be a value
type or known to be a reference type.
A good explanation of this problem can be found in this article: Try out Nullable Reference Types. Scroll to the paragraph "The issue with T?".
A workaround to fix the problem
The next workaround can be used if you don't want to create two methods with different names and suppress warnings:
// An overload that will be used by reference types.
public T? GetDefault<T>(T? t = default) where T : class
{
return default;
}
// An overload that will be used by value types.
public T? GetDefault<T>(T? t = default) where T : struct
{
return default;
}
Here we added an argument t to the methods GetDefault to make compiler being able to differ these two methods. Now we can use methods GetDefault and compiler will define which overload to use. The drawback of this approach is that GetDefault methods have unusable argument t.
It seems the best solution to this problem will only be in C# 9 as T??
Links:
1. https://github.com/dotnet/csharplang/issues/3471#issuecomment-631722668
2. https://github.com/dotnet/csharplang/issues/3297
At the moment, a working solution was provided by Rikki Gibson. It implies additional code, but it works as it should.

Why is Nullable<T> nullable? Why it cannot be reproduced?

When I write
Nullable<Nullable<DateTime>> test = null;
I get a compilation error:
The type 'System.Datetime?' must be a non-nullable value type in order to use it as a paramreter 'T' in the generic type or method 'System.Nullable<T>'
But Nullable<T> is a struct so it's supposed to be non-nullable.
So I tried to create this struct:
public struct Foo<T> where T : struct
{
private T value;
public Foo(T value)
{
this.value = value;
}
public static explicit operator Foo<T>(T? value)
{
return new Foo<T>(value.Value);
}
public static implicit operator T?(Foo<T> value)
{
return new Nullable<T>(value.value);
}
}
Now when I write
Nullable<Foo<DateTime>> test1 = null;
Foo<Nullable<DateTime>> test2 = null;
Foo<DateTime> test3 = null;
The first line is ok but for the second and third lines I get the two following compilation error:
The type 'System.DateTime?' must be a non-nullable value type in order to use it as a parameter 'T' in the generic type or method 'MyProject.Foo<T>' (second line only)
and
Cannot convert null to 'MyProject.Foo<System.DateTime?> because it is a non-nullable value type'
Foo<Nullable<DateTime>> test = new Foo<DateTime?>();
doesn't work neither event if Nullable<DateTime> is a struct.
Conceptually, I can understand why Nullable<T> is nullable, it avoids having stuffs like DateTime?????????? however I can still have List<List<List<List<List<DateTime>>>>>...
So why this limitation and why can't I reproduce this behavior in Foo<T>? Is this limitation enforced by the compiler or is it intrinsic in Nullable<T> code?
I read this question but it just says that it is not possible none of the answers say fundamentally why it's not possible.
But Nullable is a struct so it's supposed to be non-nullable.
Nullable<T> is indeed a struct, but the precise meaning of the generic struct constraint as stated in the docs is:
The type argument must be a value type. Any value type except Nullable can be specified. See Using Nullable Types (C# Programming Guide) for more information.
For the same reason, your line
Foo<Nullable<DateTime>> test2 = null;
results in the compiler error you are seeing, because your generic struct constraint restricts your generic T argument in a way so Nullable<DateTime> must not be specified as an actual argument.
A rationale for this may have been to make calls such as
Nullable<Nullable<DateTime>> test = null;
less ambiguous: Does that mean you want to set test.HasValue to false, or do you actually want to set test.HasValue to true and test.Value.HasValue to false? With the given restriction to non-nullable type arguments, this confusion does not occur.
Lastly, the null assignment works with Nullable<T> because - as implied by the selected answers and their comments to this SO question and this SO question - the Nullable<T> type is supported by some compiler magic.
The error is saying that the type-parameter of Nullable should be not-nullable.
What you're doing, is creating a Nullable type which has a nullable type-parameter, which is not allowed:
Nullable<Nullable<DateTime>>
is the same as
Nullable<DateTime?>
Which is quite pointless. Why do you want to have a nullable type for a type that is already nullable ?
Nullable is just a type that has been introduced in .NET 2.0 so that you are able to use ' nullable value types'. For instance, if you have a method wich has a datetime-parameter that is optional; instead of passing a 'magic value' like DateTime.MinValue, you can now pass null to that method if you do not want to use that parameter.
In generic classes where T: struct means that type T cannot be null.
However Nullable types are designed to add nullability to structs. Technically they are structs, but they behave like they may contain null value. Because of this ambiguity the use of nullables is not allowed with where T: struct constraint - see Constraints on Type Parameters
Nullable types are not just generic structs with special C# compiler support. Nullable types are supported by CLR itself (see CLR via C# by Jeffrey Richter), and looks like this special CLR support makes them non-recursive.
CLR supports special boxing/unboxing rules int? i = 1; object o = i will put int value into variable o and not Nullable<int> value. In case of multiple nullables - should o = (int??)1; contain int or int? value?
CLR has special support for calling GetType and interface members - it calls methods of underlying type. This actually leads to situation when Nullable.GetType() throws a NullObjectReference exception, when it has NullValueFlag.
As for C#, there are a lot of features in C# that are hard-coded for nullable types.
Based on this article Nullable Types (C# Programming Guide) the primary goal of introducing nullable types is to add null support for the types that do not support nulls. Logically, since DateTime? already supports nulls it shouldn't be allowed to be "more" nullable.
This document also plainly states that
Nested nullable types are not allowed. The following line will not compile: Nullable<Nullable<int>> n;
Special C# features of nullable types:
C# has special ?? operator. Should (int???)null ?? (int)1 resolve to (int??)1 or to (int)1 value?
Nullables have special System.Nullable.GetValueOrDefault property. What should it return for nested nullables?
Special processing for ? == null and ? != null operators. If the Nullable<Nullable<T>> contains Nullable<T> value, but this value is null, what should HasValue property return? What should be the result of comparison with null?
Special implicit conversions. Should int?? i = 10 be implicitly convertible?
Explicit conversions. Should be int i = (int??)10; supported?
Special support for bool? type Using nullable types. E.g. (bool?)null | (bool?)true == true.
So, should CLR support recursive GetType() call? Should it remove Nullable wrappers when boxing value? If it should do for one-level values, why don't for all other levels as well? Too many options to consider, too many recursive processing.
The easiest solution is to make Nullable<Nullable<T>> non-compilable.

Categories

Resources