遍历带索引属性(反射)

14

我想要迭代一个只能通过反射访问到的索引属性,但是(我知道可能有非常简单的答案,但是MSDN/Google失败了=/)除了使用PropertyInfo.GetValue(prop, counter)并增加计数器,直到抛出TargetInvocationException之外,我找不到/想不到其他方法。

例如:

foreach ( PropertyInfo prop in obj.GetType().GetProperties() )
{
    if ( prop.GetIndexParameters().Length > 0 )
    {
        // get an integer count value, by incrementing a counter until the exception is thrown
        int count = 0;
        while ( true )
        {
            try
            {
                prop.GetValue( obj, new object[] { count } );
                count++;
            }
            catch ( TargetInvocationException ) { break; }
        }

        for ( int i = 0; i < count; i++ )
        {
            // process the items value
            process( prop.GetValue( obj, new object[] { i } ) );
        }
    }
}

现在,这种解决方法存在一些问题...非常丑陋...

如果它是多维的或者例如没有按整数进行索引怎么办...

这是我正在使用的测试代码,如果有人需要的话。 如果有人感兴趣,我正在制作一个自定义缓存系统,而.Equals并不能满足我的需求。

    static void Main()
    {
        object str = new String( ( "Hello, World" ).ToArray() );

        process( str );

        Console.ReadKey();
    }

    static void process( object obj )
    {
        Type type = obj.GetType();

        PropertyInfo[] properties = type.GetProperties();

        // if this obj has sub properties, apply this process to those rather than this.
        if ( properties.Length > 0 )
        {
            foreach ( PropertyInfo prop in properties )
            {
                // if it's an indexed type, run for each
                if ( prop.GetIndexParameters().Length > 0 )
                {
                    // get an integer count value
                    // issues, what if it's not an integer index (Dictionary?), what if it's multi-dimensional?
                    // just need to be able to iterate through each value in the indexed property
                    int count = 0;
                    while ( true )
                    {
                        try
                        {
                            prop.GetValue( obj, new object[] { count } );
                            count++;
                        }
                        catch ( TargetInvocationException ) { break; }
                    }

                    for ( int i = 0; i < count; i++ )
                    {
                        process( prop.GetValue( obj, new object[] { i } ) );
                    }
                }
                else
                {
                    // is normal type so.
                    process( prop.GetValue( obj, null ) );
                }
            }
        }
        else
        {
            // process to be applied to each property
            Console.WriteLine( "Property Value: {0}", obj.ToString() );
        }
    }

object str = new String(("Hello, World").ToArray()) 的目的是什么? - Cheng Chen
只是一个传递给我的函数的变量示例……我正在尝试不同的定义字符串/String的方法,并留在了一个有点棘手的位置上… object str ="Hello, World!";同样可以正常工作。 - Dead.Rabit
2
如果我的键是字符串而不是整数,我该怎么办?我不知道它们的名称。如何找到它们并使用它们? - Alexander
6个回答

10

索引器的 getter 就像普通方法一样,只是使用方括号而不是圆括号。您不应该期望能够自动确定方法的可接受值范围,因此对于索引器也是不可行的。


1
一个解决方法是使用反射来查找名为“Length”或“Count”的成员属性,并将其用作整数索引属性的上限,或者对于字符串索引属性,使用名为“Keys”的属性。 - Dai

5

在索引属性中使用顺序索引号是不可靠的。
索引属性并不是数组。
以下是反例:

Dictionary<int, bool> dictionary = new Dictionary<int, bool>();
dictionary[1] = true;
dictionary[5] = false;

根据您的类型,通常有其他方法来获取可能的索引值,在这种情况下使用 dictionary.Keys。如果您的类型允许,我会按照以下顺序尝试:
  1. 为类型本身实现IEnumerable<T>
  2. 如果您有多个索引属性,则可以为每个索引属性实现相应的 IEnumerable<T> 属性。
如果您没有有效值的规范和询问有效值的方法,那么您就很难办了。

不幸的是,在运行时我对输入类型一无所知(它实际上是作为对象扩展方法实现的),但你给了我一个搜索IEnumerable实现的想法。谢谢。 - Dead.Rabit

4

索引器将被编译为方法。以下是一个例子:

class IndexedData
{ 
    public double this[int index]
    {
        get { return (double)index; }
    }
}

它将被编译为类似于这样的内容:

public double get_Item(int index)
{
    return (double)index;
}

以下代码无法编译,因为该类中有两个double get_Item(int)方法。索引器是编译器的魔法。
class IndexedData
{ 
    public double this[int index]
    {
        get { return (double)index; }
    }

    public double get_Item(int index)
    {
        return 1d;
    }
}

相比遍历索引值,字符串已经是一个类型为char的索引参数...所以我想要做的是: foreach(char c in str) Console.write(c);,只不过是用反射实现的... - Dead.Rabit
@Dead.Rabit:看看我的编辑。我认为它会帮助你理解索引器。 - Cheng Chen

3
你可以使用PropertyInfo.GetIndexParameters来查找索引属性参数的数量和类型。
我认为,除非你“作弊”并使用你可能拥有的关于该属性的内部信息,否则你无法找到这些参数的“合法”值。

3

成功进行了改进,但同时发现这个测试代码在处理自引用(例如Array.Syncroot)时会遇到无限循环问题。

简而言之,它现在可以找到从IEnumerable继承的东西(大多数索引化的东西),并在这些东西上使用foreach循环。再结合现有(丑陋的)代码适用于字符串的知识,它比以前更加彻底了...

我很高兴,但也很失望,因为似乎没有一个好的答案。

感谢大家的帮助。


如果有人发现自己处于类似的境地,请使用更新的测试代码。

    static void process( object obj )
    {
        Type type = obj.GetType();

        PropertyInfo[] properties = type.GetProperties();

        // if this obj has sub properties, apply this process to those rather than this.
        if ( properties.Length > 0 )
        {
            foreach ( PropertyInfo prop in obj.GetType().GetProperties() )
            {
                    if ( prop.PropertyType.FindInterfaces( ( t, c ) => t == typeof( IEnumerable ), null ).Length > 0 )
                    {
                        MethodInfo accessor = prop.GetGetMethod();
                        MethodInfo[] accessors = prop.GetAccessors();

                        foreach ( object item in (IEnumerable)obj )
                        {
                            process( item );
                        }
                    }
                    else if ( prop.GetIndexParameters().Length > 0 )
                    {
                        // get an integer count value, by incrementing a counter until the exception is thrown
                        int count = 0;
                        while ( true )
                    {
                        try
                        {
                            prop.GetValue( obj, new object[] { count } );
                            count++;
                        }
                        catch ( TargetInvocationException ) { break; }
                    }

                    for ( int i = 0; i < count; i++ )
                    {
                        // process the items value
                        process( prop.GetValue( obj, new object[] { i } ) );
                    }
                }
                else
                {
                    // is normal type so.
                    process( prop.GetValue( obj, null ) );
                }
            }
        }
        else
        {
            // process to be applied to each property
            Console.WriteLine( "Property Value: {0}", obj.ToString() );
        }
    }

0

以上的代码以及与此问题相关的代码对我遇到的问题非常有帮助。我正在发布我的代码,希望这个可以对大家也有用。

public ActionResult Survey(SurveyCollection surveyCollection) { if (surveyCollection != null) { Answer_DropDownCordinateOptionList traceObject = new Answer_DropDownCordinateOptionList(); IList traceObjectCollection = new List(); traceObjectCollection = ExtractNestedObjects(surveyCollection, traceObject, traceObjectCollection); }

    return View(surveyCollection);
}

private static IList<T> ExtractNestedObjects<T>(object baseObject, T findObject, IList<T> resultCollection)
{
    if (baseObject != null && findObject != null)
    {
        Type typeDestination = findObject.GetType();

        Type typeSource = baseObject.GetType();
        PropertyInfo[] propertyInfoCollection = typeSource.GetProperties();
        foreach (PropertyInfo propertyInfo in propertyInfoCollection)
        {
            if (propertyInfo.PropertyType.FindInterfaces((t, c) => t == typeof(IEnumerable), null).Length > 0)
            {
                if(propertyInfo.GetValue(baseObject, null) != null)
                {
                    if(propertyInfo.GetValue(baseObject, null).GetType().IsPrimitive)
                    {
                        ExtractNestedObjects<T>(propertyInfo.GetValue(baseObject, null), findObject, resultCollection);
                    }
                    else if (propertyInfo.GetValue(baseObject, null).GetType().IsGenericType)
                    {
                        foreach (var item in (IList)propertyInfo.GetValue(baseObject, null))
                        {
                            ExtractNestedObjects<T>(item, findObject, resultCollection);
                        }
                    }
                }
            }
            else
            {
                if (propertyInfo.Name == typeDestination.Name)
                {
                    if (propertyInfo.GetValue(baseObject, null) != null)
                    {
                        resultCollection.Add((T)propertyInfo.GetValue(baseObject, null));
                    }
                }

                ExtractNestedObjects<T>(propertyInfo.GetValue(baseObject, null), findObject, resultCollection);
            }
        }
    }
    return resultCollection;
}


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