1

I want to distinct a list of objects just based on some properties. These properties are gotten via reflection and some conditions. I searched a lot but cannot found any code snippets or solutions that are able to do a loop in this lambda expression.

List<PropertyInfo> propList = ... 
var distinctList = FullList
  .GroupBy(uniqueObj => 
  { 
  //do a loop to iterate all elements in propList 
  })
  .Select(x => x.First());
Renan Araújo
  • 3,533
  • 11
  • 39
  • 49

2 Answers2

1

You can create expression using the property name with this method:

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;
}

And use like this:

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

Now you can make a distinct easily:

List<Person> distinctPeople = allPeople
  .GroupBy(exp.Compile())
  .Select(g => g.First())
  .ToList();
Renan Araújo
  • 3,533
  • 11
  • 39
  • 49
  • Thank you very much, Renan. However, I think it should be GroupBy(p => exp) but it doesn't work. – Trí Nguyễn Nov 21 '15 at 04:22
  • In my test, with _**var exp = GetPropertySelector("Name");**_ I got different result between **GroupBy(p => exp)** and **GroupBy(p => p.Name)**. – Trí Nguyễn Nov 21 '15 at 05:56
  • Hello @TríNguyễn, sorry about the delay. Just add `Compile()` method in your `exp`. Take a look at my updated answer. – Renan Araújo Nov 21 '15 at 12:41
  • Hi @Renan, I was very busy in yesterday so that I don't have time to understand and test your code. This is actually interesting. I really appreciate for you help. I think it would work but this code is just for a single property. I'll try to make a loop based on it. – Trí Nguyễn Nov 22 '15 at 04:35
1

Ok, took me a while to think this one through.

Basically, you can use the Linq GroupBy operator, but you need to use the overload that accepts a custom IEQualityComparer, because you want to verify equality of the objects based on a subset of all their properties.

The subset of properties is stored in a List<PropertyInfo> that you created somewhere else in your code, or that you receive from a service or whatever.

So, implementing IEqualityComparer, then use it with 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.

I hope this helps you get closer to your solution.

Cheers

Luc Morin
  • 5,302
  • 20
  • 39
  • Looks like it should work. I'm a bit worried about the exponentially increasing hash code though. – Gert Arnold Nov 21 '15 at 22:52
  • I have the same thought with @GertArnold. However, your solution should be accepted because it is really close to what I'm looking for. Thank you very much! – Trí Nguyễn Nov 22 '15 at 04:47
  • Well, the hash will of course increase with the number of PropertyInfo in the List, but is computed for each object, so it's not a "global" value that will keep growing on and on. There might be different ways to compute the hash, I grabbed this one from another SO answer, so I agree that it can probably be improved. – Luc Morin Nov 22 '15 at 05:12