How to use nullable reference type attributes in C#7

Viewed 504

I got a library which is distributed as source code and I can't force my users to use C# version 8.

I still want to improve the experience for users who are using C# 8 with nullable reference types.

Or in other words: My library must compile with C# 7 but also include information about nullable reference types.

What I have done so far is to define the attributes, MaybeNull etc, and my source files start with

#if (NETSTANDARD && !NETSTANDARD2_0 && !NETSTANDARD1_6) || (NETCOREAPP && !NETCOREAPP2_0 && !NETCOREAPP2_1 && NETCOREAPP2_2)
#nullable disable warnings
#endif

And I have put attributes on properties, arguments return types etc.

[return: MaybeNull]
public string Function([AllowNull) param) ...

This works but since I can't use ? and ! there are few things I haven't found a solution to

  • Generics, there doesn't seem to be a way to mark generic type parameters as nullable with attributes. i.e. the attribute version of public async Task<string?> Method(KeyValuePair<string, string?> data)
  • Attributes on properties and fields are ignored inside the class (but works from outside)
public class C {
    [AllowNull, MaybeNull]
    private string _field;

    [AllowNull, MaybeNull]
    public string Prop1 => _field; // CS8603, Possible null reference return

    [AllowNull, MaybeNull]
    public string Prop2 { get; set; }

    public C() { 
        // CS8618, Non-nullable property 'Prop2' is uninitialized
        // CS8618, Non-nullable field '_field' is uninitialized
    }
}
  • warning on locals string local = null; etc

nullable disable warnings solves the last 2 points for my users so it is not important but generics is an issue since I have a lot of async methods. Is there a way to mark nullable reference types in generics without using ??

1 Answers

You can compile your library using C# 8 and have others consume those binaries from C# 7 (or earlier). The limiting factor isn't the language but the framework (unless they're compiling your source using an earlier compiler).

NRT annotations use attributes which are defined in the generated assembly automatically. You don't usually see these, and they're matched by name rather than Type, meaning that you can consume annotations from multiple assemblies even though they have different annotation types (with the same, well-known names).

That is distinct from attribute such as MaybeNull and NotNullWhen etc, which are not produced by the compiler, however are also matched by full name meaning you can define them yourself or use a library such as Nullable.

All that to say, you should be able to use ?, ! and avoid the #if/#endif stuff you mention.

Related