5

我正在编写这个实现 IEqualityComparer 的简洁类,这样我就可以将任何匿名类型传递给它(或者实际上是任何具有属性的类型),它会通过比较类型的属性值来自动比较类型。

public class CompareProperty<T> : IEqualityComparer<T>
    {
        private Type type;
        private PropertyInfo propInfo;
        private string _fieldName;

        public string fieldName
        {
            get;
            set;
        }

        public CompareProperty(string fieldName)
        {
            this.fieldName = fieldName;
        }

        public bool Equals<T>(T x, T y)
        {
            if (this.type == null)
            {
                type = x.GetType();
                propInfo = type.GetProperty(fieldName);
            }
            object objX = propInfo.GetValue(x, null);
            object objY = propInfo.GetValue(y, null);
            return objX.ToString() == objY.ToString();
        }
    }

我认为这是一个很好的小助手功能,我可以多次使用。

为了使用它,我必须这样做:

var t = typeof(CompareProperty<>);
var g = t.MakeGenericType(infoType.GetType());
var c = g.GetConstructor(new Type[] {String.Empty.GetType()});
var obj = c.Invoke(new object[] {"somePropertyName"});

很公平,但是我该如何处理它返回的 obj 变量呢?

someEnumerable.Distinct(obj);

distinct 扩展函数的重载不接受这个,因为它看不到 IEqualityComparer 类型,当然它只看到一个对象。

someEnumerable.Distinct((t) obj);
someEnumerable.Distinct(obj as t);

这也行不通。找不到类型/命名空间(红色下划线)。

我该如何直截了当?

4

2 回答 2

7

我将首先为非匿名类型提供一个解决方案,然后将其扩展到也适用于匿名类型。希望它可以帮助您了解人们在对您的问题发表评论时想说什么。

我的“通用”IEqualityComparer<>看起来像这样:

public class PropertyComparer<T> : IEqualityComparer<T>
{
    private readonly PropertyInfo propertyToCompare;

    public PropertyComparer(string propertyName)
    {
        propertyToCompare = typeof(T).GetProperty(propertyName);
    }
    public bool Equals(T x, T y)
    {
        object xValue = propertyToCompare.GetValue(x, null);
        object yValue = propertyToCompare.GetValue(y, null);
        return xValue.Equals(yValue);
    }

    public int GetHashCode(T obj)
    {
        object objValue = propertyToCompare.GetValue(obj, null);
        return objValue.GetHashCode();
    }
}

假设我们想首先将它与非匿名类型一起使用,例如Person

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
 }

用法将非常简单:

IEnumerable<Person> people = ... ; // some database call here
var distinctPeople = people.Distinct(new PropertyComparer<Person>("FirstName"));

如您所见,要使用PropertyComparer<T>,我们需要指定T要相互比较的类型( )实例。T处理匿名类型时会发生什么?由于它们是在运行时生成的,因此您不能通过直接创建其实例来使用比较器,因为您不知道T编译时的情况。相反,您需要使用类型推断让 C# 编译器T自行从上下文推断。这样做的好方法是编写以下扩展方法:

public static class LinqExtensions
{
    public static IEnumerable<T> WithDistinctProperty<T>(this IEnumerable<T> source, string propertyName)
    {
        return source.Distinct(new PropertyComparer<T>(propertyName));
    }
}

现在它也适用于匿名类型:

var distinctPeople = people
        .Select(x => new { x.FirstName, x.Surname })
        .WithDistinctProperty("FirstName");

突然之间,不需要在任何地方指定查询正在处理的确切类型,因为 C# 编译器足够聪明,可以从上下文中推断出来(在这种情况下,它是source从扩展方法)。

希望这会帮助你。

于 2012-07-06T19:31:32.267 回答
2

只需添加自己的验证。

class PropertyComparer<T, TResult> : IEqualityComparer<T>
{
    private Func<T, TResult> _getProperty;

    public PropertyComparer(Func<T, TResult> predicate)
    {
        this._getProperty = predicate;
    }

    public bool Equals(T x, T y)
    {
        return this._getProperty(x).Equals(_getProperty(y));
    }

    public int GetHashCode(T obj)
    {
        return this._getProperty(obj).GetHashCode();
    }
}

具有扩展方法的类:

public static class IEnumerableExtensions
{
    public static IEnumerable<TSource> DistinctBy<TSource, TResult>
        (this IEnumerable<TSource> source, Func<TSource, TResult> predicate)
    {
        return source.Distinct(new PropertyComparer<TSource, TResult>(predicate));
    }
}

用法:

someEnumerable.DistinctBy(i => i.SomeProperty);
于 2017-09-14T16:00:04.023 回答