为什么 Dictionary不显示?支持空键吗?

发布于 2024-10-11 03:43:46 字数 1982 浏览 3 评论 0原文

首先,为什么Dictionary不支持单个空键?

其次,是否有现有的类似字典的集合可以做到这一点?

我想存储一个“空”或“缺失”或“默认”System.Type,认为null 对此很有效。


更具体地说,我编写了此类:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        var type = obj.GetType();

        foreach (var entry in cases)
        {
            if (entry.Key == null || type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                break;
            }
        }
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action());
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action<T> action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action((T)x));
    }

    public static KeyValuePair<Type, Action<object>> Default(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(null, x => action());
    }
}

用于切换类型。有两种使用方法:

  1. 静态。只需调用 Switch.Execute(yourObject, Switch.Case(x => x.Action()))
  2. 预编译即可。创建一个开关,然后将其与 switchInstance.Execute(yourObject) 一起使用

效果很好除非当您尝试将默认情况添加到“预编译”版本时(空参数例外)。

Firstly, why doesn't Dictionary<TKey, TValue> support a single null key?

Secondly, is there an existing dictionary-like collection that does?

I want to store an "empty" or "missing" or "default" System.Type, thought null would work well for this.


More specifically, I've written this class:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        var type = obj.GetType();

        foreach (var entry in cases)
        {
            if (entry.Key == null || type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                break;
            }
        }
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action());
    }

    public static KeyValuePair<Type, Action<object>> Case<T>(Action<T> action)
    {
        return new KeyValuePair<Type, Action<object>>(typeof(T), x => action((T)x));
    }

    public static KeyValuePair<Type, Action<object>> Default(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(null, x => action());
    }
}

For switching on types. There are two ways to use it:

  1. Statically. Just call Switch.Execute(yourObject, Switch.Case<YourType>(x => x.Action()))
  2. Precompiled. Create a switch, and then use it later with switchInstance.Execute(yourObject)

Works great except when you try to add a default case to the "precompiled" version (null argument exception).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

赴月观长安 2024-10-18 03:43:46
  1. 原因
    如前所述,问题在于 Dictionary 需要 Object.GetHashCode() 方法的实现。 null 没有实现,因此没有关联的哈希码。

  2. 解决方案:我使用了类似于使用泛型的 NullObject 模式的解决方案,使您能够无缝地使用字典(不需要不同的字典实现)。

您可以这样使用它:

var dict = new Dictionary<NullObject<Type>, string>();
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";
dict[null] = "null type";

Assert.AreEqual("int type", dict[typeof(int)]);
Assert.AreEqual("string type", dict[typeof(string)]);
Assert.AreEqual("null type", dict[null]);

您只需要在一生中创建一次这个结构:

public struct NullObject<T>
{
    [DefaultValue(true)]
    private bool isnull;// default property initializers are not supported for structs

    private NullObject(T item, bool isnull) : this()
    {
        this.isnull = isnull;
        this.Item = item;
    }
      
    public NullObject(T item) : this(item, item == null)
    {
    }

    public static NullObject<T> Null()
    {
        return new NullObject<T>();
    }

    public T Item { get; private set; }

    public bool IsNull()
    {
        return this.isnull;
    }

    public static implicit operator T(NullObject<T> nullObject)
    {
        return nullObject.Item;
    }

    public static implicit operator NullObject<T>(T item)
    {
        return new NullObject<T>(item);
    }

    public override string ToString()
    {
        return (Item != null) ? Item.ToString() : "NULL";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return this.IsNull();

        if (!(obj is NullObject<T>))
            return false;

        var no = (NullObject<T>)obj;

        if (this.IsNull())
            return no.IsNull();

        if (no.IsNull())
            return false;

        return this.Item.Equals(no.Item);
    }

    public override int GetHashCode()
    {
        if (this.isnull)
            return 0;

        var result = Item.GetHashCode();

        if (result >= 0)
            result++;

        return result;
    }
}
  1. Why:
    As described before, the problem is that Dictionary requires an implementation of the Object.GetHashCode() method. null does not have an implementation, therefore no hash code associated.

  2. Solution: I have used a solution similar to a NullObject pattern using generics that enables you to use the dictionary seamlessly (no need for a different dictionary implementation).

You can use it like this:

var dict = new Dictionary<NullObject<Type>, string>();
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";
dict[null] = "null type";

Assert.AreEqual("int type", dict[typeof(int)]);
Assert.AreEqual("string type", dict[typeof(string)]);
Assert.AreEqual("null type", dict[null]);

You just need to create this struct once in a lifetime :

public struct NullObject<T>
{
    [DefaultValue(true)]
    private bool isnull;// default property initializers are not supported for structs

    private NullObject(T item, bool isnull) : this()
    {
        this.isnull = isnull;
        this.Item = item;
    }
      
    public NullObject(T item) : this(item, item == null)
    {
    }

    public static NullObject<T> Null()
    {
        return new NullObject<T>();
    }

    public T Item { get; private set; }

    public bool IsNull()
    {
        return this.isnull;
    }

    public static implicit operator T(NullObject<T> nullObject)
    {
        return nullObject.Item;
    }

    public static implicit operator NullObject<T>(T item)
    {
        return new NullObject<T>(item);
    }

    public override string ToString()
    {
        return (Item != null) ? Item.ToString() : "NULL";
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return this.IsNull();

        if (!(obj is NullObject<T>))
            return false;

        var no = (NullObject<T>)obj;

        if (this.IsNull())
            return no.IsNull();

        if (no.IsNull())
            return false;

        return this.Item.Equals(no.Item);
    }

    public override int GetHashCode()
    {
        if (this.isnull)
            return 0;

        var result = Item.GetHashCode();

        if (result >= 0)
            result++;

        return result;
    }
}
暖树树初阳… 2024-10-18 03:43:46

我突然想到,你最好的答案可能就是跟踪是否定义了默认情况:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;
    private Action<object> defaultCase;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            if (entry.Key == null)
                defaultCase = entry.Value;
            else
                _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
        else if (defaultCase != null)
            defaultCase(obj);
    }

...

班级的其余部分将保持不变。

It just hit me that your best answer is probably to just keep track of whether a default case has been defined:

class Switch
{
    private Dictionary<Type, Action<object>> _dict;
    private Action<object> defaultCase;

    public Switch(params KeyValuePair<Type, Action<object>>[] cases)
    {
        _dict = new Dictionary<Type, Action<object>>(cases.Length);
        foreach (var entry in cases)
            if (entry.Key == null)
                defaultCase = entry.Value;
            else
                _dict.Add(entry.Key, entry.Value);
    }

    public void Execute(object obj)
    {
        var type = obj.GetType();
        if (_dict.ContainsKey(type))
            _dict[type](obj);
        else if (defaultCase != null)
            defaultCase(obj);
    }

...

The whole rest of your class would remain untouched.

知足的幸福 2024-10-18 03:43:46

它不支持它,因为字典对键进行散列以确定索引,而它不能对空值执行此操作。

一个快速解决方法是创建一个虚拟类,并插入键值??虚拟类实例。
需要更多有关您实际尝试执行的操作的信息,以提供不那么“hacky”的修复

It doesn't support it because the dictionary hashes the key to determine the index, which it can't do on a null value.

A quick fix would be to create a dummy class, and insert the key value ?? dummyClassInstance.
Would need more information about what you're actually trying to do to give a less 'hacky' fix

几度春秋 2024-10-18 03:43:46

NameValueCollection 可以采用 null 键。

NameValueCollection could take null key.

清旖 2024-10-18 03:43:46

如果您确实想要一个允许空键的字典,这是我的快速实现(没有写得很好或没有经过充分测试):

class NullableDict<K, V> : IDictionary<K, V>
{
    Dictionary<K, V> dict = new Dictionary<K, V>();
    V nullValue = default(V);
    bool hasNull = false;

    public NullableDict()
    {
    }

    public void Add(K key, V value)
    {
        if (key == null)
            if (hasNull)
                throw new ArgumentException("Duplicate key");
            else
            {
                nullValue = value;
                hasNull = true;
            }
        else
            dict.Add(key, value);
    }

    public bool ContainsKey(K key)
    {
        if (key == null)
            return hasNull;
        return dict.ContainsKey(key);
    }

    public ICollection<K> Keys
    {
        get 
        {
            if (!hasNull)
                return dict.Keys;

            List<K> keys = dict.Keys.ToList();
            keys.Add(default(K));
            return new ReadOnlyCollection<K>(keys);
        }
    }

    public bool Remove(K key)
    {
        if (key != null)
            return dict.Remove(key);

        bool oldHasNull = hasNull;
        hasNull = false;
        return oldHasNull;
    }

    public bool TryGetValue(K key, out V value)
    {
        if (key != null)
            return dict.TryGetValue(key, out value);

        value = hasNull ? nullValue : default(V);
        return hasNull;
    }

    public ICollection<V> Values
    {
        get
        {
            if (!hasNull)
                return dict.Values;

            List<V> values = dict.Values.ToList();
            values.Add(nullValue);
            return new ReadOnlyCollection<V>(values);
        }
    }

    public V this[K key]
    {
        get
        {
            if (key == null)
                if (hasNull)
                    return nullValue;
                else
                    throw new KeyNotFoundException();
            else
                return dict[key];
        }
        set
        {
            if (key == null)
            {
                nullValue = value;
                hasNull = true;
            }
            else
                dict[key] = value;
        }
    }

    public void Add(KeyValuePair<K, V> item)
    {
        Add(item.Key, item.Value);
    }

    public void Clear()
    {
        hasNull = false;
        dict.Clear();
    }

    public bool Contains(KeyValuePair<K, V> item)
    {
        if (item.Key != null)
            return ((ICollection<KeyValuePair<K, V>>)dict).Contains(item);
        if (hasNull)
            return EqualityComparer<V>.Default.Equals(nullValue, item.Value);
        return false;
    }

    public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
    {
        ((ICollection<KeyValuePair<K, V>>)dict).CopyTo(array, arrayIndex);
        if (hasNull)
            array[arrayIndex + dict.Count] = new KeyValuePair<K, V>(default(K), nullValue);
    }

    public int Count
    {
        get { return dict.Count + (hasNull ? 1 : 0); }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<K, V> item)
    {
        V value;
        if (TryGetValue(item.Key, out value) && EqualityComparer<V>.Default.Equals(item.Value, value))
            return Remove(item.Key);
        return false;
    }

    public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
    {
        if (!hasNull)
            return dict.GetEnumerator();
        else
            return GetEnumeratorWithNull();
    }

    private IEnumerator<KeyValuePair<K, V>> GetEnumeratorWithNull()
    {
        yield return new KeyValuePair<K, V>(default(K), nullValue);
        foreach (var kv in dict)
            yield return kv;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

If you really want a dictionary that allows null keys, here's my quick implementation (not well-written or well-tested):

class NullableDict<K, V> : IDictionary<K, V>
{
    Dictionary<K, V> dict = new Dictionary<K, V>();
    V nullValue = default(V);
    bool hasNull = false;

    public NullableDict()
    {
    }

    public void Add(K key, V value)
    {
        if (key == null)
            if (hasNull)
                throw new ArgumentException("Duplicate key");
            else
            {
                nullValue = value;
                hasNull = true;
            }
        else
            dict.Add(key, value);
    }

    public bool ContainsKey(K key)
    {
        if (key == null)
            return hasNull;
        return dict.ContainsKey(key);
    }

    public ICollection<K> Keys
    {
        get 
        {
            if (!hasNull)
                return dict.Keys;

            List<K> keys = dict.Keys.ToList();
            keys.Add(default(K));
            return new ReadOnlyCollection<K>(keys);
        }
    }

    public bool Remove(K key)
    {
        if (key != null)
            return dict.Remove(key);

        bool oldHasNull = hasNull;
        hasNull = false;
        return oldHasNull;
    }

    public bool TryGetValue(K key, out V value)
    {
        if (key != null)
            return dict.TryGetValue(key, out value);

        value = hasNull ? nullValue : default(V);
        return hasNull;
    }

    public ICollection<V> Values
    {
        get
        {
            if (!hasNull)
                return dict.Values;

            List<V> values = dict.Values.ToList();
            values.Add(nullValue);
            return new ReadOnlyCollection<V>(values);
        }
    }

    public V this[K key]
    {
        get
        {
            if (key == null)
                if (hasNull)
                    return nullValue;
                else
                    throw new KeyNotFoundException();
            else
                return dict[key];
        }
        set
        {
            if (key == null)
            {
                nullValue = value;
                hasNull = true;
            }
            else
                dict[key] = value;
        }
    }

    public void Add(KeyValuePair<K, V> item)
    {
        Add(item.Key, item.Value);
    }

    public void Clear()
    {
        hasNull = false;
        dict.Clear();
    }

    public bool Contains(KeyValuePair<K, V> item)
    {
        if (item.Key != null)
            return ((ICollection<KeyValuePair<K, V>>)dict).Contains(item);
        if (hasNull)
            return EqualityComparer<V>.Default.Equals(nullValue, item.Value);
        return false;
    }

    public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
    {
        ((ICollection<KeyValuePair<K, V>>)dict).CopyTo(array, arrayIndex);
        if (hasNull)
            array[arrayIndex + dict.Count] = new KeyValuePair<K, V>(default(K), nullValue);
    }

    public int Count
    {
        get { return dict.Count + (hasNull ? 1 : 0); }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(KeyValuePair<K, V> item)
    {
        V value;
        if (TryGetValue(item.Key, out value) && EqualityComparer<V>.Default.Equals(item.Value, value))
            return Remove(item.Key);
        return false;
    }

    public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
    {
        if (!hasNull)
            return dict.GetEnumerator();
        else
            return GetEnumeratorWithNull();
    }

    private IEnumerator<KeyValuePair<K, V>> GetEnumeratorWithNull()
    {
        yield return new KeyValuePair<K, V>(default(K), nullValue);
        foreach (var kv in dict)
            yield return kv;
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
栖迟 2024-10-18 03:43:46

NHibernate 附带了一个 NullableDictionary。那对我来说是这样的。

https://github.com/nhibernate/nhibernate -core/blob/master/src/NHibernate/Util/NullableDictionary.cs

过期以后 2024-10-18 03:43:46

字典将对键提供的哈希值进行哈希处理以获取索引,如果为 null ,哈希函数无法返回有效值,这就是为什么它不支持键中为 null 的原因。

Dictionary will hash the key supplie to get the index , in case of null , hash function can not return a valid value that's why it does not support null in key.

静谧 2024-10-18 03:43:46

在您的情况下,您尝试使用 null 作为标记值(“默认”),而不是实际需要将 null 存储为值。与其费力地创建一个可以接受空键的字典,为什么不直接创建自己的哨兵值呢?这是“空对象模式”的变体:

class Switch
{
    private class DefaultClass { }

    ....

    public void Execute(object obj)
    {
        var type = obj.GetType();
        Action<object> value;
        // first look for actual type
        if (_dict.TryGetValue(type, out value) ||
        // look for default
            _dict.TryGetValue(typeof(DefaultClass), out value))
            value(obj);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        var type = obj.GetType();

        foreach (var entry in cases)
        {
            if (entry.Key == typeof(DefaultClass) || type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                break;
            }
        }
    }

    ...

    public static KeyValuePair<Type, Action<object>> Default(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(new DefaultClass(), x => action());
    }
}

请注意,您的第一个 Execute 函数与第二个函数有很大不同。您可能想要这样的东西:

    public void Execute(object obj)
    {
        Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)_dict);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)cases);
    }

    public static void Execute(object obj, IEnumerable<KeyValuePair<Type, Action<object>>> cases)
    {
        var type = obj.GetType();
        Action<object> defaultEntry = null;
        foreach (var entry in cases)
        {
            if (entry.Key == typeof(DefaultClass))
                defaultEntry = entry.Value;
            if (type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                return;
            }
        }
        if (defaultEntry != null)
            defaultEntry(obj);
    }

In your case you are trying to use null as a sentinel value (a "default") instead of actually needing to store null as a value. Rather than go to the hassle of creating a dictionary that can accept null keys, why not just create your own sentinel value. This is a variation on the "null object pattern":

class Switch
{
    private class DefaultClass { }

    ....

    public void Execute(object obj)
    {
        var type = obj.GetType();
        Action<object> value;
        // first look for actual type
        if (_dict.TryGetValue(type, out value) ||
        // look for default
            _dict.TryGetValue(typeof(DefaultClass), out value))
            value(obj);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        var type = obj.GetType();

        foreach (var entry in cases)
        {
            if (entry.Key == typeof(DefaultClass) || type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                break;
            }
        }
    }

    ...

    public static KeyValuePair<Type, Action<object>> Default(Action action)
    {
        return new KeyValuePair<Type, Action<object>>(new DefaultClass(), x => action());
    }
}

Note that your first Execute function differs significantly from your second. It may be the case that you want something like this:

    public void Execute(object obj)
    {
        Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)_dict);
    }

    public static void Execute(object obj, params KeyValuePair<Type, Action<object>>[] cases)
    {
        Execute(obj, (IEnumerable<KeyValuePair<Type, Action<object>>>)cases);
    }

    public static void Execute(object obj, IEnumerable<KeyValuePair<Type, Action<object>>> cases)
    {
        var type = obj.GetType();
        Action<object> defaultEntry = null;
        foreach (var entry in cases)
        {
            if (entry.Key == typeof(DefaultClass))
                defaultEntry = entry.Value;
            if (type.IsAssignableFrom(entry.Key))
            {
                entry.Value(obj);
                return;
            }
        }
        if (defaultEntry != null)
            defaultEntry(obj);
    }
苏佲洛 2024-10-18 03:43:46

几天前我遇到了这个线程,需要一个经过深思熟虑且聪明的解决方案来处理空键。我花了时间自己实现了一个来处理更多场景。

您可以在我的 pre- 中找到我目前对 NullableKeyDictionary 的实现发布包 Teronis.NetStandard.Collections (0.1.7-alpha.37)

实现

public class NullableKeyDictionary<KeyType, ValueType> : INullableKeyDictionary<KeyType, ValueType>, IReadOnlyNullableKeyDictionary<KeyType, ValueType>, IReadOnlyCollection<KeyValuePair<INullableKey<KeyType>, ValueType>> where KeyType : notnull

public interface INullableKeyDictionary<KeyType, ValueType> : IDictionary<KeyType, ValueType>, IDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull

public interface IReadOnlyNullableKeyDictionary<KeyType, ValueType> : IReadOnlyDictionary<KeyType, ValueType>, IReadOnlyDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull

用法(Xunit 测试摘录)

// Assign.
var dictionary = new NullableKeyDictionary<string, string>();
IDictionary<string, string> nonNullableDictionary = dictionary;
INullableKeyDictionary<string, string> nullableDictionary = dictionary;

// Assert.
dictionary.Add("value");
/// Assert.Empty does cast to IEnumerable, but our implementation of IEnumerable 
/// returns an enumerator of type <see cref="KeyValuePair{NullableKey, TValue}"/>.
/// So we test on correct enumerator implementation wether it can move or not.
Assert.False(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("value"));

Assert.True(dictionary.Remove());
Assert.Empty(nullableDictionary);

dictionary.Add("key", "value");
Assert.True(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("key", "value"));

dictionary.Add("value");
Assert.Equal(1, nonNullableDictionary.Count);
Assert.Equal(2, nullableDictionary.Count);

Add(..) 存在以下重载:

void Add([AllowNull] KeyType key, ValueType value)
void Add(NullableKey<KeyType> key, [AllowNull] ValueType value)
void Add([AllowNull] ValueType value); // Shortcut for adding value with null key.

此类的行为应与字典确实如此。

对于 Remove(..) 键,您可以使用以下重载:

void Remove([AllowNull] KeyType key)
void Remove(NullableKey<KeyType> key)
void Remove(); // Shortcut for removing value with null key.

索引器接受 [AllowNull] KeyTypeNullableKey。因此,支持的场景,就像其他帖子中所述的那样,是受支持的:

var dict = new NullableKeyDictionary<Type, string>
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";

dict[null] = "null type";
// Or:
dict[NullableKey<Type>.Null] = "null type";

我非常感谢反馈和改进建议。 :)

I come across this thread some days ago and needed a well thought out and clever solution to handle null keys. I took the time and implemented one by me to handle more scenarios.

You can find my implementation of NullableKeyDictionary currently in my pre-release package Teronis.NetStandard.Collections (0.1.7-alpha.37).

Implementation

public class NullableKeyDictionary<KeyType, ValueType> : INullableKeyDictionary<KeyType, ValueType>, IReadOnlyNullableKeyDictionary<KeyType, ValueType>, IReadOnlyCollection<KeyValuePair<INullableKey<KeyType>, ValueType>> where KeyType : notnull

public interface INullableKeyDictionary<KeyType, ValueType> : IDictionary<KeyType, ValueType>, IDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull

public interface IReadOnlyNullableKeyDictionary<KeyType, ValueType> : IReadOnlyDictionary<KeyType, ValueType>, IReadOnlyDictionary<NullableKey<KeyType>, ValueType> where KeyType : notnull

Usage (Excerpt of the Xunit test)

// Assign.
var dictionary = new NullableKeyDictionary<string, string>();
IDictionary<string, string> nonNullableDictionary = dictionary;
INullableKeyDictionary<string, string> nullableDictionary = dictionary;

// Assert.
dictionary.Add("value");
/// Assert.Empty does cast to IEnumerable, but our implementation of IEnumerable 
/// returns an enumerator of type <see cref="KeyValuePair{NullableKey, TValue}"/>.
/// So we test on correct enumerator implementation wether it can move or not.
Assert.False(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("value"));

Assert.True(dictionary.Remove());
Assert.Empty(nullableDictionary);

dictionary.Add("key", "value");
Assert.True(nonNullableDictionary.GetEnumerator().MoveNext());
Assert.NotEmpty(nullableDictionary);
Assert.Throws<ArgumentException>(() => dictionary.Add("key", "value"));

dictionary.Add("value");
Assert.Equal(1, nonNullableDictionary.Count);
Assert.Equal(2, nullableDictionary.Count);

The following overloads exists for Add(..):

void Add([AllowNull] KeyType key, ValueType value)
void Add(NullableKey<KeyType> key, [AllowNull] ValueType value)
void Add([AllowNull] ValueType value); // Shortcut for adding value with null key.

This class should behave same and intuitive as the dictionary does.

For Remove(..) keys you can use the following overloads:

void Remove([AllowNull] KeyType key)
void Remove(NullableKey<KeyType> key)
void Remove(); // Shortcut for removing value with null key.

The indexers do accept [AllowNull] KeyType or NullableKey<KeyType>. So supported scenarios, like they are stated in other posts, are supported:

var dict = new NullableKeyDictionary<Type, string>
dict[typeof(int)] = "int type";
dict[typeof(string)] = "string type";

dict[null] = "null type";
// Or:
dict[NullableKey<Type>.Null] = "null type";

I highly appreciate feedback and suggestions for improvements. :)

站稳脚跟 2024-10-18 03:43:46

编辑:实际提出的问题的真正答案:为什么不能使用 null 作为 Dictionary的键?

通用字典不支持 null 的原因是 TKey 可能是值类型,不包含 null。

new Dictionary<int, string>[null] = "Null"; //error!

要获得这样的功能,您可以使用非通用的 Hashtable(它使用对象键和值),或者使用 DictionaryBase 推出您自己的 Hashtable。

编辑:只是为了澄清为什么 null 在这种情况下是非法的,请考虑这个通用方法:

bool IsNull<T> (T value) {
    return value == null;
}

但是当您调用 IsNull(null) 时会发生什么?

Argument '1': cannot convert from '<null>' to 'int'

您会收到编译器错误,因为您无法将 null 转换为 int。我们可以通过说我们只想要可为 null 的类型来修复它:

bool IsNull<T> (T value) where T : class {
    return value == null;
}

而且,那没问题。限制是我们不能再调用 IsNull,因为 int 不是一个类(可空对象)

EDIT: Real answer to the question actually being asked: Why can't you use null as a key for a Dictionary<bool?, string>?

The reason the generic dictionary doesn't support null is because TKey might be a value type, which doesn't have null.

new Dictionary<int, string>[null] = "Null"; //error!

To get one that does, you could either use the non-generic Hashtable (which uses object keys and values), or roll your own with DictionaryBase.

Edit: just to clarify why null is illegal in this case, consider this generic method:

bool IsNull<T> (T value) {
    return value == null;
}

But what happens when you call IsNull<int>(null)?

Argument '1': cannot convert from '<null>' to 'int'

You get a compiler error, since you can't convert null to an int. We can fix it, by saying that we only want nullable types:

bool IsNull<T> (T value) where T : class {
    return value == null;
}

And, that's A-Okay. The restriction is that we can no longer call IsNull<int>, since int is not a class (nullable object)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文