Question

I have a class Product with a set of properties:

public class Product
{
     public string Id { get; set; }
     public string Name { get; set; }
     public string Categories { get; set; }
}

From a component, I obtain a List<Product> and, for several reasons, I need to use Reflection to get the properties of Product and then get the Distinct values and their Count() for each property.

Is it possible to achieve my goal through reflection? If not is there any other way to do it? Thanks!

UPDATE

The problem is that I do not know in advance which properties I have to use and which properties are in the Product class. That's why I think reflection is the best option.

I can achieve the same result by using a Switch - Case construct where the switch compare the Property Name extarcted from the class and each Case corresponds to a specific Property Name. But the flexibility of this solution is not enough for my problem

Was it helpful?

Solution

So it sounds like you're asking for something slightly different than the rest of us previously thought. You're not looking for the number of distinct values, or you're looking for the number of duplicates of each distinct value, which is essentially a group-by with a count of each group.

private static Dictionary<string, Dictionary<object, int>> 
    getDistinctValues<T>(List<T> list)
{
    var properties = typeof(T).GetProperties();

    var result = properties
        //The key of the first dictionary is the property name
        .ToDictionary(prop => prop.Name,
            //the value is another dictionary
            prop => list.GroupBy(item => prop.GetValue(item, null))
                //The key of the inner dictionary is the unique property value
                //the value if the inner dictionary is the count of that group.
                .ToDictionary(group => group.Key, group => group.Count()));

    return result;
}

At one point I had broken this up into two methods, but I condensed it down a bit to the point where I don't think it's needed. If you have trouble wrapping your head around all of the levels of nesting of this query feel free to ask for further clarifications.

OTHER TIPS

private static int DistinctCount<T>(IEnumerable<T> items, string property)
{
    var propertyInfo = typeof(T).GetProperty(property);

    return items.Select(x => propertyInfo.GetValue(x, null)).Distinct().Count();
}

Usage:

List<Product> prods = GetProductsFromSomeplace();

int distinctCountById = DistinctCount(prods, "Id");
int distinctCountByName = DistinctCount(prods, "Name");
int distinctCountByCategories = DistinctCount(prods, "Categories");

If you want to allow for a custom IEqualityComparer for the properties, you can have an overload:

private static int DistinctCount<TItems, TProperty>(IEnumerable<TItems> items,
                                                    string property,
                                                    IEqualityComparer<TProperty> propertyComparer)
{
    var propertyInfo = typeof(TItems).GetProperty(property);

    return items.Select(x => (TProperty)propertyInfo.GetValue(x, null))
                                 .Distinct(propertyComparer).Count();
}

And use like so:

List<Product> prods = GetProductsFromSomeplace();

int distinctCountById = DistinctCount(prods, "Id", new MyCustomIdComparer());

Where MyCustomIdComparer implements IEqualityComparer<TProperty> (in this case IEC<int>)

I present a solution below - but ideally you should look at breaking creating an abstraction for this problem that allows an object returning an IEnumerable<T> to provide a list of 'filterable' properties of the T, along with the values that are to be used. That way whatever is returning the data from the data source can do this with full knowledge. It pushes more of the work back to your data source/service/whatever, but it makes your UI much simpler.

Since you don't know the properties - then you can do this (I'm assuming assuming an IEnumerable because I'm assuming a generic solution is out - since you say you need reflection). If you do have a typed expression (i.e. you actually have a List<Product>) then a generic solution would be better as it would remove the need to get the first item:

public Dictionary<string, IEnumerable<object>> 
  GetAllPropertyDistincts(IEnumerable unknownValues)
{
  //need the first item for the type:
  var first = unknownValues.Cast<object>().First(); //obviously must NOT be empty :)
  var allDistinct = first.GetType()
    .GetProperties(BindingFlags.Public | BindingFlags.Instance)
    .Select(p => new 
      { 
        PropName = p.Name,
        Distinct = unknownValues.Cast<object>().Select(
          o => property.GetValue(o, null)
        ).Distinct() 
      }).ToDictionary(v => v.PropName, v => v.Distinct);
}

Now you have a dictionary, keyed by the property name of every distinct value for each property of every object in your untyped enumerable (well - assuming they're all of the same type or base). Note - there might be some issues with properties of certain types and the default IEqualityComparer that the Distinct extension method uses - because it's a generic method and at the moment it'll be using EqualityComparer<object>.Default - which won't necessarily work for some types.

To turn this into a generic solution you can just change the first four lines to:

public Dictionary<string, IEnumerable<object>> 
  GetAllPropertyDistincts<T>(IEnumerable<T> unknownValues)
{
  var allDistinct = typeof(T)

With the .GetProperties(BindingFlags.Public | BindingFlags.Instance) line following, and then change the inner call unknownValues.Cast<object>().Select( to just unknownValues.Select(.

If the list is not typed with Product but indeed with an open generic parameter T and this parameter has no restriction (where T : Product) then casting can help

int count = list
    .Cast<Product>()
    .Select(p => p.Id)
    .Distinct()
    .Count();  

Okay, so I got slightly carried out with my answer, but here it is... a fully fledged distinct value counter. This doesn't completely answer your question, but should be a good start towards counting a property on a given object. Using this, in conjunction to looping through all of the properties on an object, should do the trick :p

/// <summary>
/// A distinct value counter, using reflection
/// </summary>
public class DistinctValueCounter<TListItem>
{
    /// <summary>
    /// Gets or sets the associated list items
    /// </summary>
    private IEnumerable<TListItem> ListItems { get; set; }

    /// <summary>
    /// Constructs a new distinct value counter
    /// </summary>
    /// <param name="listItems">The list items to check</param>
    public DistinctValueCounter(IEnumerable<TListItem> listItems)
    {
        this.ListItems = listItems;
    }

    /// <summary>
    /// Gets the distinct values, and their counts
    /// </summary>
    /// <typeparam name="TProperty">The type of the property expected</typeparam>
    /// <param name="propertyName">The property name</param>
    /// <returns>A dictionary containing the distinct counts, and their count</returns>
    public Dictionary<TProperty, int> GetDistinctCounts<TProperty>(string propertyName)
    {
        var result = new Dictionary<TProperty, int>();

        // check if there are any list items
        if (this.ListItems.Count() == 0)
        {
            return result;
        }

        // get the property info, and check it exists
        var propertyInfo = this.GetPropertyInfo<TProperty>(this.ListItems.FirstOrDefault(), propertyName);
        if (propertyInfo == null)
        {
            return result;
        }

        // get the values for the property, from the list of items
        return ListItems.Select(item => (TProperty)propertyInfo.GetValue(item, null))
            .GroupBy(value => value)
            .ToDictionary(value => value.Key, value => value.Count());
    }

    /// <summary>
    /// Gets the property information, for a list item, by its property name
    /// </summary>
    /// <typeparam name="TProperty">The expected property type</typeparam>
    /// <param name="listItem">The list item</param>
    /// <param name="propertyName">The property name</param>
    /// <returns>The property information</returns>
    private PropertyInfo GetPropertyInfo<TProperty>(TListItem listItem, string propertyName)
    {
        // if the list item is null, return null
        if (listItem == null)
        {
            return null;
        }

        // get the property information, and check it exits
        var propertyInfo = listItem.GetType().GetProperty(propertyName);
        if (propertyInfo == null)
        {
            return null;
        }

        // return the property info, if it is a match
        return propertyInfo.PropertyType == typeof(TProperty) ? propertyInfo : null;
    }
}

Usage:

var counter = new DistinctValueCounter<Person>(people);
var resultOne = counter.GetDistinctCounts<string>("Name");

If I understand the goal, you should be able to just use LINQ:

List<Product> products = /* whatever */
var distinctIds = products.Select(p=>p.Id).Distinct();
var idCount = distinctIds.Count();
...
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top