【问题标题】:Remove duplicate objects based on a list properties根据列表属性删除重复对象
【发布时间】:2015-11-21 03:23:07
【问题描述】:

我想仅基于某些属性来区分对象列表。这些属性是通过反射和一些条件获得的。我搜索了很多,但找不到任何代码 sn-ps 或能够在此 lambda 表达式中执行循环的解决方案。

List<PropertyInfo> propList = ... 
var distinctList = FullList
  .GroupBy(uniqueObj => 
  { 
  //do a loop to iterate all elements in propList 
  })
  .Select(x => x.First());

【问题讨论】:

标签: c# .net linq generics reflection


【解决方案1】:

好的,我花了一些时间来考虑这一点。

基本上,您可以使用 Linq GroupBy 运算符,但您需要使用接受自定义 IEQualityComparer 的重载,因为您想根据对象所有属性的子集验证对象的相等性。

属性子集存储在您在代码中的其他位置创建的List&lt;PropertyInfo&gt; 中,或者您从服务或其他地方接收到的。

所以,实现IEqualityComparer,然后与GroupBy一起使用:

//Dummy class representing your data.
//
//Notice that I made the IEqualityComparer as a child class only
//for the sake of demonstration

public class DataObject 
{
    public string Name { get; set; }
    public int Age { get; set; }
    public int Grade { get; set; }

    public static List<PropertyInfo> GetProps()
    {
        //Only return a subset of the DataObject class properties, simulating your List<PropertyInfo>
        return typeof(DataObject).GetProperties().Where(p => p.Name == "Name" || p.Name == "Grade").ToList();
    }


    public class DataObjectComparer : IEqualityComparer<DataObject>
    {
        public bool Equals(DataObject x, DataObject y)
        {
            if (x == null || y == null)
                return false;

            foreach (PropertyInfo pi in DataObject.GetProps())
            {
                if (!pi.GetValue(x).Equals(pi.GetValue(y)))
                    return false;
            }
            return true;
        }

        public int GetHashCode(DataObject obj)
        {
            int hash = 17;

            foreach (PropertyInfo pi in DataObject.GetProps())
            {
                hash = hash * 31 + pi.GetValue(obj).GetHashCode();
            }

            return hash;
        }
    }
}


//Then use that in your code:
//

List<DataObject> lst = new List<DataObject>();
lst.Add(new DataObject { Name = "Luc", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 49, Grade = 100 });
lst.Add(new DataObject { Name = "Dan", Age = 23, Grade = 100 });
lst.Add(new DataObject { Name = "Luc", Age = 20, Grade = 80 });

List<DataObject> dist = lst.GroupBy(p => p, new DataObject.DataObjectComparer()).Select(g => g.First()).ToList();    
//The resulting list now contains distinct objects based on the `Name` and `Grade` properties only.

我希望这可以帮助您更接近您的解决方案。

干杯

【讨论】:

  • 看起来应该可以了。不过,我有点担心呈指数增长的哈希码。
  • 我对@GertArnold 有同样的想法。但是,您的解决方案应该被接受,因为它非常接近我正在寻找的。非常感谢!
  • 嗯,散列当然会随着 List 中 PropertyInfo 的数量而增加,但是是为每个对象计算的,所以它不是一个会不断增长的“全局”值。可能有不同的方法来计算哈希,我从另一个 SO 答案中获取了这个,所以我同意它可能会得到改进。
【解决方案2】:

您可以使用此方法使用属性名称创建表达式:

public static Expression<Func<T, object>> GetPropertySelector<T>(string propertyName)
{
    var arg = Expression.Parameter(typeof(T), "x");
    var property = Expression.Property(arg, propertyName);
    //return the property as object
    var conv = Expression.Convert(property, typeof(object));
    var exp = Expression.Lambda<Func<T, object>>(conv, new ParameterExpression[] { arg });
    return exp;
}

并像这样使用:

var exp = GetPropertySelector<Person>("PropertyName");

现在您可以轻松地进行区分:

List<Person> distinctPeople = allPeople
  .GroupBy(exp.Compile())
  .Select(g => g.First())
  .ToList();

【讨论】:

  • 非常感谢瑞南。但是,我认为它应该是 GroupBy(p => exp) 但它不起作用。
  • 在我的测试中,使用 var exp = GetPropertySelector("Name"); 我在 GroupBy( p => exp)GroupBy(p => p.Name).
  • 您好@TríNguyễn,抱歉耽搁了。只需在 exp 中添加 Compile() 方法即可。看看我更新的答案。
  • 嗨@Renan,我昨天很忙,所以我没有时间理解和测试你的代码。这实际上很有趣。我真的很感谢你的帮助。我认为它会起作用,但此代码仅适用于单个属性。我会尝试根据它做一个循环。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-09
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-14
相关资源
最近更新 更多