【问题标题】:Based on a list of class objects, how to create a list of valid property names?基于类对象列表,如何创建有效属性名称列表?
【发布时间】:2019-12-12 07:06:11
【问题描述】:

我有一个如下所示的 Data 类。

public class Data
{
    public string Dt1 { get; set; }
    public string Dt2 { get; set; }
    public string Dt3 { get; set; }
    public string Dt4 { get; set; }
    public string Dt5 { get; set; }
}

以及它的类对象列表,以及以下示例数据。

var list = new List<Data>
{
    new Data() { Dt1 = "DtA", Dt2 = string.Empty, Dt3 = "-", Dt5 = "DtC" },
    new Data() { Dt1 = "DtB", Dt2 = string.Empty, Dt3 = string.Empty, Dt5 = "-" },
    new Data() { Dt1 = "DtC", Dt2 = "-", Dt5 = "-" },
    new Data() { Dt1 = "DtD", Dt2 = string.Empty, Dt3 = "DtX", Dt5 = string.Empty },
    new Data() { Dt1 = "DtE", Dt3 = "-" }
};

我有一个“无效”字符串列表,如下所示。

var invalid = new List<string>() { string.Empty, "-", null };

现在,我想从上面的列表中识别出至少包含一个 valid 字符串的属性名称,然后用这些属性名称创建一个List&lt;string&gt;。如果我们考虑上面的样本数据,你可以看到,

  • 所有Dt1 值均有效。
  • 所有Dt2 值均无效。
  • Dt3 至少有一个有效值。
  • Dt4 从未被赋值,因此所有值都无效。
  • Dt5 至少有一个有效值。

所以,我的结果列表应该是

Dt1、Dt3、Dt5

我的方法是编写一个函数来识别字符串中是否至少有一个有效值,然后使用它检查列表的每个属性。

public static bool IsDataValid(List<string> data, List<string> invalid)
{
    foreach (var item in data)
    {
        if (!invalid.Contains(item))
        {
            return true;
        }
    }
    return false;
}

那么,

var invalid = new List<string>() { string.Empty, "-", null };
var result = new List<string>();
if (IsDataValid(list.Select(x => x.Dt1).ToList(), invalid))
{
    result.Add("Dt1");
}
if (IsDataValid(list.Select(x => x.Dt2).ToList(), invalid))
{
    result.Add("Dt2");
}
if (IsDataValid(list.Select(x => x.Dt3).ToList(), invalid))
{
    result.Add("Dt3");
}
if (IsDataValid(list.Select(x => x.Dt4).ToList(), invalid))
{
    result.Add("Dt4");
}
if (IsDataValid(list.Select(x => x.Dt5).ToList(), invalid))
{
    result.Add("Dt5");
}

这确实有效,但对我来说有点“难看”。另外,我实际的 Data 类有 20 多个属性,所以我必须使用 20 个 if 语句,这又是糟糕的设计。

我想知道是否还有其他方法,尤其是我不必“硬编码” if 语句的方法。像下面这样我可以迭代类的属性并找出列表中哪些是有效的。

foreach (var prop in typeof(Data).GetProperties())
{
    // How do I do a `.Select()` here?
}

【问题讨论】:

  • 不完全。我知道如何获取一个类的属性列表。我的问题是,由于获得的属性名称将是string,我不能做类似list.Select(x =&gt; x.(property name) 的事情,因为这显然行不通。
  • 不是 100% 确定你将如何实现它,但这看起来很像在 MVC 中广泛使用的 IValidatable / 带有数据注释。也许您可以在这里使用IValidatable,或者考虑创建它的自定义版本。当您进行验证检查时,您必须有验证规则,然后是 yield return
  • 那么你想要这样的东西吗? dotnetfiddle.net/TCx5HN 我很快就搞定了,通过手动循环替换 LINQ 查询可能会提高效率...
  • @dbc 这看起来很有希望。让我仔细看看。
  • @Sach - 我通过用一个简单的循环替换 LINQ 查询 + Distinct() 加快了速度。

标签: c# linq select properties


【解决方案1】:

您可以使用Type.GetProperties() 获取您类型的所有属性,然后使用PropertyInfo.NamePropertyInfo.PropertyTypePropertyInfo.GetValue() 获取您集合中每个Data 项目的所有字符串属性的名称和值。这样您就可以收集所有属性的名称,其中至少包含一个有效值,如下所示:

public static class ValidationExtensions
{
    public static ICollection<string> ValidProperties<TObject, TValue>(this IEnumerable<TObject> items, Predicate<TValue> isValid)
    {
        var properties = typeof(TObject).GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Where(p => typeof(TValue).IsAssignableFrom(p.PropertyType))
            .Where(p => p.CanRead && p.GetIndexParameters().Length == 0 && p.GetGetMethod() != null)
            .ToList();

        var set = new HashSet<string>();

        foreach (var i in items) // Only iterate through the items once.
            foreach (var p in properties)
            {
                if (set.Contains(p.Name)) // Do not call GetValue() if not necessary, it's expensive.
                    continue;
                if (isValid((TValue)p.GetValue(i)))
                    set.Add(p.Name);
            }

        // Return properties in order
        return properties.Select(p => p.Name).Where(n => set.Contains(n)).ToList();
    }
}

注意事项:

演示小提琴here.

【讨论】:

  • 谢谢,这看起来很完美!
猜你喜欢
  • 2014-10-17
  • 1970-01-01
  • 2018-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多