仅当它实际上是一个数组时,如何使用反射检测数组 属性 的长度?

How to detect the length of array property using reflection only if it actually is an array?

我正在像这样基于反射转换对象。

obj.GetType().GetProperties()

它生成预期的属性列表。

{System.Reflection.PropertyInfo[4]}
[0]: {System.String Name}
[1]: {System.Guid[] Ids}
[2]: {System.String[] Tags}
[3]: {System.Nullable`1[System.Boolean] Status}

然后,我想清除未使用的属性列表,所以我添加了一个条件,即 属性 的值需要不同于 null

obj.GetType().GetProperties()
  .Where(a => a.GetValue(obj) != null)

它为 atomary 字段而不是数组生成预期结果(因为它们不确定地不是 null,只是空的)。

{System.Linq.Enumerable.WhereArrayIterator<System.Reflection.PropertyInfo>}
[0]: {System.String Name}
[1]: {System.Guid[] Ids}
[2]: {System.String[] Tags}

我不确定我应该如何检测非空但为空的数组属性以排除这些属性。我不能简单地转换为数组(如讨论的 here),因为并非每个 属性 都是数组。

我最接近的方法是这件“作品”,它似乎相当……不受欢迎。它有这种独特的代码味道。

obj.GetType().GetProperties()
  .Where(a => a.GetValue(obj) != null 
    && !(a.GetValue(obj) is Array && (a.GetValue(obj) as Array).Length == 0))

当我尝试使用 Select(a=>a.GetValue(obj)) 用它创建一些东西时,它变得更加笨拙,并且更明显地迫切需要改进。我还注意到,只要数组不为空,我的映射就无法生成 System.String%5b%5d,这可能需要我额外使代码变得笨拙。

我想不出一个与你的有很大不同的解决方案,从根本上说,它感觉像是一种自定义逻辑来确定 属性 值是否为“空”。

也许模式匹配的 switch 表达式可能会使代码更简洁,但是,例如:

var obj = new MyObject
{
    Name = "Test",
    Ids = new Guid[0],
    Tags = new[] { "t1" },
    Status = null
};

var values = obj.GetType().GetProperties()
    .Select(p => p.GetValue(obj))
    .Where(o => o switch {
        Array array => array.Length > 0,
        _ => o != null
    });

您可以使用模式匹配来简化条件。它允许您只调用 GetValue 一次

p => p.GetValue(obj) is not null and not IList { Count: 0 }

或稍短的版本

p => p.GetValue(obj) is not (null or IList { Count: 0 })

我测试了 IList 而不是数组。这包括数组以及 ArrayListList<T>。请注意,数组也通过显式实现的 IList.Count 属性 公开其 Length。 “显式实现”表示该成员是隐藏的或私有的,除非通过界面直接查看。

您甚至可以测试 ICollection 接口。这将包括更多 collections.

IList { Count: 0 } 是一个 属性 模式,它测试 object 是否是具有 Count 0 的 IList

在一起:

var result = obj.GetType().GetProperties()
  .Where(p => p.GetValue(obj) is not (null or IList { Count: 0 }));

请注意,这使用了 C# 9.0 中引入的 logical patterns

不幸的是,在使用这种盲目反射时,代码气味在很大程度上是不可避免的(据我所知),而且很少干净地凝结成一个紧密的块。

给定 string[] 输出,您的映射似乎对确切的 属性 类型很敏感,暗示它无法在没有帮助的情况下解包集合。

为了解决这两个问题,我建议使用一个冗长的可枚举助手,其中集合处理细节对于任何未来的维护者来说都是显而易见的。

static IEnumerable<(Type Type, IEnumerable Values)> GetPopulatedPropertyData(object obj)
{
    foreach (var prop in obj.GetType().GetProperties())
    {
        var value = prop.GetValue(obj);
        if (value is Array a)
        {
            if (a.Length == 0) { continue; }
            yield return (prop.PropertyType.GetElementType()!, a);
        }
        // Future collections added in reverse hierarchial order here
        else if (value is not null)
        {
            yield return (prop.PropertyType, new[] { value });
        }
    }
}

这应该可以通过提供正确的类型来促进映射,同时将大部分丑陋的代码保留在自己的框中。