通过反射从列表获取属性名称和 T 类型的非重复值

本文关键字:类型 属性 反射 列表 获取 | 更新日期: 2023-09-27 18:33:08

我有一个具有一组属性的产品类:

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

从组件中,我获得了一个List<Product>,出于多种原因,我需要使用 Reflection 来获取Product的属性,然后获取每个属性的Distinct值及其Count()

是否有

可能通过反思来实现我的目标?如果没有,还有其他方法可以做到吗?谢谢!

更新

问题是我事先不知道我必须使用哪些属性以及哪些属性Product 类中。这就是为什么我认为反思是最好的选择。

我可以通过使用 Switch - Case 构造来获得相同的结果,其中开关比较从类中导出的属性名称,并且每个Case对应于一个特定的属性名称。但是这个解决方案的灵活性不足以解决我的问题

通过反射从列表<T>获取属性名称和 T 类型的非重复值

所以听起来你要求的东西与我们其他人以前想象的略有不同。 您不是在查找非重复值的数量,而是在查找每个非重复值的重复项数,这实质上是具有每个组计数的分组依据。

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

有一次,我把它分成了两种方法,但我把它压缩了一点,以至于我认为不需要它。 如果您在了解此查询的所有嵌套级别时遇到问题,请随时要求进一步澄清。

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

用法:

List<Product> prods = GetProductsFromSomeplace();
int distinctCountById = DistinctCount(prods, "Id");
int distinctCountByName = DistinctCount(prods, "Name");
int distinctCountByCategories = DistinctCount(prods, "Categories");

如果要允许为属性使用自定义 IEqualityComparer,则可以使用重载:

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

并像这样使用:

List<Product> prods = GetProductsFromSomeplace();
int distinctCountById = DistinctCount(prods, "Id", new MyCustomIdComparer());

MyCustomIdComparer 实现IEqualityComparer<TProperty>的地方(在本例中为 IEC<int>(

我在下面提出了一个解决方案 - 但理想情况下,您应该考虑为此问题创建一个抽象,该抽象允许返回IEnumerable<T>的对象提供T的"可过滤"属性列表,以及要使用的值。 这样,从数据源返回数据的任何内容都可以在完全了解的情况下执行此操作。 它将更多的工作推回数据源/服务/其他任何内容,但它使您的 UI 更简单。

由于您不知道属性 - 那么您可以这样做(我假设假设一个IEnumerable因为我假设一个通用解决方案已经出来 - 因为你说你需要反思(。 如果你有一个类型化的表达式(即你实际上有一个List<Product>(,那么一个通用的解决方案会更好,因为它将消除获取第一项的需要:

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

现在你有一个字典,由非类型化枚举中每个对象的每个属性的每个不同值的属性名称键控(好吧 - 假设它们都是相同的类型或基数(。 请注意 - 某些类型的属性和 Distinct 扩展方法使用的默认IEqualityComparer可能存在一些问题 - 因为它是一个泛型方法,目前它将使用 EqualityComparer<object>.Default - 这不一定适用于某些类型。

要将其转换为通用解决方案,您只需将前四行更改为:

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

在后面.GetProperties(BindingFlags.Public | BindingFlags.Instance)行,然后将内部调用unknownValues.Cast<object>().Select(更改为仅unknownValues.Select(

如果列表不是用Product而是用一个开放的泛型参数T输入的,并且这个参数没有限制(where T : Product(,那么强制转换会有所帮助

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

好的,所以我对我的回答有点感动,但这里是...一个成熟的独特价值计数器。这并不能完全回答您的问题,但应该是计算给定对象属性的良好开端。使用它,结合循环遍历对象上的所有属性,应该可以解决问题: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;
    }
}

用法:

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

如果我理解目标,你应该能够只使用 LINQ:

List<Product> products = /* whatever */
var distinctIds = products.Select(p=>p.Id).Distinct();
var idCount = distinctIds.Count();
...