Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# 8 nullable + Dictionary<>

My code looks something like this:

#nullable enable
class MyClass<KEY, ITEM>
{
    readonly Dictionary<KEY, ITEM> Map = new Dictionary<KEY, ITEM>();
    public void Process(KEY key, ITEM item)
    {
        if (key != null)
        {
            Map[key] = item;
        }
    }
}
#nullable disable

The compiler is not thrilled with this, it gives me the warning

type 'KEY' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>

which I can certainly understand. The problem is, sending null for the 'key' parameter to Process() is perfectly valid so I can't add the "where KEY: notnull" constraint to the class. (and MyClass needs to accept both classes and structs for the KEY type parameter)

The only thing I can think of is this:

#nullable enable
class MyClass<KEY, ITEM>
{
#nullable disable
    readonly Dictionary<KEY, ITEM> Map = new Dictionary<KEY, ITEM>();
#nullable enable
    public void Process(KEY key, ITEM item)
    {
        if (key != null)
        {
            Map[key] = item;
        }
    }
}
#nullable disable

Which keeps the compiler happy, but then I don't have all those nice C# 8 null checks. For example, it allows me to write this code:

Map[default] = item;

and the compiler doesn't bat an eye.

How can I tell the compiler that the 'KEY' type parameter to Dictionary<> should disallow nulls, but still allow KEY values to be null in the outer class?

EDIT

I want to use the new C# 8 nullability features so that I catch as many null pointers at compile time as possible (instead of waiting for runtime exceptions).

FURTHER EDIT

The direction I'm headed right now, is to put a thin layer around Dictionary to enforce the null restrictions and use it instead of Dictionary<>

#nullable enable
public class CheckDictionary<KEYTYPE, VALUETYPE>
{
#nullable disable
    readonly Dictionary<KEYTYPE, VALUETYPE> Dictionary = new Dictionary<KEYTYPE, VALUETYPE>();
#nullable enable

    public VALUETYPE this[[DisallowNull] KEYTYPE key]
    {
        get { return Dictionary[key]; }
        set { Dictionary[key] = value; }
    }

    public bool Remove([DisallowNull] KEYTYPE key)
    { return Dictionary.Remove(key); }

    public bool TryGetValue([DisallowNull] KEYTYPE key, out VALUETYPE value)
    { return Dictionary.TryGetValue(key, out value); }

    public List<VALUETYPE> Values => Dictionary.Values.ToList();
}
like image 321
Betty Crokker Avatar asked Oct 29 '25 13:10

Betty Crokker


1 Answers

I think that in your case the next approach can be used:

  • Constrain type parameter TKey to be notnull. As a result the compiler will enfoce null checks against TKey.
  • Add AllowNullAttribute to the parameter TKey key of the method Process. As a result code that passes null key to the method Process will not produce warnings.

Here is the code with comments:

class MyClass<TKey, TItem> where TKey : notnull
{
    // With "notnull" constraint type parameter "TKey" matches type constraint
    // of the class Dictionary<TKey, TValue>, therefore compiler does not
    // generate the next warning:
    //   The type 'TKey' cannot be used as type parameter 'TKey' in the 
    //   generic type or method 'Dictionary<TKey, TValue>'. Nullability
    //   of type argument 'TKey' doesn't match 'notnull' constraint.
    readonly Dictionary<TKey, TItem> Map = new Dictionary<TKey, TItem>();

    public void Process([System.Diagnostics.CodeAnalysis.AllowNull] TKey key, TItem item)
    {
        // "TKey key" is marked with [AllowNull] attribute. Therefore if you delete
        // null check "key != null" compiler will produce the next warning on the line
        // "Map[key] = item":
        //   Possible null reference argument for parameter 'key' in
        //   'TItem Dictionary<TKey, TItem>.this[TKey key]'.
        if (key != null) 
            Map[key] = item;

        // Because "TKey" is constrained to be "notnull", this line of code
        // produces the next warning:
        //   Possible null reference argument for parameter 'key' in
        //   'TItem Dictionary<TKey, TItem>.this[TKey key]'.
        Map[default] = item;
    }
}

static class DemoClass
{
    public static void Demo()
    {
        MyClass<string, int> mc1 = new MyClass<string, int>();
        // This line does not produce a warning, because "TKey key" is marked
        // with [AllowNull] attribute.
        mc1.Process(null, 0);
        // This line does not produce a warning too.
        mc1.Process(GetNullableKey(), 0);

        // Usage of "MyClass" with value type "TKey" is also allowed.
        // Compiler does not produce warnings.
        MyClass<int, int> mc2 = new MyClass<int, int>();
        mc2.Process(0, 1);
    }

    public static string? GetNullableKey() => null;
}

So using such approach we:

  • enfoced null checks against TKey in the MyClass;
  • allowed to pass null key to the Process method without getting warnings.
like image 89
Iliar Turdushev Avatar answered Oct 31 '25 11:10

Iliar Turdushev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!