C#中的双向1对1词典

我正在寻找一个通用的,在C#(2)中的双向1对1 Dictionary类,即。 一个BiDictionaryOneToOne<T, S> ,它保证只包含每个值和关键字中的一个(无论如何都是RefEquals),并且可以使用键或值进行search。 任何人都知道,或者我应该自己实现呢? 我不敢相信我是第一个需要这个…

这个问题的答案中有一个BiDictionary,但它不是唯一的元素(也不实现RemoveByFirst(T t)或RemoveBySecond(S s))。

谢谢!

好的,这里是我的尝试(build立在Jon的基础上 – 谢谢),在这里归档并开放改进:

 /// <summary> /// This is a dictionary guaranteed to have only one of each value and key. /// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1. /// </summary> /// <typeparam name="TFirst">The type of the "key"</typeparam> /// <typeparam name="TSecond">The type of the "value"</typeparam> public class BiDictionaryOneToOne<TFirst, TSecond> { IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>(); IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>(); #region Exception throwing methods /// <summary> /// Tries to add the pair to the dictionary. /// Throws an exception if either element is already in the dictionary /// </summary> /// <param name="first"></param> /// <param name="second"></param> public void Add(TFirst first, TSecond second) { if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second)) throw new ArgumentException("Duplicate first or second"); firstToSecond.Add(first, second); secondToFirst.Add(second, first); } /// <summary> /// Find the TSecond corresponding to the TFirst first /// Throws an exception if first is not in the dictionary. /// </summary> /// <param name="first">the key to search for</param> /// <returns>the value corresponding to first</returns> public TSecond GetByFirst(TFirst first) { TSecond second; if (!firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); return second; } /// <summary> /// Find the TFirst corresponing to the Second second. /// Throws an exception if second is not in the dictionary. /// </summary> /// <param name="second">the key to search for</param> /// <returns>the value corresponding to second</returns> public TFirst GetBySecond(TSecond second) { TFirst first; if (!secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); return first; } /// <summary> /// Remove the record containing first. /// If first is not in the dictionary, throws an Exception. /// </summary> /// <param name="first">the key of the record to delete</param> public void RemoveByFirst(TFirst first) { TSecond second; if (!firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); firstToSecond.Remove(first); secondToFirst.Remove(second); } /// <summary> /// Remove the record containing second. /// If second is not in the dictionary, throws an Exception. /// </summary> /// <param name="second">the key of the record to delete</param> public void RemoveBySecond(TSecond second) { TFirst first; if (!secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); secondToFirst.Remove(second); firstToSecond.Remove(first); } #endregion #region Try methods /// <summary> /// Tries to add the pair to the dictionary. /// Returns false if either element is already in the dictionary /// </summary> /// <param name="first"></param> /// <param name="second"></param> /// <returns>true if successfully added, false if either element are already in the dictionary</returns> public Boolean TryAdd(TFirst first, TSecond second) { if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second)) return false; firstToSecond.Add(first, second); secondToFirst.Add(second, first); return true; } /// <summary> /// Find the TSecond corresponding to the TFirst first. /// Returns false if first is not in the dictionary. /// </summary> /// <param name="first">the key to search for</param> /// <param name="second">the corresponding value</param> /// <returns>true if first is in the dictionary, false otherwise</returns> public Boolean TryGetByFirst(TFirst first, out TSecond second) { return firstToSecond.TryGetValue(first, out second); } /// <summary> /// Find the TFirst corresponding to the TSecond second. /// Returns false if second is not in the dictionary. /// </summary> /// <param name="second">the key to search for</param> /// <param name="first">the corresponding value</param> /// <returns>true if second is in the dictionary, false otherwise</returns> public Boolean TryGetBySecond(TSecond second, out TFirst first) { return secondToFirst.TryGetValue(second, out first); } /// <summary> /// Remove the record containing first, if there is one. /// </summary> /// <param name="first"></param> /// <returns> If first is not in the dictionary, returns false, otherwise true</returns> public Boolean TryRemoveByFirst(TFirst first) { TSecond second; if (!firstToSecond.TryGetValue(first, out second)) return false; firstToSecond.Remove(first); secondToFirst.Remove(second); return true; } /// <summary> /// Remove the record containing second, if there is one. /// </summary> /// <param name="second"></param> /// <returns> If second is not in the dictionary, returns false, otherwise true</returns> public Boolean TryRemoveBySecond(TSecond second) { TFirst first; if (!secondToFirst.TryGetValue(second, out first)) return false; secondToFirst.Remove(second); firstToSecond.Remove(first); return true; } #endregion /// <summary> /// The number of pairs stored in the dictionary /// </summary> public Int32 Count { get { return firstToSecond.Count; } } /// <summary> /// Removes all items from the dictionary. /// </summary> public void Clear() { firstToSecond.Clear(); secondToFirst.Clear(); } } 

双向字典的更完整的实现:

  • 支持原始Dictionary<TKey,TValue> 几乎所有接口 (基础设施接口除外):
    • IDictionary<TKey, TValue>
    • IReadOnlyDictionary<TKey, TValue>
    • IDictionary
    • ICollection<KeyValuePair<TKey, TValue>> (这个和下面是上面的基本接口)
    • ICollection
    • IReadOnlyCollection<KeyValuePair<TKey, TValue>>
    • IEnumerable<KeyValuePair<TKey, TValue>>
    • IEnumerable
  • 使用SerializableAttribute进行SerializableAttribute
  • 使用DebuggerDisplayAttribute (带有计数信息)和DebuggerTypeProxyAttribute (用于在手表中显示键值对) debugging视图
  • 反向字典可以作为IDictionary<TValue, TKey> Reverse属性,也可以实现上面提到的所有接口。 所有在字典上的操作都可以修改。

用法:

 var dic = new BiDictionary<int, string>(); dic.Add(1, "1"); dic[2] = "2"; dic.Reverse.Add("3", 3); dic.Reverse["4"] = 4; dic.Clear(); 

代码可以在我的私人框架GitHub: BiDictionary(TFirst,TSecond).cs ( 永久链接 , search )。

复制:

 [Serializable] [DebuggerDisplay ("Count = {Count}"), DebuggerTypeProxy (typeof(DictionaryDebugView<,>))] public class BiDictionary<TFirst, TSecond> : IDictionary<TFirst, TSecond>, IReadOnlyDictionary<TFirst, TSecond>, IDictionary { private readonly IDictionary<TFirst, TSecond> _firstToSecond = new Dictionary<TFirst, TSecond>(); [NonSerialized] private readonly IDictionary<TSecond, TFirst> _secondToFirst = new Dictionary<TSecond, TFirst>(); [NonSerialized] private readonly ReverseDictionary _reverseDictionary; public BiDictionary () { _reverseDictionary = new ReverseDictionary(this); } public IDictionary<TSecond, TFirst> Reverse { get { return _reverseDictionary; } } public int Count { get { return _firstToSecond.Count; } } object ICollection.SyncRoot { get { return ((ICollection)_firstToSecond).SyncRoot; } } bool ICollection.IsSynchronized { get { return ((ICollection)_firstToSecond).IsSynchronized; } } bool IDictionary.IsFixedSize { get { return ((IDictionary)_firstToSecond).IsFixedSize; } } public bool IsReadOnly { get { return _firstToSecond.IsReadOnly || _secondToFirst.IsReadOnly; } } public TSecond this [TFirst key] { get { return _firstToSecond[key]; } set { _firstToSecond[key] = value; _secondToFirst[value] = key; } } object IDictionary.this [object key] { get { return ((IDictionary)_firstToSecond)[key]; } set { ((IDictionary)_firstToSecond)[key] = value; ((IDictionary)_secondToFirst)[value] = key; } } public ICollection<TFirst> Keys { get { return _firstToSecond.Keys; } } ICollection IDictionary.Keys { get { return ((IDictionary)_firstToSecond).Keys; } } IEnumerable<TFirst> IReadOnlyDictionary<TFirst, TSecond>.Keys { get { return ((IReadOnlyDictionary<TFirst, TSecond>)_firstToSecond).Keys; } } public ICollection<TSecond> Values { get { return _firstToSecond.Values; } } ICollection IDictionary.Values { get { return ((IDictionary)_firstToSecond).Values; } } IEnumerable<TSecond> IReadOnlyDictionary<TFirst, TSecond>.Values { get { return ((IReadOnlyDictionary<TFirst, TSecond>)_firstToSecond).Values; } } public IEnumerator<KeyValuePair<TFirst, TSecond>> GetEnumerator () { return _firstToSecond.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } IDictionaryEnumerator IDictionary.GetEnumerator () { return ((IDictionary)_firstToSecond).GetEnumerator(); } public void Add (TFirst key, TSecond value) { _firstToSecond.Add(key, value); _secondToFirst.Add(value, key); } void IDictionary.Add (object key, object value) { ((IDictionary)_firstToSecond).Add(key, value); ((IDictionary)_secondToFirst).Add(value, key); } public void Add (KeyValuePair<TFirst, TSecond> item) { _firstToSecond.Add(item); _secondToFirst.Add(item.Reverse()); } public bool ContainsKey (TFirst key) { return _firstToSecond.ContainsKey(key); } public bool Contains (KeyValuePair<TFirst, TSecond> item) { return _firstToSecond.Contains(item); } public bool TryGetValue (TFirst key, out TSecond value) { return _firstToSecond.TryGetValue(key, out value); } public bool Remove (TFirst key) { TSecond value; if (_firstToSecond.TryGetValue(key, out value)) { _firstToSecond.Remove(key); _secondToFirst.Remove(value); return true; } else return false; } void IDictionary.Remove (object key) { var firstToSecond = (IDictionary)_firstToSecond; if (!firstToSecond.Contains(key)) return; var value = firstToSecond[key]; firstToSecond.Remove(key); ((IDictionary)_secondToFirst).Remove(value); } public bool Remove (KeyValuePair<TFirst, TSecond> item) { return _firstToSecond.Remove(item); } public bool Contains (object key) { return ((IDictionary)_firstToSecond).Contains(key); } public void Clear () { _firstToSecond.Clear(); _secondToFirst.Clear(); } public void CopyTo (KeyValuePair<TFirst, TSecond>[] array, int arrayIndex) { _firstToSecond.CopyTo(array, arrayIndex); } void ICollection.CopyTo (Array array, int index) { ((IDictionary)_firstToSecond).CopyTo(array, index); } [OnDeserialized] internal void OnDeserialized (StreamingContext context) { _secondToFirst.Clear(); foreach (var item in _firstToSecond) _secondToFirst.Add(item.Value, item.Key); } private class ReverseDictionary : IDictionary<TSecond, TFirst>, IReadOnlyDictionary<TSecond, TFirst>, IDictionary { private readonly BiDictionary<TFirst, TSecond> _owner; public ReverseDictionary (BiDictionary<TFirst, TSecond> owner) { _owner = owner; } public int Count { get { return _owner._secondToFirst.Count; } } object ICollection.SyncRoot { get { return ((ICollection)_owner._secondToFirst).SyncRoot; } } bool ICollection.IsSynchronized { get { return ((ICollection)_owner._secondToFirst).IsSynchronized; } } bool IDictionary.IsFixedSize { get { return ((IDictionary)_owner._secondToFirst).IsFixedSize; } } public bool IsReadOnly { get { return _owner._secondToFirst.IsReadOnly || _owner._firstToSecond.IsReadOnly; } } public TFirst this [TSecond key] { get { return _owner._secondToFirst[key]; } set { _owner._secondToFirst[key] = value; _owner._firstToSecond[value] = key; } } object IDictionary.this [object key] { get { return ((IDictionary)_owner._secondToFirst)[key]; } set { ((IDictionary)_owner._secondToFirst)[key] = value; ((IDictionary)_owner._firstToSecond)[value] = key; } } public ICollection<TSecond> Keys { get { return _owner._secondToFirst.Keys; } } ICollection IDictionary.Keys { get { return ((IDictionary)_owner._secondToFirst).Keys; } } IEnumerable<TSecond> IReadOnlyDictionary<TSecond, TFirst>.Keys { get { return ((IReadOnlyDictionary<TSecond, TFirst>)_owner._secondToFirst).Keys; } } public ICollection<TFirst> Values { get { return _owner._secondToFirst.Values; } } ICollection IDictionary.Values { get { return ((IDictionary)_owner._secondToFirst).Values; } } IEnumerable<TFirst> IReadOnlyDictionary<TSecond, TFirst>.Values { get { return ((IReadOnlyDictionary<TSecond, TFirst>)_owner._secondToFirst).Values; } } public IEnumerator<KeyValuePair<TSecond, TFirst>> GetEnumerator () { return _owner._secondToFirst.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator(); } IDictionaryEnumerator IDictionary.GetEnumerator () { return ((IDictionary)_owner._secondToFirst).GetEnumerator(); } public void Add (TSecond key, TFirst value) { _owner._secondToFirst.Add(key, value); _owner._firstToSecond.Add(value, key); } void IDictionary.Add (object key, object value) { ((IDictionary)_owner._secondToFirst).Add(key, value); ((IDictionary)_owner._firstToSecond).Add(value, key); } public void Add (KeyValuePair<TSecond, TFirst> item) { _owner._secondToFirst.Add(item); _owner._firstToSecond.Add(item.Reverse()); } public bool ContainsKey (TSecond key) { return _owner._secondToFirst.ContainsKey(key); } public bool Contains (KeyValuePair<TSecond, TFirst> item) { return _owner._secondToFirst.Contains(item); } public bool TryGetValue (TSecond key, out TFirst value) { return _owner._secondToFirst.TryGetValue(key, out value); } public bool Remove (TSecond key) { TFirst value; if (_owner._secondToFirst.TryGetValue(key, out value)) { _owner._secondToFirst.Remove(key); _owner._firstToSecond.Remove(value); return true; } else return false; } void IDictionary.Remove (object key) { var firstToSecond = (IDictionary)_owner._secondToFirst; if (!firstToSecond.Contains(key)) return; var value = firstToSecond[key]; firstToSecond.Remove(key); ((IDictionary)_owner._firstToSecond).Remove(value); } public bool Remove (KeyValuePair<TSecond, TFirst> item) { return _owner._secondToFirst.Remove(item); } public bool Contains (object key) { return ((IDictionary)_owner._secondToFirst).Contains(key); } public void Clear () { _owner._secondToFirst.Clear(); _owner._firstToSecond.Clear(); } public void CopyTo (KeyValuePair<TSecond, TFirst>[] array, int arrayIndex) { _owner._secondToFirst.CopyTo(array, arrayIndex); } void ICollection.CopyTo (Array array, int index) { ((IDictionary)_owner._secondToFirst).CopyTo(array, index); } } } internal class DictionaryDebugView<TKey, TValue> { private readonly IDictionary<TKey, TValue> _dictionary; [DebuggerBrowsable (DebuggerBrowsableState.RootHidden)] public KeyValuePair<TKey, TValue>[] Items { get { var array = new KeyValuePair<TKey, TValue>[_dictionary.Count]; _dictionary.CopyTo(array, 0); return array; } } public DictionaryDebugView (IDictionary<TKey, TValue> dictionary) { if (dictionary == null) throw new ArgumentNullException("dictionary"); _dictionary = dictionary; } } public static class KeyValuePairExts { public static KeyValuePair<TValue, TKey> Reverse<TKey, TValue> (this KeyValuePair<TKey, TValue> @this) { return new KeyValuePair<TValue, TKey>(@this.Value, @this.Key); } } 

你提到的问题也显示了这个答案中的一对一的实现。 添加RemoveByFirst和RemoveBySecond将是微不足道的 – 就像实现额外的接口等

这与被接受的答案是一样的,但是我也提供了Update方法,并且更加充实:

 public class BiDictionary<TKey1, TKey2> : IEnumerable<Tuple<TKey1, TKey2>> { Dictionary<TKey1, TKey2> _forwards; Dictionary<TKey2, TKey1> _reverses; public int Count { get { if (_forwards.Count != _reverses.Count) throw new Exception("somewhere logic went wrong and your data got corrupt"); return _forwards.Count; } } public ICollection<TKey1> Key1s { get { return _forwards.Keys; } } public ICollection<TKey2> Key2s { get { return _reverses.Keys; } } public BiDictionary(IEqualityComparer<TKey1> comparer1 = null, IEqualityComparer<TKey2> comparer2 = null) { _forwards = new Dictionary<TKey1, TKey2>(comparer1); _reverses = new Dictionary<TKey2, TKey1>(comparer2); } public bool ContainsKey1(TKey1 key) { return ContainsKey(key, _forwards); } private static bool ContainsKey<S, T>(S key, Dictionary<S, T> dict) { return dict.ContainsKey(key); } public bool ContainsKey2(TKey2 key) { return ContainsKey(key, _reverses); } public TKey2 GetValueByKey1(TKey1 key) { return GetValueByKey(key, _forwards); } private static T GetValueByKey<S, T>(S key, Dictionary<S, T> dict) { return dict[key]; } public TKey1 GetValueByKey2(TKey2 key) { return GetValueByKey(key, _reverses); } public bool TryGetValueByKey1(TKey1 key, out TKey2 value) { return TryGetValue(key, _forwards, out value); } private static bool TryGetValue<S, T>(S key, Dictionary<S, T> dict, out T value) { return dict.TryGetValue(key, out value); } public bool TryGetValueByKey2(TKey2 key, out TKey1 value) { return TryGetValue(key, _reverses, out value); } public bool Add(TKey1 key1, TKey2 key2) { if (ContainsKey1(key1) || ContainsKey2(key2)) // very important return false; AddOrUpdate(key1, key2); return true; } public void AddOrUpdateByKey1(TKey1 key1, TKey2 key2) { if (!UpdateByKey1(key1, key2)) AddOrUpdate(key1, key2); } // dont make this public; a dangerous method used cautiously in this class private void AddOrUpdate(TKey1 key1, TKey2 key2) { _forwards[key1] = key2; _reverses[key2] = key1; } public void AddOrUpdateKeyByKey2(TKey2 key2, TKey1 key1) { if (!UpdateByKey2(key2, key1)) AddOrUpdate(key1, key2); } public bool UpdateKey1(TKey1 oldKey, TKey1 newKey) { return UpdateKey(oldKey, _forwards, newKey, (key1, key2) => AddOrUpdate(key1, key2)); } private static bool UpdateKey<S, T>(S oldKey, Dictionary<S, T> dict, S newKey, Action<S, T> updater) { T otherKey; if (!TryGetValue(oldKey, dict, out otherKey) || ContainsKey(newKey, dict)) return false; Remove(oldKey, dict); updater(newKey, otherKey); return true; } public bool UpdateKey2(TKey2 oldKey, TKey2 newKey) { return UpdateKey(oldKey, _reverses, newKey, (key1, key2) => AddOrUpdate(key2, key1)); } public bool UpdateByKey1(TKey1 key1, TKey2 key2) { return UpdateByKey(key1, _forwards, _reverses, key2, (k1, k2) => AddOrUpdate(k1, k2)); } private static bool UpdateByKey<S, T>(S key1, Dictionary<S, T> forwards, Dictionary<T, S> reverses, T key2, Action<S, T> updater) { T otherKey; if (!TryGetValue(key1, forwards, out otherKey) || ContainsKey(key2, reverses)) return false; if (!Remove(otherKey, reverses)) throw new Exception("somewhere logic went wrong and your data got corrupt"); updater(key1, key2); return true; } public bool UpdateByKey2(TKey2 key2, TKey1 key1) { return UpdateByKey(key2, _reverses, _forwards, key1, (k1, k2) => AddOrUpdate(k2, k1)); } public bool RemoveByKey1(TKey1 key) { return RemoveByKey(key, _forwards, _reverses); } private static bool RemoveByKey<S, T>(S key, Dictionary<S, T> keyDict, Dictionary<T, S> valueDict) { T otherKey; if (!TryGetValue(key, keyDict, out otherKey)) return false; if (!Remove(key, keyDict) || !Remove(otherKey, valueDict)) throw new Exception("somewhere logic went wrong and your data got corrupt"); return true; } private static bool Remove<S, T>(S key, Dictionary<S, T> dict) { return dict.Remove(key); } public bool RemoveByKey2(TKey2 key) { return RemoveByKey(key, _reverses, _forwards); } public void Clear() { _forwards.Clear(); _reverses.Clear(); } public IEnumerator<Tuple<TKey1, TKey2>> GetEnumerator() { if (_forwards.Count != _reverses.Count) throw new Exception("somewhere logic went wrong and your data got corrupt"); foreach (var item in _forwards) yield return Tuple.Create(item.Key, item.Value); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

与我在这里的答案类似

几件事要注意:

  1. 我只实现了IEnumerable<> 。 我不认为ICollection<>是有意义的,因为这个方法的名字对于这个特殊的集合结构可能是不一样的。 由你决定IEnumerable<>内部应该发生什么。 所以现在你也有集合初始化语法了

     var p = new BiDictionary<int, string> { 1, "a" }, { 2, "b" } }; 
  2. 我试图在这里和那里抛出一些奇怪的exception – 只是为了数据的完整性。 只是要更安全的一面,以便您知道我的代码是否有错误。

  3. 性能:您可以使用任一Keys查找Value ,这意味着GetContains方法只需要1次查找(O(1))。 Add需要2个查找和2个添加。 Update需要1个查找和2个添加。 Remove需要3个查找。 所有类似于接受的答案。

我已经创build了这样一个类,使用C5集合类。

 public class Mapper<K,T> : IEnumerable<T> { C5.TreeDictionary<K,T> KToTMap = new TreeDictionary<K,T>(); C5.HashDictionary<T,K> TToKMap = new HashDictionary<T,K>(); /// <summary> /// Initializes a new instance of the Mapper class. /// </summary> public Mapper() { KToTMap = new TreeDictionary<K,T>(); TToKMap = new HashDictionary<T,K>(); } public void Add(K key, T value) { KToTMap.Add(key, value); TToKMap.Add(value, key); } public bool ContainsKey(K key) { return KToTMap.Contains(key); } public int Count { get { return KToTMap.Count; } } public K this[T obj] { get { return TToKMap[obj]; } } public T this[K obj] { get { return KToTMap[obj]; } } public IEnumerator<T> GetEnumerator() { return KToTMap.Values.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return KToTMap.Values.GetEnumerator(); } } 

被接受的答案的另一个扩展。 它实现了IEnumerable,所以可以使用foreach。 我意识到IEnumerable实现有更多的答案,但是这个使用结构,所以它是垃圾收集器友好 。 这在Unity引擎中尤其有用(与分析器一起检查)。

 /// <summary> /// This is a dictionary guaranteed to have only one of each value and key. /// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1. /// It implements garbage-collector-friendly IEnumerable. /// </summary> /// <typeparam name="TFirst">The type of the "key"</typeparam> /// <typeparam name="TSecond">The type of the "value"</typeparam> public class BiDictionary<TFirst, TSecond> : IEnumerable<BiDictionary<TFirst, TSecond>.Pair> { public struct Pair { public TFirst First; public TSecond Second; } public struct Enumerator : IEnumerator<Pair>, IEnumerator { public Enumerator(Dictionary<TFirst, TSecond>.Enumerator dictEnumerator) { _dictEnumerator = dictEnumerator; } public Pair Current { get { Pair pair; pair.First = _dictEnumerator.Current.Key; pair.Second = _dictEnumerator.Current.Value; return pair; } } object IEnumerator.Current { get { return Current; } } public void Dispose() { _dictEnumerator.Dispose(); } public bool MoveNext() { return _dictEnumerator.MoveNext(); } public void Reset() { throw new NotSupportedException(); } private Dictionary<TFirst, TSecond>.Enumerator _dictEnumerator; } #region Exception throwing methods /// <summary> /// Tries to add the pair to the dictionary. /// Throws an exception if either element is already in the dictionary /// </summary> /// <param name="first"></param> /// <param name="second"></param> public void Add(TFirst first, TSecond second) { if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) throw new ArgumentException("Duplicate first or second"); _firstToSecond.Add(first, second); _secondToFirst.Add(second, first); } /// <summary> /// Find the TSecond corresponding to the TFirst first /// Throws an exception if first is not in the dictionary. /// </summary> /// <param name="first">the key to search for</param> /// <returns>the value corresponding to first</returns> public TSecond GetByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); return second; } /// <summary> /// Find the TFirst corresponing to the Second second. /// Throws an exception if second is not in the dictionary. /// </summary> /// <param name="second">the key to search for</param> /// <returns>the value corresponding to second</returns> public TFirst GetBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); return first; } /// <summary> /// Remove the record containing first. /// If first is not in the dictionary, throws an Exception. /// </summary> /// <param name="first">the key of the record to delete</param> public void RemoveByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) throw new ArgumentException("first"); _firstToSecond.Remove(first); _secondToFirst.Remove(second); } /// <summary> /// Remove the record containing second. /// If second is not in the dictionary, throws an Exception. /// </summary> /// <param name="second">the key of the record to delete</param> public void RemoveBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) throw new ArgumentException("second"); _secondToFirst.Remove(second); _firstToSecond.Remove(first); } #endregion #region Try methods /// <summary> /// Tries to add the pair to the dictionary. /// Returns false if either element is already in the dictionary /// </summary> /// <param name="first"></param> /// <param name="second"></param> /// <returns>true if successfully added, false if either element are already in the dictionary</returns> public bool TryAdd(TFirst first, TSecond second) { if (_firstToSecond.ContainsKey(first) || _secondToFirst.ContainsKey(second)) return false; _firstToSecond.Add(first, second); _secondToFirst.Add(second, first); return true; } /// <summary> /// Find the TSecond corresponding to the TFirst first. /// Returns false if first is not in the dictionary. /// </summary> /// <param name="first">the key to search for</param> /// <param name="second">the corresponding value</param> /// <returns>true if first is in the dictionary, false otherwise</returns> public bool TryGetByFirst(TFirst first, out TSecond second) { return _firstToSecond.TryGetValue(first, out second); } /// <summary> /// Find the TFirst corresponding to the TSecond second. /// Returns false if second is not in the dictionary. /// </summary> /// <param name="second">the key to search for</param> /// <param name="first">the corresponding value</param> /// <returns>true if second is in the dictionary, false otherwise</returns> public bool TryGetBySecond(TSecond second, out TFirst first) { return _secondToFirst.TryGetValue(second, out first); } /// <summary> /// Remove the record containing first, if there is one. /// </summary> /// <param name="first"></param> /// <returns> If first is not in the dictionary, returns false, otherwise true</returns> public bool TryRemoveByFirst(TFirst first) { TSecond second; if (!_firstToSecond.TryGetValue(first, out second)) return false; _firstToSecond.Remove(first); _secondToFirst.Remove(second); return true; } /// <summary> /// Remove the record containing second, if there is one. /// </summary> /// <param name="second"></param> /// <returns> If second is not in the dictionary, returns false, otherwise true</returns> public bool TryRemoveBySecond(TSecond second) { TFirst first; if (!_secondToFirst.TryGetValue(second, out first)) return false; _secondToFirst.Remove(second); _firstToSecond.Remove(first); return true; } #endregion /// <summary> /// The number of pairs stored in the dictionary /// </summary> public Int32 Count { get { return _firstToSecond.Count; } } /// <summary> /// Removes all items from the dictionary. /// </summary> public void Clear() { _firstToSecond.Clear(); _secondToFirst.Clear(); } public Enumerator GetEnumerator() { //enumerator.Reset(firstToSecond.GetEnumerator()); return new Enumerator(_firstToSecond.GetEnumerator()); } IEnumerator<Pair> IEnumerable<Pair>.GetEnumerator() { return GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } private Dictionary<TFirst, TSecond> _firstToSecond = new Dictionary<TFirst, TSecond>(); private Dictionary<TSecond, TFirst> _secondToFirst = new Dictionary<TSecond, TFirst>(); } 

一个简单的容器类与2字典的应该工作就好IMO。

更新:你需要每一个“列”或所有这些唯一性(如复合主键)?

有点晚了,但是这是我后来写的一个实现。 它处理一些有趣的边缘情况,例如,当关键字覆盖相等性检查以执行部分​​相等时。 这导致主字典存储A => 1但是逆存储1 => A'

您可以通过Inverse属性访问逆字典。

 var map = new BidirectionalDictionary<int, int>(); map.Add(1, 2); var result = map.Inverse[2]; // result is 1 

 // // BidirectionalDictionary.cs // // Author: // Chris Chilvers <chilversc@googlemail.com> // // Copyright (c) 2009 Chris Chilvers // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Collections; using System.Collections.Generic; namespace Cadenza.Collections { public class BidirectionalDictionary<TKey, TValue> : IDictionary<TKey, TValue> { private readonly IEqualityComparer<TKey> keyComparer; private readonly IEqualityComparer<TValue> valueComparer; private readonly Dictionary<TKey, TValue> keysToValues; private readonly Dictionary<TValue, TKey> valuesToKeys; private readonly BidirectionalDictionary<TValue, TKey> inverse; public BidirectionalDictionary () : this (10, null, null) {} public BidirectionalDictionary (int capacity) : this (capacity, null, null) {} public BidirectionalDictionary (IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) : this (10, keyComparer, valueComparer) { } public BidirectionalDictionary (int capacity, IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer) { if (capacity < 0) throw new ArgumentOutOfRangeException ("capacity", capacity, "capacity cannot be less than 0"); this.keyComparer = keyComparer ?? EqualityComparer<TKey>.Default; this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default; keysToValues = new Dictionary<TKey, TValue> (capacity, this.keyComparer); valuesToKeys = new Dictionary<TValue, TKey> (capacity, this.valueComparer); inverse = new BidirectionalDictionary<TValue, TKey> (this); } private BidirectionalDictionary (BidirectionalDictionary<TValue, TKey> inverse) { this.inverse = inverse; keyComparer = inverse.valueComparer; valueComparer = inverse.keyComparer; valuesToKeys = inverse.keysToValues; keysToValues = inverse.valuesToKeys; } public BidirectionalDictionary<TValue, TKey> Inverse { get { return inverse; } } public ICollection<TKey> Keys { get { return keysToValues.Keys; } } public ICollection<TValue> Values { get { return keysToValues.Values; } } public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator () { return keysToValues.GetEnumerator (); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } void ICollection<KeyValuePair<TKey, TValue>>.CopyTo (KeyValuePair<TKey, TValue>[] array, int arrayIndex) { ((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).CopyTo (array, arrayIndex); } public bool ContainsKey (TKey key) { if (key == null) throw new ArgumentNullException ("key"); return keysToValues.ContainsKey (key); } public bool ContainsValue (TValue value) { if (value == null) throw new ArgumentNullException ("value"); return valuesToKeys.ContainsKey (value); } bool ICollection<KeyValuePair<TKey, TValue>>.Contains (KeyValuePair<TKey, TValue> item) { return ((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).Contains (item); } public bool TryGetKey (TValue value, out TKey key) { if (value == null) throw new ArgumentNullException ("value"); return valuesToKeys.TryGetValue (value, out key); } public bool TryGetValue (TKey key, out TValue value) { if (key == null) throw new ArgumentNullException ("key"); return keysToValues.TryGetValue (key, out value); } public TValue this[TKey key] { get { return keysToValues [key]; } set { if (key == null) throw new ArgumentNullException ("key"); if (value == null) throw new ArgumentNullException ("value"); //foo[5] = "bar"; foo[6] = "bar"; should not be valid //as it would have to remove foo[5], which is unexpected. if (ValueBelongsToOtherKey (key, value)) throw new ArgumentException ("Value already exists", "value"); TValue oldValue; if (keysToValues.TryGetValue (key, out oldValue)) { // Use the current key for this value to stay consistent // with Dictionary<TKey, TValue> which does not alter // the key if it exists. TKey oldKey = valuesToKeys [oldValue]; keysToValues [oldKey] = value; valuesToKeys.Remove (oldValue); valuesToKeys [value] = oldKey; } else { keysToValues [key] = value; valuesToKeys [value] = key; } } } public int Count { get { return keysToValues.Count; } } bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { get { return false; } } public void Add (TKey key, TValue value) { if (key == null) throw new ArgumentNullException ("key"); if (value == null) throw new ArgumentNullException ("value"); if (keysToValues.ContainsKey (key)) throw new ArgumentException ("Key already exists", "key"); if (valuesToKeys.ContainsKey (value)) throw new ArgumentException ("Value already exists", "value"); keysToValues.Add (key, value); valuesToKeys.Add (value, key); } public void Replace (TKey key, TValue value) { if (key == null) throw new ArgumentNullException ("key"); if (value == null) throw new ArgumentNullException ("value"); // replaces a key value pair, if the key or value already exists those mappings will be replaced. // eg you have; a -> b, b -> a; c -> d, d -> c // you add the mapping; a -> d, d -> a // this will remove both of the original mappings Remove (key); inverse.Remove (value); Add (key, value); } void ICollection<KeyValuePair<TKey, TValue>>.Add (KeyValuePair<TKey, TValue> item) { Add (item.Key, item.Value); } public bool Remove (TKey key) { if (key == null) throw new ArgumentNullException ("key"); TValue value; if (keysToValues.TryGetValue (key, out value)) { keysToValues.Remove (key); valuesToKeys.Remove (value); return true; } else { return false; } } bool ICollection<KeyValuePair<TKey, TValue>>.Remove (KeyValuePair<TKey, TValue> item) { bool removed = ((ICollection<KeyValuePair<TKey, TValue>>) keysToValues).Remove (item); if (removed) valuesToKeys.Remove (item.Value); return removed; } public void Clear () { keysToValues.Clear (); valuesToKeys.Clear (); } private bool ValueBelongsToOtherKey (TKey key, TValue value) { TKey otherKey; if (valuesToKeys.TryGetValue (value, out otherKey)) // if the keys are not equal the value belongs to another key return !keyComparer.Equals (key, otherKey); else // value doesn't exist in map, thus it cannot belong to another key return false; } } } 

Original source and tests on github.