2011-01-08 4 views
31

Во-первых, Почему не Dictionary<TKey, TValue> поддерживает один ключ null?Словарь с нулевым ключом?

Во-вторых, существует ли существующая коллекция, подобная словарю?

Я хочу сохранить «пустые» или «отсутствующие» или «по умолчанию» 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<YourType>(x => x.Action()))
  2. Предварительно скомпилировано. Создайте переключатель, а затем использовать его позже с switchInstance.Execute(yourObject)

Прекрасно работает кроме при попытке добавить дело по умолчанию в «скомпилированный» версии (нуль исключение аргумента).

+2

возможно дубликат [C# - Нужна реализация IDictionary что позволит пустой ключ] (http://stackoverflow.com/questions/1916814/c-need-an-idictionary-implementation-that -will-allow-a-null-key) – Gabe

+0

Возможный дубликат [C# - нужна реализация IDictionary, которая позволит использовать нулевой ключ] (https://stackoverflow.com/questions/1916814/c-sharp-need-an -idictionary-implementation-that-will-allow-a-null-key) – DavidRR

ответ

13

Он просто ударил меня, что ваш лучший ответ, вероятно, просто следить, был ли определен случай по умолчанию:

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

... 

вся остальная часть вашего класса останется нетронутым.

+0

Да, это тоже очень хорошо работает. Может быть немного чище, чем использование фиктивного класса. – mpen

+0

Я помню, как однажды создавал DefaultDictionary . У него была добавленная функция возврата Default всякий раз, когда вы пытались получить ключ, которого не было, но кроме этого должны соответствовать потребностям и быть многоразовыми. Не помню, где я помещал код, хотя -.- – Alxandr

0

EDIT: Реальный ответ на вопрос, на самом деле спрашивают: Why can't you use null as a key for a Dictionary<bool?, string>?

Причины общего словарь не поддерживает Нулевой потому TKey может быть типом значения, которое не имеет нуля.

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

Чтобы получить один, что делает, вы можете использовать либо нетипичные Hashtable (который использует ключи объекта и значение), или свернуть свой собственный с DictionaryBase.

Edit: просто выяснить, почему нуль является незаконным в этом случае рассматривать этот общий метод:

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

Но что происходит, когда вы звоните IsNull<int>(null)?

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

Вы получите ошибку компиляции, так как вы не можете преобразовать null к int. Мы можем исправить это, сказав, что нам нужны только нулевые типы:

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

И это A-Okay. Ограничение состоит в том, что мы больше не можем звонить по номеру IsNull<int>, поскольку int не является классом (объект с нулевым значением)

+0

Я не вижу этот не общий словарь в MSDN. У вас есть ссылка? – mpen

+4

Это просто ложь. Нет причин, по которым вы не можете сравнить тип значения с «null». Фактически, ваша функция 'IsNull' компилируется просто отлично и возвращает' false' для любых типов значений, которые не являются нулевыми значениями. – Gabe

+0

Извините, мой плохой. Это называется «Hashtable». Я обновил свой ответ, чтобы отразить это. –

6

NameValueCollection может принимать нулевой ключ.

+0

Требуется 'Dictionary >' на самом деле, но это может пригодиться для других проектов. – mpen

12

Он не поддерживает его, потому что словарь хэширует ключ, чтобы определить индекс, который он не может сделать с нулевым значением.

Быстрое исправление заключалось бы в создании класса фиктивного типа и введении значения ключа? dummyClassInstance. Вам понадобится дополнительная информация о том, что вы на самом деле пытаетесь сделать, чтобы дать меньше «взломанного» исправления.

+4

Может ли создать специальный случай для 'null', если он действительно этого захочет. – mpen

+3

Это неверно, основываясь на моем чтении. Класс 'Dictionary ' использует 'IEqualityComparer' для получения хеш-кода, и я считаю, что значение по умолчанию просто возвращает 0 для' null'. – Gabe

+0

Обновленный вопрос. – mpen

0

Словарь будет хешировать ключ supplie для получения индекса, в случае null хеш-функция не может вернуть действительное значение поэтому он не поддерживает нулевой ключ.

1

В вашем случае вы пытаетесь использовать 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); 
    } 
+0

Yup ... это сработает. Кажется немного уродливым, esp для * другой функции * Execute, но он действительно работает. – mpen

+0

@Ralph: Ваша другая функция Execute не искала «null», поэтому я даже не потрудился с ней. Я отредактировал свой ответ, чтобы включить его. Обратите внимание, что у него очень разная семантика из-за отсутствия использования IsAssignableFrom'. – Gabe

+0

Ну нет, ему не нужно искать нуль, потому что ... о, подождите, да. Они, как вы написали другую функцию execute ..., делают поведение последовательным, но побеждают цель иметь dict (быстрый поиск). О, ну, в любом случае, это не имеет значения ... У меня есть только 2 элемента: P – mpen

2

Если вы действительно хотите, словарь, который позволяет нулевые ключи, вот мое быстрое внедрение (не очень хорошо написано или хорошо проверенная):

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(); 
    } 
} 
+0

Название класса обманчиво :) Обычно NullableX означает, что X может быть нулевым, а не содержать нули. Несмотря ни на что .. Я не уверен, что было бы более уместно. Спасибо = D – mpen

+1

Я кратко рассмотрел «NullableKeyDictionary», но решил сократить его. – Gabe

13

1) Почему: Как описано выше, проблема заключается в том, что словарь требует реализации метода 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; 
    } 
} 
+0

Я только что понял, что это работает, потому что его структура. Это довольно элегантно. –