views:

1167

answers:

2

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)));
+2  A: 

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));
    }
}
Marc Gravell
Sorry, forgot to ad the link to the blog post. Updated OP. I'll ad my real target to the OP too to illustrate.
borisCallens
The blog post is linked, but it looks broken to me. Even the simplest sort of projection comparison is going to break it for any cases where hashing is used.
Jon Skeet
Well, that about settles it then. "No there isn't" seems to be the answer.
borisCallens
+1  A: 

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);
    }
}
Matthew Flaschen
Well, yes for now I've solved it with a custom Comparer class, but I thought it would be a nice excercise to search for a better solution. Seems there is no real nice solution though.
borisCallens