具有两个键和一个值的.net字典

本文关键字:一个 字典 net 两个 | 更新日期: 2023-09-27 18:14:33

.NET中是否存在可以容纳2个键和一个值的字典?像

Dictionary(Of TKey, Of TKey, TValue)

我需要存储两个键,并且在某些时候通过键1查看项,在其他时候通过键2查看项。

我当前的解决方案是维护两个字典

Dictionary<string, long> Dict1 = new Dictionary<string, long>();
Dictionary<long, long> Dict2 = new Dictionary<long, long>();

,当需要添加条目时,我会将其添加到两个字典中。

Dict1.Add("abc", 111);
Dict2.Add(345, 111);

,然后我将根据需要查找的键从这两个字典中的任意一个中查找项

当我删除或更新一个项目时,我也会这样做。

我已经考虑过复合键,但我不知道如何设置它,我不想失去任何搜索项目的速度。

.NET中是否有一些解决方案可以容纳多个键的字典?

具有两个键和一个值的.net字典

当您希望您的值从任何一个键中"可找到"时,我将只使用两个字典,就像您现在所做的那样。然而,我会把它包装在一个类中,方法名称如FindByXXXFindByYYY

更难的问题是如何执行delete,因为在删除时需要知道两个键。也许你的值存储了两个键,所以你可以将值传递给你的delete方法。也许你永远不需要从字典中删除条目。或者需要删除项的代码知道两个键。

因此没有标准的字典来做到这一点,因为每个用户的需求是不同的。

(注意不要使用组合键的字典,因为这样需要在查找项时知道两个键)

也许,像这样:

public class TwoKeyDictionary<Tkey1, Tkey2, TValue>
{
    private object m_data_lock = new object();
    private Dictionary<Tkey1, Tkey2> m_dic1 = new Dictionary<Tkey1, Tkey2>();
    private Dictionary<Tkey2, TValue> m_dic2 = new Dictionary<Tkey2, TValue>();
    public void AddValue(Tkey1 key1, Tkey2 key2, TValue value)
    {
        lock(m_data_lock)
        {
            m_dic1[key1] = key2;
            m_dic2[key2] = value;
        }
    }
    public TValue getByKey1(Tkey1 key1)
    {
        lock(m_data_lock)
            return m_dic2[m_dic1[key1]];
    }
    public TValue getByKey2(Tkey key2)
    {
        lock(m_data_lock)
            return m_dic2[key2];
    }
    public void removeByKey1(Tkey1 key1)
    {
        lock(m_data_lock)
        {
            Tkey2 tmp_key2 =   m_dic1[key1];
            m_dic1.Remove(key1);
            m_dic2.Remove(tmp_key2);
        }
    }
    public void removeByKey2(Tkey2 key2)
    {
        lock(m_data_lock)
        {
            Tkey1 tmp_key1 = m_dic1.First((kvp) => kvp.Value.Equals(key2)).Key;
            m_dic1.Remove(tmp_key1);
            m_dic2.Remove(key2);
        }
    }
}

我可以提供第二个解决方案,但它似乎比第一个更慢更难看。

public class TwoKeysDictionary<K1, K2, V>
{
    private class TwoKeysValue<K1, K2, V>
    {
        public K1 Key1 { get; set; }
        public K2 Key2 { get; set; }
        public V Value { get; set; }
    }
    private List<TwoKeysValue<K1, K2, V>> m_list = new List<TwoKeysValue<K1, K2, V>>();
    public void Add(K1 key1, K2 key2, V value)
    {
        lock (m_list)
            m_list.Add(new TwoKeysValue<K1, K2, V>() { Key1 = key1, Key2 = key2, Value = value });
    }
    public V getByKey1(K1 key1)
    {
        lock (m_list)
            return m_list.First((tkv) => tkv.Key1.Equals(key1)).Value;
    }
    public V getByKey2(K2 key2)
    {
        lock (m_list)
            return m_list.First((tkv) => tkv.Key2.Equals(key2)).Value;
    }
    public void removeByKey1(K1 key1)
    {
        lock (m_list)
            m_list.Remove(m_list.First((tkv) => tkv.Key1.Equals(key1)));
    }
    public void removeByKey2(K2 key2)
    {
        lock (m_list)
            m_list.Remove(m_list.First((tkv) => tkv.Key2.Equals(key2)));
    }
}

在非常糟糕的情况下,当键是一个大的结构(即大的值类型),键的大小相等,值是小的值类型(例如,一个字节),你有第一个解决方案:一组Key1,两组Key2,一组值= 3组大对象和1组小值。第二个解决方案是:一组Key1,一组Key2,一组值=两组大对象和一组值。例如,使用第一种解决方案时,您需要比第二种解决方案多50%(或更少)的内存空间,但第二种解决方案与第一种解决方案相比非常非常慢。

您的解决方案对应用程序的内存占用有很大的影响。随着字典的增长,它将至少需要存储实际数据所需的两倍内存(对于值类型)。

你也许可以从不同的角度来处理这个问题。有两个字典:

var lookupDictionary = new Dictionary<string, string>();
var valuesDictionary = new Dictionary<string, [YourValueType]>();

从现在开始,一切都很简单。

// Add a new entry into the values dictionary and give it a unique key
valuesDictionary.Add("FooBar", "FUBAR VALUE");
// Add any number of lookup keys with the same value key
lookupDictionary.Add("Foo", "FooBar");
lookupDictionary.Add("Bar", "FooBar");
lookupDictionary.Add("Rab", "FooBar");
lookupDictionary.Add("Oof", "FooBar");

当你需要从valuesDictionary中找到一些东西时,你先点击lookupDictionary。这将为您提供您在valuesDictionary中寻找的值的键。

编辑

我没有在我的回答中解决删除问题,所以它是这样的:D

点击lookupDictionary查找值键,然后删除lookupDictionary中具有的所有条目。

应该足够简单和安全,因为valuesDictionary保证有一个唯一的键,因此您不会意外地删除其他值的查找键

然而,正如Ian Ringrose在评论中指出的那样,您将对lookupDictionary进行全面扫描以删除。这可能会对紧循环等的性能产生不良影响。

目前我实在想不出一个好办法来解决这个问题。也许其他人会有一些关于如何改进的想法。

我希望这对你有帮助。

你不可能只用一本字典而不损失查找速度。原因是,如果要创建一个复合键,那么在重写GetHashCode时就无法返回有意义的值。这意味着需要对每个键进行相等性比较,直到找到字典条目。在这种情况下,使用组合键还有一个潜在的问题:因为Equals方法会检查一个属性或另一个属性是否相等,下面的键本质上是重复的键{Id=1, Name="Bob"} {Id=1, Name="Anna"},这并没有给我一种温暖模糊的感觉。

这就留给你一个字典,或者用你自己的类包装一对字典。

有趣的问题,这里有一个解决方案。你必须为你想要支持的每个键类型添加一个索引器。

public class NewDic<T>
{
    public void Add(string key1, long key2, T value)
    {
        mDic.Add(key1, value);
        mDic.Add(key2, value);
    }
    public T this[string s]
    {
        get { return mDic[s]; }
    }
    public T this[long l]
    {
        get { return mDic[l]; }
    }

    Dictionary<object, T> mDic = new Dictionary<object, T>();
}
        NewDic<long> dic = new NewDic<long>();
        dic.Add("abc", 20, 10);
        Console.WriteLine(dic["abc"]);
        Console.WriteLine(dic[20]);

这不是一个合适的字典,但可以用于简单的字典式添加删除功能。

也可以是泛型的,只要在键类型中适当地实现IComparable,并相应地更改字典代码。(注意,键的默认值不允许管理歧义!)

internal class KeyValueSet //this dictionary item is tailor made for this example
{
    public string KeyStr { get; set; }
    public int KeyInt { get; set; }
    public int Value { get; set; }
    public KeyValueSet() { }
    public KeyValueSet(string keyStr, int keyInt, int value)
    {
        KeyStr = keyStr;
        KeyInt = keyInt;
        Value = value;
    }
}
public class DoubleKeyDictionary
{
    List<KeyValueSet> _list = new List<KeyValueSet>();
    private void Add(KeyValueSet set)
    {
        if (set == null)
            throw new InvalidOperationException("Cannot add null");
        if (string.IsNullOrEmpty(set.KeyStr) && set.KeyInt == 0)
            throw new InvalidOperationException("Invalid key");
        if (!string.IsNullOrEmpty(set.KeyStr) && _list.Any(l => l.KeyStr.Equals(set.KeyStr))
            || set.KeyInt != 0 && _list.Any(l => l.KeyInt == set.KeyInt))
            throw new InvalidOperationException("Either of keys exists");
        _list.Add(set);
    }
    public void Add(string keyStr, int keyInt, int value)
    {
        Add(new KeyValueSet { KeyInt = keyInt, KeyStr = keyStr, Value = value });
    }
    public void Add(string key, int value)
    {
        Add(new KeyValueSet { KeyInt = 0, KeyStr = key, Value = value });
    }
    public void Add(int key, int value)
    {
        Add(new KeyValueSet { KeyInt = key, KeyStr = string.Empty, Value = value });
    }
    public void Remove(int key)
    {
        if (key == 0)
            throw new InvalidDataException("Key not found");
        var val = _list.First(l => l.KeyInt == key);
        _list.Remove(val);
    }
    public void Remove(string key)
    {
        if (string.IsNullOrEmpty(key))
            throw new InvalidDataException("Key not found");
        var val = _list.First(l => l.KeyStr == key);
        _list.Remove(val);
    }
    public void Remove(KeyValueSet item)
    {
        _list.Remove(item);
    }
    public int this[int index]
    {
        get
        {
            if (index != 0 && _list.Any(l => l.KeyInt == index))
                return _list.First(l => l.KeyInt == index).Value;
            throw new InvalidDataException("Key not found");
        }
        set
        {
            Add(index, value);
        }
    }
    public int this[string key]
    {
        get
        {
            if (!string.IsNullOrEmpty(key) && _list.Any(l => l.KeyStr == key))
                return _list.First(l => l.KeyStr == key).Value;
            throw new InvalidDataException("Key not found");
        }
        set
        {
            Add(key, value);
        }
    }
}

测试DoubleKeyDictionary

var dict = new DoubleKeyDictionary();
dict.Add(123, 1);
dict.Add(234, 2);
dict.Add("k1", 3);
dict.Add("k2", 4);            
dict[456] = 5;
dict["k3"] = 6;
dict.Add("k4", 567, 7);
dict.Remove(123);
Console.WriteLine(dict[234]); //2
Console.WriteLine(dict["k2"]); //4
Console.WriteLine(dict[456]); //5
Console.WriteLine(dict[567]); //7
Console.WriteLine(dict["k4"]); //7
Console.WriteLine(dict[123]); //exception

作为本地解决方案,我使用简单的方法:

假设我有一个由字符串标识的产品集合和一个表单,每个产品都有按钮。

管理按钮的状态时,我需要通过字符串键找到按钮。在处理点击时,我需要通过按钮实例查找产品id。

为了不维护两个独立的字典,我做了以下操作:

public class SPurchaseOption
{
    public Button Button;
    public string ProductID;
    public string SomeOtherAssociatedData;
}
Dictionary<object, SPurchaseOption> purchaseOptions;

当按钮初始化时,我在Dictionary中添加了两个条目,即

Key: ProductID, Value: "SPurchaseOption"
Key: Button,    Value: "SPurchaseOption"

对于更通用的方法,如果你需要一个常用的组件,你将不得不围绕两个字典构建一个包装,即:

public class DoubleKeyedDictionary<TKey1, TKey2, TValue>
{
    class SItem
    {
        public TKey1 key1;
        public TKey2 key2;
        public TValue value;
    }
    Dictionary<TKey1, SItem> dic1;
    Dictionary<TKey2, SItem> dic2;
}

将允许通过任意键访问值和备选键。

正如您的问题的评论所建议的那样,您可以简单地为Dictionary使用Object密钥:

Dictionary<Object, long> dict = new Dictionary<Object, long>();
dict.Add("abc", 111);
dict.Add(345, 111);

为了得到一个更简洁的解决方案,你可以将这个字典包装在一个自定义类中,并创建你自己版本的Add方法:

public void Add(ISet<Object> keys, T value){
    foreach(Object k in keys)
    {
        _privateDict.Add(k, value); 
    }
}

Dictionary<Tuple<string, long>, long>怎么样?元组是按值比较的,所以它应该以预期的方式唯一地索引。另外,现在您不必在两个地方打包long值(并处理同步所有值的痛苦)。

这个方法怎么样?基本上,仍然使用基于字典的策略,但是通过带有重载索引器属性的类来封装它。所以它看起来像一个字典,感觉像一个字典,但支持多个键(不像字典,哈哈)。

public class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>
{
    private readonly Dictionary<TFirstKey, TValue> firstKeyDictionary = 
        new Dictionary<TFirstKey, TValue>();
    private readonly Dictionary<TSecondKey, TFirstKey> secondKeyDictionary = 
        new Dictionary<TSecondKey, TFirstKey>();
    public TValue this[TFirstKey idx]
    {
        get
        {
            return firstKeyDictionary[idx];
        }
        set
        {
            firstKeyDictionary[idx] = value;
        }
    }
    public TValue this[TSecondKey idx]
    {
        get
        {
            var firstKey = secondKeyDictionary[idx];
            return firstKeyDictionary[firstKey];
        }
        set
        {
            var firstKey = secondKeyDictionary[idx];
            firstKeyDictionary[firstKey] = value;
        }
    }
    public IEnumerable<KeyValuePair<TFirstKey, TValue>> GetKeyValuePairsOfFirstKey()
    {
        return firstKeyDictionary.ToList();
    }
    public IEnumerable<KeyValuePair<TSecondKey, TValue>> GetKeyValuePairsOfSecondKey()
    {
        var r = from s in secondKeyDictionary
            join f in firstKeyDictionary on s.Value equals f.Key
            select new KeyValuePair<TSecondKey, TValue>(s.Key, f.Value);
        return r.ToList();
    }
    public void Add(TFirstKey firstKey, TSecondKey secondKey, TValue value)
    {
        firstKeyDictionary.Add(firstKey, value);
        secondKeyDictionary.Add(secondKey, firstKey);
    }
    public bool Remove(TFirstKey firstKey)
    {
        if (!secondKeyDictionary.Any(f => f.Value.Equals(firstKey))) return false;
        var secondKeyToDelete = secondKeyDictionary.First(f => f.Value.Equals(firstKey));
        secondKeyDictionary.Remove(secondKeyToDelete.Key);
        firstKeyDictionary.Remove(firstKey);
        return true;
    }
    public bool Remove(TSecondKey secondKey)
    {
        if (!secondKeyDictionary.ContainsKey(secondKey)) return false;
        var firstKey = secondKeyDictionary[secondKey];
        secondKeyDictionary.Remove(secondKey);
        firstKeyDictionary.Remove(firstKey);
        return true;
    }
}

测试代码…

    static void Main(string[] args)
    {
        var dict = new MultiKeyDictionary<string, long, long>();
        dict.Add("abc", 111, 1234);
        dict.Add("def", 222, 7890);
        dict.Add("hij", 333, 9090);
        Console.WriteLine(dict["abc"]); // expect 1234
        Console.WriteLine(dict["def"]); // expect 7890
        Console.WriteLine(dict[333]); // expect 9090
        Console.WriteLine();
        Console.WriteLine("removing def");
        dict.Remove("def");
        Console.WriteLine();
        Console.WriteLine("now we have:");
        foreach (var d in dict.GetKeyValuePairsOfFirstKey())
        {
            Console.WriteLine($"{d.Key} : {d.Value}");
        }
        Console.WriteLine();
        Console.WriteLine("removing 333");
        dict.Remove(333);
        Console.WriteLine();
        Console.WriteLine("now we have:");
        foreach (var d in dict.GetKeyValuePairsOfSecondKey())
        {
            Console.WriteLine($"{d.Key} : {d.Value}");
        }
        Console.ReadLine();
    }

起初我想我可以创建一个实现IDictionary<TKey1, TValue>IDictionary<TKey2, TValue>的类,并且只有一个Dictionary作为字段,并以最小的逻辑将大多数方法委托给单个Dictionary。

这种方法的问题是TKey1TKey2 可能是相同的类型,这是一个问题,因为这个新类将实现相同的接口两次。当TKey1string, TKey2也是string时,运行时应该调用哪个方法? 正如上面其他人建议的那样,最好创建自己的数据结构,在幕后使用一个或两个字典。例如,如果您提前知道希望使用stringint作为密钥,则可以使用以下方法:
public class StringIntDictionary<TValue> : IDictionary<string, TValue>, IDictionary<int, TValue>
{
    private IDictionary<object, TValue> _dictionary = new Dictionary<object, TValue>();
    // implement interface below, delegate to _dictionary
}

这将允许您同时使用stringint键:

var dict = StringIntDictionary<bool>();
dict["abc"] = true;
dict[123] = true;

一种非常粗糙的方法,在我找到更好的方法之前可能已经足够了。

class MyClass
{
  public string StringKey = "";
  public int IntKey = 0;
  public override Equals(object obj)
  {
    // Code to return true if all fields are equal
  }
}
Dictionary <MyClass, string> MyDict;
MyClass myClass;
MyDict[MyDict.Keys.FirstOrDefault(x => x.Equals(MyClass))];

在我看来,使用元组的答案是正确的。不幸的是,我的NuGet太旧了,无法获得我想要使用的ValueTuple包,所以我的字段不是"item1","item2"等。那会比我在这里做的更让人困惑。当我更改VS/NuGet版本时,它一直是ValueTuples用于这种情况。这周我已经第二次遇到这种需要了!

在效率方面有更好的办法

public class MultiKeyDictionary<TKeyType1, TKeyType2, TValueType>
{
    private readonly object threadLock = new object();
    private readonly Dictionary<TKeyType1, TValueType> _dictionary1 = new Dictionary<TKeyType1, TValueType>();
    private readonly Dictionary<TKeyType2, TValueType> _dictionary2 = new Dictionary<TKeyType2, TValueType>();
    private readonly Dictionary<TKeyType1, TKeyType2> _Key1Key2Map = new Dictionary<TKeyType1, TKeyType2>();
    private readonly Dictionary<TKeyType2, TKeyType1> _Key2Key1Map = new Dictionary<TKeyType2, TKeyType1>();
    public bool Add(TKeyType1 key1, TKeyType2 key2, TValueType v)
    {
        if (ContainsKey1(key1) || ContainsKey2(key2))
            return false;
        _dictionary1.Add(key1, v);
        _dictionary2.Add(key2, v);
        _Key1Key2Map.Add(key1, key2);
        _Key2Key1Map.Add(key2, key1);
        return true;
    }
    public bool ContainsKey1(TKeyType1 key)
    {
        return _dictionary1.ContainsKey(key);
    }
    public bool ContainsKey2(TKeyType2 key)
    {
        return _dictionary2.ContainsKey(key);
    }       
    //Note if TKeyType1 and TKeyType2 are the same then we are forced to use GetBy functions
    public TValueType GetByKey1(TKeyType1 key)
    {
        return _dictionary1[key];
    }
    public TValueType GetByKey2(TKeyType2 key)
    {
        return _dictionary2[key];
    }
    public bool SetByKey1(TKeyType1 key, TValueType val)
    {
        if (ContainsKey1(key))
            return false;
        lock (threadLock)
        {
            var key2 = _Key1Key2Map[key];
            _dictionary1[key] = val;
            _dictionary2[key2] = val;
        }
        return true;
    }
    public bool SetByKey2(TKeyType2 key, TValueType val)
    {
        if (ContainsKey2(key))
            return false;
        lock (threadLock)
        {
            var key1 = _Key2Key1Map[key];
            _dictionary1[key1] = val;
            _dictionary2[key] = val;
        }
        return true;
    }
    public void RemoveUsingKey1(TKeyType1 key)
    {
        lock (threadLock)
        {
            var key2 = _Key1Key2Map[key];
            _dictionary1.Remove(key);
            _dictionary2.Remove(key2);
            _Key1Key2Map.Remove(key);
            _Key2Key1Map.Remove(key2);
        }
    }
    public void RemoveUsingKey2(TKeyType2 key)
    {
        lock (threadLock)
        {
            var key1 = _Key2Key1Map[key];
            _dictionary1.Remove(key1);
            _dictionary2.Remove(key);
            _Key1Key2Map.Remove(key1);
            _Key2Key1Map.Remove(key);
        }
    }
    public bool Contains(TKeyType1 key)
    {
        return _dictionary1.ContainsKey(key);
    }
    public bool Contains(TKeyType2 key)
    {
        return _dictionary2.ContainsKey(key);
    }
    public TValueType this[TKeyType1 key]
    {
        get => GetByKey1(key);
        set => SetByKey1(key, value);
    }
    public TValueType this[TKeyType2 key]
    {
        get => GetByKey2(key);
        set => SetByKey2(key, value);
    }
    public void Remove(TKeyType1 key)
    {
        RemoveUsingKey1(key);
    }
    public void Remove(TKeyType2 key)
    {
        RemoveUsingKey2(key);
    }
    public int Count => _dictionary1.Count;
    public Dictionary<TKeyType1, TValueType>.KeyCollection Key1s => _dictionary1.Keys;
    public Dictionary<TKeyType2, TValueType>.KeyCollection Key2s => _dictionary2.Keys;
    public Dictionary<TKeyType1, TValueType>.ValueCollection Values => _dictionary1.Values;
    public void Clear()
    {
        lock (threadLock)
        {
            _dictionary1.Clear();
            _dictionary2.Clear();
            _Key1Key2Map.Clear();
            _Key2Key1Map.Clear();
        }
    }
    //Map between Keys
    public TKeyType2 Key2(TKeyType1 key)
    {
        return _Key1Key2Map[key];
    }
    public TKeyType1 Key1(TKeyType2 key)
    {
        return _Key2Key1Map[key];
    }
}

我已经创建了自己的版本。它更复杂一些。文档应该解释其中的一些功能。从本质上讲,它允许以合理的方式处理相同值的两个键,自动合并条目,可能是线程安全的(未经测试),允许将键映射在一起,并处理删除条目,同时在其基础上具有字典的功能。当添加一个条目,但其中一个键已经存在时,它将只添加键并覆盖该值。它在逻辑上与其他形式的集合非常不同,所以这是我能实现的最多的集合。

一些带有键对的结构似乎不合适,因为我需要在现有条目中任意添加第二个键,并且考虑到合并功能。我还考虑了两个键使用相同类型的情况,但也考虑了它们不使用相同类型的情况。

/// <summary> A collection that internally uses a list (which in turn internally uses an array), and two dictionaries for the index.
/// This allows operating it based on two keys and provides means to (automatically) map keys to each other.
/// The indexing of the internal list is treated specially. In order to not infringe on the validity of the dictionaries' references to the indexes,
/// they are kept identical. Removing is handled by setting the entries to 'null', and once a new item is added, they are overwritten. </summary>
/// <typeparam name="TKey1"> The first key. </typeparam>
/// <typeparam name="TKey2"> The second key. </typeparam>
/// <typeparam name="T"> The stored value type. </typeparam>
public class TwoKeyDictionary<TKey1, TKey2, T> : IEnumerable<TwoKeyDictionaryEntry<TKey1, TKey2, T>>, IReadOnlyCollection<TwoKeyDictionaryEntry<TKey1, TKey2, T>>
{
    private readonly Dictionary<TKey1, int> _keys01 = new Dictionary<TKey1, int> ();
    private readonly Dictionary<TKey2, int> _keys02 = new Dictionary<TKey2, int> ();
    private readonly List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> _items = new List<TwoKeyDictionaryEntry<TKey1, TKey2, T>> ();
    private int _freeIndex = 0; // The index of the first free slot.
    private int _freeCount = 0; // Free before the last value.
    private readonly object _lock = new object ();

    public TwoKeyDictionary () { }

    /// <summary> Adds an item. </summary>
    public bool Add (TKey1 key, T value)
    {
        return AddByKey1 (key, value);
    }
    /// <summary> Adds an item. </summary>
    public bool Add (TKey2 key, T value)
    {
        return AddByKey2 (key, value);
    }
    /// <summary> Adds an item. </summary>
    public bool AddByKey1 (TKey1 key, T value)
    {
        lock (_lock)
        {
            return AddByKey1Internal (key, value);
        }
    }
    /// <summary> Adds an item. </summary>
    public bool AddByKey2 (TKey2 key, T value)
    {
        lock (_lock)
        {
            return AddByKey2Internal (key, value);
        }
    }
    /// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'. </summary>
    public bool Add (TKey1 key1, TKey2 key2, T value)
    {
        return Add (key1, key2, value, false);
    }
    /// <summary> Adds an item with two keys. If either key already exists, it will map the other key to it. The value will only be overwritten if it's 'null'.
    /// This may also define how the key is mapped, if occurring. </summary>
    public bool Add (TKey1 key1, TKey2 key2, T value, bool mapToKey2)
    {
        lock (_lock)
        {
            return AddInternal (key1, key2, value, mapToKey2);
        }
    }

    /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the other.
    /// By default this will map to key1. </summary>
    public bool Map (TKey1 key1, TKey2 key2)
    {
        return MapToKey1 (key1, key2);
    }
    /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key2. </summary>
    public bool MapToKey1 (TKey1 key1, TKey2 key2)
    {
        lock (_lock)
        {
            return MapToKey1Internal (key1, key2);
        }
    }
    /// <summary> Maps both keys together. If either key exists, it will add the other one to it. If both exist, it will merge the entries and delete the one with key1. </summary>
    public bool MapToKey2 (TKey1 key1, TKey2 key2)
    {
        lock (_lock)
        {
            return MapToKey2Internal (key1, key2);
        }
    }

    /// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary>
    public bool Remove (TKey1 key)
    {
        return RemoveByKey1 (key);
    }
    /// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary>
    public bool Remove (TKey2 key)
    {
        return RemoveByKey2 (key);
    }
    /// <summary> Removes an entry based on key1. If there is a key2 mapped to it, it will be removed as well. </summary>
    public bool RemoveByKey1 (TKey1 key)
    {
        lock (_lock)
        {
            return RemoveByKey1Internal (key);
        }
    }
    /// <summary> Removes an entry based on key2. If there is a key1 mapped to it, it will be removed as well. </summary>
    public bool RemoveByKey2 (TKey2 key)
    {
        lock (_lock)
        {
            return RemoveByKey2Internal (key);
        }
    }
    /// <summary> Removes an entry based on both, key1 and key2. Any entries related to either keys will be removed. </summary>
    public bool Remove (TKey1 key1, TKey2 key2)
    {
        lock (_lock)
        {
            return RemoveByKey1Internal (key1) | RemoveByKey2Internal (key2);
        }
    }

    /// <summary> Tries to return a value based on key1. </summary>
    public bool TryGetValue (TKey1 key, out T value)
    {
        return TryGetValueByKey1 (key, out value);
    }
    /// <summary> Tries to return a value based on key2. </summary>
    public bool TryGetValue (TKey2 key, out T value)
    {
        return TryGetValueByKey2 (key, out value);
    }
    /// <summary> Tries to return a value based on key1. </summary>
    public bool TryGetValueByKey1 (TKey1 key, out T value)
    {
        if (key == null) { value = default; return false; }
        if (_keys01.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                value = entry.Value;
                return true;
            }
        }
        value = default;
        return false;
    }
    /// <summary> Tries to return a value based on key2. </summary>
    public bool TryGetValueByKey2 (TKey2 key, out T value)
    {
        if (key == null) { value = default; return false; }
        if (_keys02.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                value = entry.Value;
                return true;
            }
        }
        value = default;
        return false;
    }
    /// <summary> Tries to return a value based on key1 or key2. Prioritizes key1. </summary>
    public bool TryGetValue (TKey1 key1, TKey2 key2, out T value)
    {
        return TryGetValue (key1, key2, false, out value);
    }
    /// <summary> Tries to return a value based on key1 or key2. </summary>
    public bool TryGetValue (TKey1 key1, TKey2 key2, bool prioritizeKey2, out T value)
    {
        return prioritizeKey2 ? TryGetValue (key1, out value) || TryGetValue (key2, out value) : TryGetValue (key2, out value) || TryGetValue (key1, out value);
    }

    /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
    public bool ContainsKey (TKey1 key)
    {
        return ContainsKey1 (key);
    }
    /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
    public bool ContainsKey (TKey2 key)
    {
        return ContainsKey2 (key);
    }
    /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
    public bool ContainsKey1 (TKey1 key)
    {
        if (key == null) return false;
        if (_keys01.TryGetValue (key, out int index)) return _items[index] != null;
        else return false;
    }
    /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
    public bool ContainsKey2 (TKey2 key)
    {
        if (key == null) return false;
        if (_keys02.TryGetValue (key, out int index)) return _items[index] != null;
        else return false;
    }
    /// <summary> Returns 'true' if they key and the entry still exists. The stored value itself may still be 'null' regardless. </summary>
    public bool ContainsKey (TKey1 key1, TKey2 key2)
    {
        return ContainsKey1 (key1) || ContainsKey2 (key2);
    }

    #region Internal
    // Returns true if this wasn't the last position.
    private bool GetFreeIndex (bool apply, out int index)
    {
        if (_freeCount == 0)
        {
            index = _items.Count;
            return false;
        }
        else
        {
            index = _freeIndex;
            if (apply)
            {
                // We must find the next free slot.
                int freeIndex = _freeIndex + 1;
                int count = _items.Count;
                while (freeIndex < count && _items[freeIndex] != null)
                {
                    freeIndex++;
                }
                if (freeIndex == count) _freeCount = 0;
                else Interlocked.Decrement (ref _freeCount);
                _freeIndex = freeIndex;
            }
            return true;
        }
    }
    private bool MapToKey1Internal (TKey1 key1, TKey2 key2)
    {
        if (key1 == null || key2 == null) return false;
        bool s1 = _keys01.TryGetValue (key1, out int index1);
        bool s2 = _keys02.TryGetValue (key2, out int index2);
        if (s1 && s2)
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1];
            TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2];
            RemoveByKey2Internal (key2);
            e1.Key2 = key2;
            if (e1.Value == null) e1.Value = e2.Value;
            return true;
        }
        else if (s1)
        {
            _items[index1].Key2 = key2;
            _keys02.Add (key2, index1);
            return true;
        }
        else if (s2)
        {
            _items[index2].Key1 = key1;
            _keys01.Add (key1, index2);
            return true;
        }
        else return false;
    }
    private bool MapToKey2Internal (TKey1 key1, TKey2 key2)
    {
        if (key1 == null || key2 == null) return false;
        bool s1 = _keys01.TryGetValue (key1, out int index1);
        bool s2 = _keys02.TryGetValue (key2, out int index2);
        if (s1 && s2)
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> e1 = _items[index1];
            TwoKeyDictionaryEntry<TKey1, TKey2, T> e2 = _items[index2];
            RemoveByKey1Internal (key1);
            e2.Key1 = key1;
            if (e2.Value == null) e2.Value = e1.Value;
            return true;
        }
        else if (s1)
        {
            _items[index1].Key2 = key2;
            return true;
        }
        else if (s2)
        {
            _items[index2].Key1 = key1;
            return true;
        }
        else return false;
    }
    private bool AddByKey1Internal (TKey1 key, T value)
    {
        if (key == null) return false;
        if (_keys01.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                entry.Value = value;
                return true;
            }
            else
            {
                _keys01.Remove (key);
                return AddByKey1Internal (key, value);
            }
        }
        else
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key, default, value);
            if (GetFreeIndex (true, out int freeIndex))
            {
                _items[freeIndex] = item;
            }
            else
            {
                _items.Add (item);
            }
            _keys01.Add (key, freeIndex);
            return true;
        }
    }
    private bool AddByKey2Internal (TKey2 key, T value)
    {
        if (key == null) return false;
        if (_keys02.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                entry.Value = value;
                return true;
            }
            else
            {
                _keys02.Remove (key);
                return AddByKey2Internal (key, value);
            }
        }
        else
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> item = new TwoKeyDictionaryEntry<TKey1, TKey2, T> (default, key, value);
            if (GetFreeIndex (true, out int freeIndex))
            {
                _items[freeIndex] = item;
            }
            else
            {
                _items.Add (item);
            }
            _keys02.Add (key, freeIndex);
            return true;
        }
    }
    private bool AddInternal (TKey1 key1, TKey2 key2, T value, bool mapToKey2)
    {
        if (key1 == null) return AddByKey2Internal (key2, value);
        else if (key2 == null) return AddByKey1Internal (key1, value);
        bool hasKey1 = _keys01.TryGetValue (key1, out int index1);
        bool hasKey2 = _keys02.TryGetValue (key2, out int index2);
        if (hasKey1 && hasKey2)
        {
            // We have 2 different entries' keys that point to the same value. Merge them to one key, remove the other.
            if (mapToKey2)
            {
                if (MapToKey2Internal (key1, key2))
                {
                    _items[index2].Value = value;
                }
            }
            else
            {
                if (MapToKey1Internal (key1, key2))
                {
                    _items[index1].Value = value;
                }
            }
        }
        else if (hasKey1)
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index1];
            entry.Key2 = key2;
            entry.Value = value;
        }
        else if (hasKey2)
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index2];
            entry.Key1 = key1;
            entry.Value = value;
        }
        else
        {
            _items.Add (new TwoKeyDictionaryEntry<TKey1, TKey2, T> (key1, key2, value));
        }
        return true;
    }
    private bool RemoveByKey1Internal (TKey1 key)
    {
        if (key == null) return false;
        if (_keys01.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                _keys01.Remove (key);
                if (entry.Key2 != null) _keys02.Remove (entry.Key2);
                if (index == _items.Count - 1)
                {
                    _items.RemoveAt (index);
                }
                else
                {
                    _items[index] = null;
                    _freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index;
                    Interlocked.Increment (ref _freeCount);
                }
                return true;
            }
            else
            {
                _keys01.Remove (key);
            }
        }
        return false;
    }
    private bool RemoveByKey2Internal (TKey2 key)
    {
        if (key == null) return false;
        if (_keys02.TryGetValue (key, out int index))
        {
            TwoKeyDictionaryEntry<TKey1, TKey2, T> entry = _items[index];
            if (entry != null)
            {
                _keys02.Remove (key);
                if (entry.Key1 != null) _keys01.Remove (entry.Key1);
                if (index == _items.Count - 1)
                {
                    _items.RemoveAt (index);
                }
                else
                {
                    _items[index] = null;
                    _freeIndex = _freeCount > 0 ? Math.Min (_freeIndex, index) : index;
                    Interlocked.Increment (ref _freeCount);
                }
                return true;
            }
            else
            {
                _keys02.Remove (key);
            }
        }
        return false;
    }
    #endregion
    #region Interface Implementations
    public int Count => _items.Count (j => j != null);
    public IEnumerator<TwoKeyDictionaryEntry<TKey1, TKey2, T>> GetEnumerator ()
    {
        return _items.Where (j => j != null).GetEnumerator ();
    }
    IEnumerator IEnumerable.GetEnumerator ()
    {
        return _items.Where (j => j != null).GetEnumerator ();
    }
    #endregion
}

/// <summary> The entry class of <see cref="TwoKeyDictionary{TKey1, TKey2, T}"/>, which grants references to the keys in both dictionaries used. </summary>
/// <typeparam name="TKey1"> The first key. </typeparam>
/// <typeparam name="TKey2"> The second key. </typeparam>
/// <typeparam name="T"> The stored value type. </typeparam>
public class TwoKeyDictionaryEntry<TKey1, TKey2, T>
{
    public TKey1 Key1 { get; internal set; }
    public TKey2 Key2 { get; internal set; }
    public T Value { get; internal set; }

    internal TwoKeyDictionaryEntry () { }
    internal TwoKeyDictionaryEntry (TKey1 key1, TKey2 key2, T value)
    {
        Key1 = key1;
        Key2 = key2;
        Value = value;
    }

    public override string ToString ()
    {
        return $"{Key1?.ToString () ?? "---"} | {Key2?.ToString () ?? "---"} | {Value}";
    }
}