简体   繁体   中英

iterating through an indexed property (Reflection)

I want to itterate over an indexed property that I only have access to via reflection,

but ( and I say this in the full knowledge that there's probably an embarrassingly simple answer, MSDN/Google fail =/ ) I cannot find/think of a way besides incrementing a counter over the PropertyInfo.GetValue(prop, counter) until the TargetInvocationException is thrown.

ala:

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 } ) );
        }
    }
}

now, there are some issues with this... very ugly.. solution..

what if it's multi-dimensional or not indexed by integers for example...

heres the test code I'm using to try and get it working if anyone needs it. If anyone's interested I'm making a custom caching system and .Equals doesn't cut it.

    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() );
        }
    }

The getter of an indexer is just like normal method, except it takes square brackets, not round ones. You wouldn't expect to be able to automatically determine the range of acceptable values for a method, so it's not feasible for an indexer.

Indexers will be compiled to methods. Here is an example:

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

It will be compiled to something like this:

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

The following code can't be compiled because there are two double get_Item(int) methods in the class. Indexer is a magic of the compiler.

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

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

Having sequential index numbers in an indexed property is nothing you can bet on.
Indexed properties are not arrays.
Counter example:

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

Depending on the type you usually have other methods to get the possible index values, in this case dictionary.Keys . If it possible with your types I would try in this order

  1. Implement IEnumerable<T> for the type itself.
  2. If you have several indexed properties you can implement a corresponding IEnumerable<T> property for each indexed property.

If you have no specification of valid values and no method of asking what the valid values are, then you are pretty much out of luck.

You can use PropertyInfo.GetIndexParameters to find out the number and type of indexed property parameters.

I don't think there is anything you can do about finding what "legal" values for these parameters are, unless you "cheat" and use inside information you may have on what that property is.

Managed to make an improvement, mind, also found out that this test code suffers from infinite loops for self references (Array.Syncroot for example)

In a nutshell it now finds things that inherit from IEnumerable (which is most indexed things) and use a foreach loop on those and coupled with the knowledge that the existing (ugly) code works for strings, it's now allot more thorough then it used to be...

glad but disapointed that there doesn't seem to be a nice answer.

Thanks for the help everyone


Updated test code if someone finds themselves in a similar position

    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() );
        }
    }

The above codes and the related ones to this question were really helpful for the problem that I was facing. I'm posting my code and I hope this one works for you guys too.

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; } 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM