仅当它实际上是一个数组时,如何使用反射检测数组 属性 的长度?
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
而不是数组。这包括数组以及 ArrayList
和 List<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 });
}
}
}
这应该可以通过提供正确的类型来促进映射,同时将大部分丑陋的代码保留在自己的框中。
我正在像这样基于反射转换对象。
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
而不是数组。这包括数组以及 ArrayList
和 List<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 });
}
}
}
这应该可以通过提供正确的类型来促进映射,同时将大部分丑陋的代码保留在自己的框中。