Skip to content

iikuzmychov/BidirectionalDictionary

Repository files navigation

NuGet Downloads codecov

BidirectionalDictionary

Proper implementation of a bidirectional dictionary, also known as "bidirectional map" or "two-way dictionary", for .NET Standard 2.0 and higher.

Quick sample

using System.Collections.Generic;

var countryCapitals = new BidirectionalDictionary<string, string>()
{
    ["Italy"] = "Rome",
    ["India"] = "New Delhi",
    ["USA"]   = "Washington, D.C.",
};

Console.WriteLine(countryCapitals["Italy"]); // "Rome"
Console.WriteLine(countryCapitals.Inverse["Rome"]); // "Italy"

Read-only support

You can expose a read-only view over an existing bidirectional dictionary, keeping inversion capabilities intact. The wrapper uses the same underlying data and blocks modifications through the read-only API.

From BidirectionalDictionary:

using System.Collections.Generic;

BidirectionalDictionary<Key, Value> bidirectionalDictionary = ...; 
var readOnly = bidirectionalDictionary.AsReadOnly();

From IBidirectionalDictionary:

using System.Collections.Generic;
using System.Collections.ObjectModel;

IBidirectionalDictionary<Key, Value> bidirectionalDictionary = ...;
var readOnly = new ReadOnlyBidirectionalDictionary<Key, Value>(bidirectionalDictionary);

Thread safety

You can use ConcurrentBidirectionalDictionary for multi-threaded scenarios:

using System.Collections.Concurrent;

var countryCapitals = new ConcurrentBidirectionalDictionary<string, string>();
countryCapitals.TryAdd("Italy", "Rome");

Console.WriteLine(countryCapitals["Italy"]); // "Rome"
Console.WriteLine(countryCapitals.Inverse["Rome"]); // "Italy"

The concurrent implementation keeps read operations lock-free. Write operations use fine-grained key/value stripe locks instead of locking the whole dictionary.

Interfaces

To support abstraction-friendly code, the package exposes two interfaces:

  • IBidirectionalDictionary
  • IReadOnlyBidirectionalDictionary

BidirectionalDictionary, ReadOnlyBidirectionalDictionary, and ConcurrentBidirectionalDictionary all implement these interfaces, so you can depend on contracts instead of concrete types when needed.

LINQ extensions

The package includes LINQ-extensions to create a BidirectionalDictionary directly from sequences.

From KeyValuePair<TKey, TValue>:

using System.Linq;

IEnumerable<KeyValuePair<int, string>> source = new[]
{
    new KeyValuePair<int, string>(1, "one"),
    new KeyValuePair<int, string>(2, "two")
};

var bidirectionalDictionary = source.ToBidirectionalDictionary();

From tuple sequence:

using System.Linq;

IEnumerable<(string Key, string Value)> source = new[]
{
    (Key: "US", Value: "United States"),
    (Key: "IT", Value: "Italy")
};

var bidirectionalDictionary = source.ToBidirectionalDictionary();

From arbitrary source with selectors:

using System.Linq;

var users = new[]
{
    new { Id = 10, Email = "a@example.com" },
    new { Id = 20, Email = "b@example.com" }
};

var bidirectionalDictionary = users.ToBidirectionalDictionary(user => user.Id, user => user.Email);

You can also pass custom comparers via overloads with keyComparer and valueComparer.

Benchmarks

Benchmark highlights:

  • Key lookups stay close to Dictionary<TKey, TValue>.
  • Reverse lookups by value are O(1).
  • Additional mutation cost is the tradeoff for keeping both indexes synced.

More details can be found in benchmarks/BidirectionalDictionary.Benchmarks.

Used by

used by

License

The library is licensed under the MIT license.