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<string>{    protected NameSet(IEnumerable<string> keys, IEqualityComparer<string> 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<string> keys, IEqualityComparer<string> keyComparer)    : base(keys, keyComparer)     {}    public static NameSetCI Create(params string[] keys) =>         new NameSetCI(keys, StringComparer.OrdinalIgnoreCase);}the other with the suffix CS which obviously stands for Case Sensitive.class NameSetCS : NameSet{    private NameSetCS(IEnumerable<string> keys, IEqualityComparer<string> keyComparer)    : base(keys, keyComparer)    { }    public static NameSetCS Create(params string[] keys) =>         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<ISet<string>>{    public bool Equals(ISet<string> x, ISet<string> y) => x.Overlaps(y);    public int GetHashCode(ISet<string> obj) => 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<NameSetCI, string>(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(); // fbdic.Add(NameSetCI.Create(foo), f); // bam!
Dictionary with ISet key as a collection of names
c#;dictionary;set
You can just use a single class with a generic Create method which has constraints for IEqualityComparer<string>:internal class NameSetGeneric : HashSet<string>{    private NameSetGeneric(IEnumerable<string> keys, IEqualityComparer<string> keyComparer)        : base(keys ?? throw new ArgumentNullException(nameof(keys)), keyComparer)    {    }    public static NameSetGeneric Create<T>(T comparer, params string[] keys)        where T : IEqualityComparer<string> => 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<NameSetGeneric, string>(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(); //fbdic.Add(NameSetGeneric.Create(StringComparer.OrdinalIgnoreCase, foo), f); // bam!