通过反射访问集合

32

有没有一种方法可以使用反射迭代(最好使用foreach)集合?我正在使用反射迭代对象中的属性,当程序到达一个类型是集合时,我希望它能够迭代集合内容并能够访问集合中的对象。

目前,我在所有属性上设置了一个属性,对于那些是集合的属性,将IsCollection标志设置为true。我的代码检查此标志,如果为true,则使用反射获取类型。是否有一种方式可以调用集合的GetEnumerator或Items以便能够迭代项目?

9个回答

38

我曾经遇到过这个问题,但是最终我并没有使用反射,而是检查它是否为IEnumerable类型。所有集合都实现了该接口。

if (item is IEnumerable)
{
    foreach (object o in (item as IEnumerable))
    {

    }
} else {
   // reflect over item
}

2
不需要 "the",你已经用 "is" 检查过了,所以一个简单的转换就可以了。 - ShuggyCoUk
1
使用 as 进行类型转换比 (IEnumerable)item 更快。 - Darren Kopp
4
可能更有效的做法是:var enumItem = item as IEnumerable; if (enumItem != null) // 使用 foreach 循环注:该代码片段是在判断一个变量是否可枚举后,如果可枚举则使用 foreach 循环处理。 - Bob Wintemberg

31
我尝试了类似于Darren建议的技术,但是请注意,并不仅仅是集合实现IEnumerable。例如,string也是IEnumerable,并将迭代字符。
这是我正在使用的小函数,用于确定对象是否为集合(这也将可枚举,因为ICollection也是IEnumerable)。
    public bool isCollection(object o)
    {
        return typeof(ICollection).IsAssignableFrom(o.GetType())
            || typeof(ICollection<>).IsAssignableFrom(o.GetType());
    }

8

只需获取属性的值,然后将其强制转换为IEnumerable。以下是一些(未经测试的)代码,可供参考:

ClassWithListProperty obj = new ClassWithListProperty();
obj.List.Add(1);
obj.List.Add(2);
obj.List.Add(3);

Type type = obj.GetType();
PropertyInfo listProperty = type.GetProperty("List", BindingFlags.Public);
IEnumerable listObject = (IEnumerable) listProperty.GetValue(obj, null);

foreach (int i in listObject)
    Console.Write(i); // should print out 123

3

仅供参考,可能会对某些人有所帮助... 我有一个包含嵌套类和其他类集合的类。我想要保存类及其嵌套类和类集合的属性值。我的代码如下:

 public void LogObject(object obj, int indent)
    {
        if (obj == null) return;
        string indentString = new string(' ', indent);
        Type objType = obj.GetType();
        PropertyInfo[] properties = objType.GetProperties();

        foreach (PropertyInfo property in properties)
        {
            Type tColl = typeof(ICollection<>);
            Type t = property.PropertyType;
            string name = property.Name;


            object propValue = property.GetValue(obj, null); 
            //check for nested classes as properties
            if (property.PropertyType.Assembly == objType.Assembly)
            {
                string _result = string.Format("{0}{1}:", indentString, property.Name);
                log.Info(_result);
                LogObject(propValue, indent + 2);
            }
            else
            {
                string _result = string.Format("{0}{1}: {2}", indentString, property.Name, propValue);
                log.Info(_result);
            }

            //check for collection
            if (t.IsGenericType && tColl.IsAssignableFrom(t.GetGenericTypeDefinition()) ||
                t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == tColl))
            {
                //var get = property.GetGetMethod();
                IEnumerable listObject = (IEnumerable)property.GetValue(obj, null);
                if (listObject != null)
                {
                    foreach (object o in listObject)
                    {
                        LogObject(o, indent + 2);
                    }
                }
            }
        }
    }

一个函数被称为:
LogObject(obj, 0);

然而,我在我的类中有一些结构体,需要找出如何获取它们的值。此外,我有一些列表(List),也需要获取它们的值。如果我更新了我的代码,我会发布。


2

您最好的选择可能是检查对象是否实现了某些集合接口 - 可能只需要 IEnumerable 接口。然后只需从对象调用 GetEnumerator() 方法,使用 IEnumerator.MoveNext() 和 IEnumerator.Current 来遍历集合。

如果集合没有实现这些接口,则这种方法将无法帮助您,但我想如果这样的话,它也不算真正意义上的集合。


0

当你使用反射时,你不一定要使用该对象的实例。你需要创建该类型的一个实例才能迭代对象的属性。因此,如果你正在使用反射,请使用ConstructorInfo.Invoke() (?)方法来创建一个新实例或指向该类型的实例。


0
一个相对简单的方法是将对象强制转换为集合,然后直接使用它。

0
我会看一下Type.FindInterfaces方法。这可以过滤掉给定类型实现的接口。例如PropertyInfo.PropertyType.FindInterfaces(filterMethod, filterObjects)。您可以通过IEnumerable进行过滤,并查看是否返回任何结果。MSDN在方法文档中有一个很好的示例。

0
如果你不是使用一个对象的实例而是一个类型,你可以使用以下方法:
// type is IEnumerable
if (type.GetInterface("IEnumerable") != null)
{
}

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