使用反射从List<T>中获取类型T的属性名称和不同值

3

我有一个类名为Product,具有一组属性:

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

从一个组件中,我获取了一个 `List`,由于某些原因,我需要使用 **反射(Reflection)** 来获取 `Product` 的属性,然后获取每个属性的不同值及其 `Count()`。
通过反射来实现我的目标是否可行?如果不行,还有其他方法吗?谢谢!
**更新**
问题在于我事先不知道要使用哪些 **属性**,也不知道 `Product` 类中有哪些属性。这就是为什么我认为反射是最好的选择的原因。
我可以通过使用 `Switch-Case` 结构来实现相同的结果,其中 `switch` 比较从类中提取的 **属性名称**,每个 `case` 对应一个特定的 **属性名称**。但是这种解决方案的灵活性对我的问题来说不够。

1
你为什么要增加限制,强迫自己使用反射,特别是对于一个不自然需要它或与之不兼容的问题? - Servy
1
你能解释一下为什么需要使用反射吗?也许有更好的解决方案(例如强制转换)。 - Olivier Jacot-Descombes
谢谢大家,我知道如何使用LINQ来做到这一点,这很简单。问题是我事先不知道要使用哪些属性以及哪些属性在Product类中。这就是为什么我认为反射是最好的选择。 - CiccioMiami
我理解您想以动态方式选择属性;但是,您怎么可能不知道Product类中有哪些属性呢?这些属性必须在编译时就已知。 - Olivier Jacot-Descombes
我正在尝试在一个带有复选框的Web应用程序中实现缩小搜索范围,并且我必须动态创建它们(复选框的ID是属性名称)。同样,当我从用户接收POST请求时,我必须获取复选框列表的ID并将其与产品的属性名称进行比较。 - CiccioMiami
6个回答

4

看起来您想要的东西与我们之前认为的略有不同。您不是在寻找不同值的数量,而是要找到每个不同值的重复次数,这本质上是一种按组分组并计算每个组的计数。

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

曾经我将这个方法分成了两个,但是我将其压缩到了一个点上,我认为这不是必要的。如果你对这个查询的所有嵌套层次感到困惑,请随时要求进一步的澄清。


当OP明确表示必须通过反射来完成时,这有什么帮助呢? - Andras Zoltan
@AndrasZoltan "如果没有其他方法,那么这是一个好的“其他方法”。如果他不能或不想使用它,那没关系,但仍然值得提供。" - Servy
添加了一个基于反射的解决方案。 - Servy
谢谢,你的解决方案真的很好。能否解释一下为什么要使用HashSet?如果某个属性是一个集合的情况下呢? - CiccioMiami
嗨,我想获取每个属性的“不同非空”值,而不是计数。我可以将其更改为Select(item => prop.GetValue(item, null))。 - Transformer
显示剩余5条评论

4
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实现了接口(在本例中为IEC<int>)。


3
我在下面提供了一种解决方案,但最好的方法是为此问题创建一个抽象层,允许返回一个实现了 IEnumerable<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(


0
如果列表没有使用 Product 类型,而是使用开放式泛型参数 T,并且该参数没有任何限制(where T : Product),那么可以使用强制转换来解决问题。
int count = list
    .Cast<Product>()
    .Select(p => p.Id)
    .Distinct()
    .Count();  

0
好的,我可能有些过于激动地回答了你的问题,但这就是答案......一个完整的不同值计数器。 这并不完全回答了你的问题,但应该是开始计算给定对象上属性计数的良好开端。 结合使用此方法和循环遍历对象上的所有属性应该就能得出结果啦 :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");

-2

如果我理解你的目标,你应该只需要使用LINQ:

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

请注意,您正在两次迭代列表以执行此操作;您可以使用 ToList 获取不同的值,以便只查询一次列表。 - Servy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接