Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HashSet constructor with custom IEqualityCompare defined by lambda?

Currently the HashSet<T> constructor that allows you to define your equality comparison yourself is the HashSet<T>(IEqualityComparer<T> comparer) constructor. I would like to define this EqualityComparer as a lambda.

I found this blog post that has made a class that allows you to generate your comparer through lambda and then hides the construction of this class with an extention method to do for example an Except().

Now I would like to do the same but with a constructor. Is it possible to create a constructor through an extention method? Or is there another way I could somehow create a HashSet<T>(Func<T,T,int> comparer)?

--UPDATE--
For clarity, this is (a snippet of) a freehand version of what I'm trying to accomplish:

HashSet<FileInfo> resultFiles = new HashSet<FileInfo>(
    srcPath.GetFiles(),
    new LambdaComparer<FileInfo>(
        (f1, f2) => f1.Name.SubString(10).Equals(f2.Name.SubString(10))));

or more ideally

HashSet<FileInfo> resultFiles = new HashSet<FileInfo>(
    srcPath.GetFiles(),
    (f1, f2) => f1.Name.SubString(10).Equals(f2.Name.SubString(10)));
like image 698
Boris Callens Avatar asked May 19 '09 07:05

Boris Callens


2 Answers

No, you can't add constructors (even with extension methods).

Assuming you have some magic way to get from a Func<T,T,int> to an IEqualityComparer<T> (I'd be interested in reading that blog post if you can cite it) - then the closest you can do is probably something like:

public static class HashSet {
    public static HashSet<T> Create<T>(Func<T, T, int> func) {
        IEqualityComparer<T> comparer = YourMagicFunction(func);
        return new HashSet<T>(comparer);
    }
}

However; I'm dubious as to what you can do with a lambda for equality... you have two concepts to express: hashing, and true equality. What would your lambda look like? If you are trying to defer to child properties, then perhaps a Func<T,TValue> to select the property, and use EqualityComparer<TValue>.Default internally... something like:

class Person {
    public string Name { get; set; }
    static void Main() {
        HashSet<Person> people = HashSetHelper<Person>.Create(p => p.Name);
        people.Add(new Person { Name = "Fred" });
        people.Add(new Person { Name = "Jo" });
        people.Add(new Person { Name = "Fred" });
        Console.WriteLine(people.Count);
    }
}
public static class HashSetHelper<T> {
    class Wrapper<TValue> : IEqualityComparer<T> {
        private readonly Func<T, TValue> func;
        private readonly IEqualityComparer<TValue> comparer;
        public Wrapper(Func<T, TValue> func,
            IEqualityComparer<TValue> comparer) {
            this.func = func;
            this.comparer = comparer ?? EqualityComparer<TValue>.Default;
        }
        public bool Equals(T x, T y) {
            return comparer.Equals(func(x), func(y));
        }

        public int GetHashCode(T obj) {
            return comparer.GetHashCode(func(obj));
        }
    }
    public static HashSet<T> Create<TValue>(Func<T, TValue> func) {
        return new HashSet<T>(new Wrapper<TValue>(func, null));
    }
    public static HashSet<T> Create<TValue>(Func<T, TValue> func,
        IEqualityComparer<TValue> comparer)
    {
        return new HashSet<T>(new Wrapper<TValue>(func, comparer));
    }
}
like image 106
Marc Gravell Avatar answered Oct 22 '22 00:10

Marc Gravell


Marc is right. There's no simple way for a single lambda to express the information needed for both Equals and GetHashCode. And if you provide a GetHashCode that returns different hashes for "equal" elements, that will cause incorrect behavior.

Here's my compromise implementation. It will allow any generic Func (like Marc, I disregarded the int because you did not explain it), and that will give correct (in that it complies with the contract), but very inefficient behavior.

I recommend you stick with a real IEqualityComparer that meets your needs. It's a shame C# does not support anonymous inner classes, though.

public static class HashSetDelegate
{
    public static HashSet<T> Create<T>(Func<T, T, bool> func)
    {
    return new HashSet<T>(new FuncIEqualityComparerAdapter<T>(func));
    }

    private class FuncIEqualityComparerAdapter<U> : IEqualityComparer<U>
    {
    private Func<U, U, bool> func;
    public FuncIEqualityComparerAdapter(Func<U, U, bool> func)
    {
        this.func = func;
    }

    public bool Equals(U a, U b)
    {
        return func(a, b);
    }

    public int GetHashCode(U obj)
    {
        return 0;
    }  

    }
}

public class HashSetTest
{
    public static void Main()
    {
    HashSet<string> s = HashSetDelegate.Create((string a, string b) => string.Compare(a, b, true) == 0);
    }
}
like image 25
Matthew Flaschen Avatar answered Oct 21 '22 23:10

Matthew Flaschen