Dictionary with ISet key as a collection of names I'm writing a command-line utility and I need to find commands (and parameters) by name. The name can either be a full name like save or a shortcut s.

I thought I use a dictionary with an ISet key and a custom comparer. At first I had a list and searched for the name with LINQ but I'd like to have something more convenient. The performance doesn't matter - this time convenience goes first. There will be at most a few dozens of commands. I know I could use a string and map each name to the command but this isn't cool :-)



First, there is a NameSet that is the base class for concrete sets.

class NameSet : HashSet&lt;string&gt;
{
    protected NameSet(IEnumerable&lt;string&gt; keys, IEqualityComparer&lt;string&gt; keyComparer)
    : base(keys ?? throw new ArgumentNullException(nameof(keys)), keyComparer)
    { }
}


one with the suffix CI which stands for Case Insensitive (like the collation in Sql Server)

class NameSetCI : NameSet 
{
    private NameSetCI(IEnumerable&lt;string&gt; keys, IEqualityComparer&lt;string&gt; keyComparer)
    : base(keys, keyComparer) 
    {}

    public static NameSetCI Create(params string[] keys) =&gt; 
        new NameSetCI(keys, StringComparer.OrdinalIgnoreCase);
}


the other with the suffix CS which obviously stands for Case Sensitive.

class NameSetCS : NameSet
{
    private NameSetCS(IEnumerable&lt;string&gt; keys, IEqualityComparer&lt;string&gt; keyComparer)
    : base(keys, keyComparer)
    { }

    public static NameSetCS Create(params string[] keys) =&gt; 
        new NameSetCS(keys, StringComparer.Ordinal);
}




The comparer for this is very simple. It just looks if there is any overlapping set.

internal class SetComparer : IEqualityComparer&lt;ISet&lt;string&gt;&gt;
{
    public bool Equals(ISet&lt;string&gt; x, ISet&lt;string&gt; y) =&gt; x.Overlaps(y);

    public int GetHashCode(ISet&lt;string&gt; obj) =&gt; 0; // Force Equals.
}


With the hash code 0 it doesn't seem to be O(1) anymore but all the keys are in one place an the logic is just a single Overlaps method. LINQ wouldn't be faster anyway and it would mean a lot more work.



Example:

var dic = new Dictionary&lt;NameSetCI, string&gt;(new SetComparer());

dic.Add(NameSetCI.Create("foo", "bar"), "fb");
dic.Add(NameSetCI.Create("qux"), "q");
dic[NameSetCI.Create("baz")] = "b";
dic[NameSetCI.Create("bar")].Dump(); // fb
dic.Add(NameSetCI.Create("foo"), "f"); // bam!
You can just use a single class with a generic Create method which has constraints for IEqualityComparer&lt;string&gt;:

internal class NameSetGeneric : HashSet&lt;string&gt;
{
    private NameSetGeneric(IEnumerable&lt;string&gt; keys, IEqualityComparer&lt;string&gt; keyComparer)
        : base(keys ?? throw new ArgumentNullException(nameof(keys)), keyComparer)
    {
    }

    public static NameSetGeneric Create&lt;T&gt;(T comparer, params string[] keys)
        where T : IEqualityComparer&lt;string&gt; =&gt; new NameSetGeneric(keys, comparer);
}


You can even go further and make the whole class generic, but that's only if you want to work with different data types.

Example usage:

var dic = new Dictionary&lt;NameSetGeneric, string&gt;(new SetComparer());
dic.Add(NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, "foo", "bar"), "fb");
dic.Add(NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, "qux"), "q");
dic[NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, "baz")] = "b";
dic[NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, "bar")].Dump(); //fb
dic.Add(NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, "foo"), "f"); // bam!