带空键的字典?

mpe*_*pen 45 c#

首先,为什么Dictionary<TKey, TValue>支持单个空键?

其次,是否有现有的字典式集合呢?

我想存储一个"空"或"缺失"或"默认" 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());
    }
}
Run Code Online (Sandbox Code Playgroud)

用于打开类型.有两种方法可以使用它:

  1. 静态.打电话吧Switch.Execute(yourObject, Switch.Case<YourType>(x => x.Action()))
  2. 预编译.创建一个开关,稍后再使用它switchInstance.Execute(yourObject)

除非您尝试将默认大小写添加到"预编译"版本(null参数异常),否则效果很好.

Fab*_*eco 22

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]);
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,虽然这对于引用类型很有用,但当值类型使用“Equals(object)”方法时,它会不必要地对值类型进行装箱/拆箱。为了避免装箱和拆箱,“NullObject&lt;T&gt;”应该实现“IEquatable&lt;T&gt;”和“IEquatable&lt;NullObject&lt;T&gt;&gt;”。请参阅:https://medium.com/@equisept/c-journey-into-struct-equality-comparison-deep-dive-9693f74562f1 (4认同)
  • 如果您正在寻找上述 `NullObject&lt;T&gt;` 包装器的内置替代方案,您可以使用 `ValueTuple&lt;T&gt;` [doco](https://docs.microsoft.com/en-us/dotnet/ api/system.valuetuple-1?view=net-5.0) (3认同)
  • ValueTuple 示例: var Dictionary = new Dictionary&lt;System.ValueTuple &lt;int?&gt;, int&gt;(); 字典.Add(默认, 1); (2认同)

Gab*_*abe 14

它只是打击了我,你最好的答案可能只是跟踪是否已定义默认情况:

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);
    }

...
Run Code Online (Sandbox Code Playgroud)

你班上的其余部分都不会受到影响.


Rob*_*Rob 13

它不支持它,因为字典散列了确定索引的键,它不能对空值执行.

快速解决方法是创建一个虚拟类,并插入键值?dummyClassInstance.需要更多关于你实际上要做什么的信息来提供一个不那么"hacky"的修复

  • Rob:`Dictionary` ctor使用`EqualityComparer <T> .Default`,它调用`CreateComparer()`,它返回一个`ObjectEqualityComparer <T>`,其`GetHashCode`函数为`null`返回0. (9认同)
  • 如果他们真的想要,可以为`null`创建一个特例. (6认同)
  • 根据我的阅读,这是不正确的.`Dictionary <K,V>`类使用`IEqualityComparer`来获取哈希码,我相信默认值只是为'null`返回0. (3认同)
  • 给定一个可以处理空值的`EqualityComparer`,没有理由`Dictionary'也不能这样做.唯一可能的丑陋是,如果`EqualityComparer`由于null参数而抛出异常,则抛出异常的参数名称可能是`x`或`y`(由`EqualityComparer`报告),而不是"关键". (3认同)

Dar*_*rov 7

NameValueCollection可以采用null键.


Gab*_*abe 5

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)