带有两个键和一个值的.NET字典

时间:2015-09-24 12:54:36

标签: c# .net dictionary

.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中是否有一些解决方案可以包含可以容纳多个密钥的字典?

14 个答案:

答案 0 :(得分:13)

如果你希望你的价值从任何一个键“可以找到”,我会像你现在一样使用两个词典。 但是我会将其包含在类中,方法名称为FindByXXXFindByYYY

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

因此没有标准字典来执行此操作,因为每个用户之间的要求不同。

(请注意,您不希望使用复合键的字典,因为只要您希望查找某个项目,就需要知道这两个键。)

答案 1 :(得分:10)

也许,这样的事情:

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

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

答案 2 :(得分:4)

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

你可能从不同的角度来看待这个问题。有两个词典:

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进行全面扫描以进行删除。这可能会对紧密环路等性能产生不良影响。

我现在无法想到解决这个问题的好方法。也许其他人可能会对如何改进它有一些想法。

我希望这会有所帮助。

答案 3 :(得分:4)

如果您使用的是C#7.0,最好的方法是使用元组类型和文字:

// Declare
var dict = new Dictionary<(string, long), long>();

// Add
dict.Add(("abc", 345), 111);

// Get
var searchedValue = dict[("abc", 345)];

答案 4 :(得分:2)

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

这使您可以使用自己的类包装字典或字典对。

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

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

这也可以是通用的,在密钥类型中正确实现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

答案 7 :(得分:0)

Dictionary<Tuple<string, long>, long>怎么样?元组按值进行比较,因此它应以预期的方式唯一地进行索引。此外,现在您不必将long值包装在两个地方(并处理在任何地方同步值的奇妙痛苦)。

这种做法怎么样?基本上,仍然使用基于字典的策略,但通过具有重载索引器属性的类来对其进行外观。所以它看起来像字典,感觉像字典,但支持多个键(不像字典,LOL)。

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

答案 8 :(得分:0)

根据您对问题的评论中的建议,您只需使用Object密钥即可Dictionary

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

答案 9 :(得分:0)

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

这种方法的问题是TKey1TKey2 可能属于同一类型,这是一个问题,因为这个新类将实现两次相同的接口。当TKey1stringTKey2时,运行时应调用哪种方法也是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;

答案 10 :(得分:0)

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

想象一下,我有一个由字符串标识的产品集合和一个带有每个产品按钮的表单。

管理按钮的状态时,我需要按字符串键找到按钮。 处理点击时,我需要按按钮实例查找产品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;
}

这将通过任何键访问值和替代键。

答案 11 :(得分:0)

一种非常粗略的方法,直到我找到更好的方法为止。

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

以我的金钱,对use tuples的回答是正确的答案。不幸的是,我的NuGet太旧了,无法获得我想使用的ValueTuple包,因此我的字段不是'item1','item2'等。这比我在这里所做的要更加混乱。当我更改VS / NuGet版本时,在这种情况下都是ValueTuples。本周第二次我遇到了需求!

答案 12 :(得分:0)

在效率上有更好的地方。

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

答案 13 :(得分:0)

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

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

/// <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}";
    }
}