Generic PropertyEqualityComparer <t> </t>

时间:2011-02-22 13:21:38

标签: c# generics

我开发了一个通用的PropertyEqualityComparer工作正常,但我不确定我是否以正确的方式完成了它,所以如果有些人可以改进代码或批评,那么他是受欢迎的。

注意:如果是引用类型,属性应该实现IEquatable。

    public sealed class PropertyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Type _iequatable = Type.GetType("System.IEquatable`1", false, true);
    private readonly PropertyInfo _property;

    public PropertyEqualityComparer(string property)
    {
        PropertyInfo propInfos = typeof(T).GetProperty(property);
        if (propInfos == null)
        {
            throw new ArgumentNullException();
        }

        // Ensure Property is Equatable (override of HashCode)
        if (propInfos.PropertyType.IsValueType
            || (!propInfos.PropertyType.IsValueType
                && propInfos.PropertyType.GetInterfaces().Any(type => type.Name == _iequatable.Name)))
        {
            _property = propInfos;
        }
        else
        {
            throw new ArgumentException();
        }
    }

    public bool Equals(T x, T y)
    {
        var xValue = _property.GetValue(x, null);
        var yValue = _property.GetValue(y, null);

        return xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        return _property.GetValue(obj, null).GetHashCode();
    }
}

我创建了两个类,看看是否有效,一切都很好,这里是类:

public sealed class A
{
    private string _s1;
    private B _b;

    public A(string s1, B b)
    {
        _s1 = s1;
        _b = b;
    }

    public string S1
    {
        get { return _s1; }
        set { _s1 = value; }
    }

    public B B
    {
        get { return _b; }
        set { _b = value; }
    }
}

public sealed class B : IEquatable<B>
{
    private string _s;

    public string S
    {
      get { return _s; }
      set { _s = value; }
    }

    public B(string s)
    {
        S = s;
    }

    public override int GetHashCode()
    {
        return S.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as B);
    }

    public bool Equals(B other)
    {
        return (other == null)
            ? false
            : this.S == other.S;
    }
}

测试代码:

B b = new B("baby");
A[] __a = { new A("first", b), new A("second", b), new A("third", b)};
PropertyEqualityComparer<A> aComparer = new PropertyEqualityComparer<A>("B");

var vDistinct = __a.Distinct(aComparer).ToArray();
// vDistinct = __a[0] { first, baby }
var vContains = __a.Contains(new A("a", new B("baby")), aComparer);
// True
vContains = __a.Contains(new A("b", new B("foobar")), aComparer);
// False

这里有待改进吗?

谢谢!

2 个答案:

答案 0 :(得分:6)

由于必须在编译时知道T的类型,因此无需将属性名称作为string传递。您可以传递委托来执行类型安全的快速成员访问,而不是脆弱的慢速反射:

var comparer = new ProjectionEqualityComparer<A, B>(a => a.B);

// ...

public sealed class ProjectionEqualityComparer<TSource, TKey>
    : EqualityComparer<TSource>
{
    private readonly Func<TSource, TKey> _keySelector;
    private readonly IEqualityComparer<TKey> _keyComparer;

    public ProjectionEqualityComparer(Func<TSource, TKey> keySelector,
        IEqualityComparer<TKey> keyComparer = null)
    {
        if (keySelector == null)
            throw new ArgumentNullException("keySelector");

        _keySelector = keySelector;
        _keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
    }

    public override bool Equals(TSource x, TSource y)
    {
        if (x == null)
            return (y == null);

        if (y == null)
            return false;

        return _keyComparer.Equals(_keySelector(x), _keySelector(y));
    }

    public override int GetHashCode(TSource obj)
    {
        if (obj == null)
           throw new ArgumentNullException("obj");

        return _keyComparer.GetHashCode(_keySelector(obj));
    }
}

如果您确实需要强制执行IEquatable<T>规则,那么您只需添加where TKey : IEquatable<TKey>通用约束,但实际上并不是必需的。使用EqualityComparer<TKey>.Default应该为您完成所有这些工作,或者如果您愿意,可以传递自定义IEqualityComparer<TKey>实施。

答案 1 :(得分:0)

看起来很不错。可以加速用于获取属性值的反射。这里有一篇关于这个主题的好文章:http://www.codeproject.com/KB/cs/ReflectionDemo.aspx

总会被问到的问题是“为什么” - 你需要如此通用吗?

如果这是一段将被大量调用的代码,那么您可以考虑使用CodeDom来生成自定义实现。