Here is a solution that gives you the performance of a dictionary lookup in both directions. I assume that you want to ignore duplicate tuples and the default comparer is sufficient. Implementing the other operations like Remove is left as an exercise for the reader
public class TwoWayCollection<A, B>
{
private Dictionary<A, HashSet<B>> byADictionary = new Dictionary<A, HashSet<B>>();
private Dictionary<B, HashSet<A>> byBDictionary = new Dictionary<B, HashSet<A>>();
public IEnumerable<B> Get(A a)
{
return byADictionary[a];
}
public IEnumerable<A> InverseGet(B b)
{
return byBDictionary[b];
}
public void Add(A a, B b)
{
if (!byADictionary.ContainsKey(a))
{
byADictionary[a] = new HashSet<B>();
}
byADictionary[a].Add(b);
if (!byBDictionary.ContainsKey(b))
{
byBDictionary[b] = new HashSet<A>();
}
byBDictionary[b].Add(a);
}
}
Then to use it is literally your proposed code. Both Get and InverseGet approach O(1) as per dictionary
TwoWayCollection<int, string> a = new TwoWayCollection<int, string>();
a.Add(1, "a");
a.Add(1, "b");
a.Add(2, "a");
a.Get(1); //returns ["a", "b"]
a.InverseGet("a"); //returns [1, 2]