Reflecting generic collections of derived types

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • SimonDotException
    New Member
    • Jul 2009
    • 5

    Reflecting generic collections of derived types

    I am trying to use reflection in a property of a base type to inspect the properties of an instance of a type which is derived from that base type, when the properties can themselves be instances of types derived from that base type, or arrays or generic collections of instances of types derived from that base type.

    All is well until I come to the properties which are generic collections, I don't seem to be able to find an elegant way of inspecting these.

    Here is some code to illustrate what I'm trying to do. The base type, and therefore the derived types, have an ErrorState property, this is an enumeration of things which could be wrong with instances of those types. There is also a ConsolidatedSta te property, this is the one which uses reflection to return a combination of all the things which may be wrong with the current instance and all of its properties.

    Code:
    [Flags]
    public enum ErrorState
    {
    	Ok = 1,
    	ConnectionError = 2,
    	HoustonWeHaveAProblem = 4
    	// etc...
    }
    
    public abstract class MyBase
    {
    	private ErrorState _errorState;
    	
    	public ErrorState ErrorState
    	{
    		get { return _errorState; }
    		set { _errorState = value; }
    	}
    
    	public ErrorState ConsolidatedState
    	{
    		get
    		{
    			ErrorState state = this.ErrorState;
    			PropertyInfo[] properties = this.GetType().GetProperties();
    			foreach( PropertyInfo property in properties )
    			{
    				// We don't want to inspect the ConsolidatedState property
    				// (this property) else we get stuck in a loop, causing a
    				// StackOverflowException.
    				if( property.Name == "ConsolidatedState" )
    				{
    					continue;
    				}
    				
    				// Is this property an array?
    				if( property.PropertyType.IsArray )
    				{
    					// Is this property an array of objects derived from 
    					// MyBase?
    					MyBase[] componentArray 
    						= property.GetValue( this, null ) as MyBase[];
    					if( componentArray != null )
    					{
    						// Yes, so inspect their ConsolidatedState properties.
    						foreach( MyBase c in componentArray )
    						{
    							state |= c.ConsolidatedState;
    						}
    					}
    					continue;
    				}
    
    				// Is this property an indexer?
    				if( property.GetIndexParameters().Length > 0 )
    				{
    					// it's probably an indexer, so ignore it
    					continue;
    				}
    				
    				// Is this property of a type derived from MyBase?
    				if( property.PropertyType.IsSubclassOf( typeof( MyBase ) ) )
    				{
    					// Yes, so it also has a ConsolidatedState property
    					MyBase component 
    						= property.GetValue( this, null ) as MyBase;
    					if( component != null )
    					{
    						state |= component.ConsolidatedState;
    					}
    					continue;
    				}
    				
    				// Is this property a generic type?
    				if( property.PropertyType.IsGenericType )
    				{
    					// Is this property a generic collection of a type
    					// derived from MyBase?
    					//Collection<MyBase> componentCollection
    					//	= property.GetValue( this, null ) 
    					//	as Collection<MyBase>; // returns null
    					
    					// Is this property a generic collection of MyDerived1?
    					IEnumerable<MyDerived1> componentCollection 
    						= property.GetValue( this, null )
    						as IEnumerable<MyDerived1>;
    					if( componentCollection != null )
    					{
    						// Yes, so inspect their ConsolidatedState properties.
    						foreach( MyBase c in componentCollection )
    						{
    							state |= c.ConsolidatedState;
    						}
    					}
    					continue;
    				}
    
    			}
    			return state;
    		}
    	}
    }
    
    public class MyDerived1 : MyBase
    {
    	// ...
    }
    
    public class MyDerived2 : MyBase
    {
    	public MyDerived1 Property1
    	{
    		get { return new MyDerived1(); }
    	}
    	
    	public MyDerived1[] Property2
    	{
    		get { return new MyDerived1[4]; }
    	}
    	
    	public Collection<MyDerived1> Property3
    	{
    		get { return new Collection<MyDerived1>(); }
    	}
    }
    The problem is with this line, which I've commented out in the ConsolidatedSta te property of MyBase:

    // Is this property a generic collection of a type
    // derived from MyBase?
    Collection<MyBa se> componentCollec tion
    = property.GetVal ue( this, null )
    as Collection<MyBa se>; // returns null

    Having determined that the property being inspected is a generic collection, I want to know whether the items in the collection are of a type which is derived from MyBase and therefore has an ErrorState.

    Having read Casting generic collections & inheritance in the archive forum, I understand now why the cast above doesn't work. As one of the contributors to that thread succinctly puts it, a List of (Farmers) is also a List of (Persons), but a (List of Farmers) is not a (List of Persons), and I'm trying to cast a (List of Farmers) to a (List of Persons).

    What I want to do is attempt to cast something which I know is a generic collection, into any sort of array / list / collection in order that I can determine whether its items are derived from MyBase and therefore whether they have a ConsolidatedSta te property.

    As you can see, I came up with a solution, but it's not very elegant because it depends on MyBase knowing that one or more of its derived types has a property which is a generic collection of instances of another specific type derived from MyBase. If someone later writes another type derived from MyBase, with a property which is a generic collection of instances of a type other than MyDerived1, then its ConsolidatedSta te property won't be able to inspect that collection unless MyBase is also changed to cater for properties of that type.

    I feel sure that there must be a more elegant solution than this, one in which the author of MyBase doesn't need to know any of the implementation details of any of the types derived from MyBase. Can anyone suggest one?

    Thank you and apologies for a long first post.

    Simon.
  • icesign
    New Member
    • Aug 2009
    • 5

    #2
    Just check if the property is IEnumerable (non-generic version) and then cast objects to whatever you want. That way you will cover arrays too because arrays are IEnumerable.

    Comment

    • SimonDotException
      New Member
      • Jul 2009
      • 5

      #3
      Originally posted by icesign
      Just check if the property is IEnumerable (non-generic version) and then cast objects to whatever you want.
      Thank you!

      I changed the lines within the ConsolidatedSta te property

      Code:
      // Is this property a generic collection of MyDerived1?
      IEnumerable<MyDerived1> componentCollection 
          = property.GetValue( this, null )
          as IEnumerable<MyDerived1>;
      if( componentCollection != null )
      {
          // Yes, so inspect their ConsolidatedState properties.
          foreach( MyBase c in componentCollection )
          {
              state |= c.ConsolidatedState;
          }
      }
      to

      Code:
      // Is this property IEnumerable?
      IEnumerable objectCollection 
          = property.GetValue( this, null ) as IEnumerable;
      if( objectCollection != null )
      {
          // Yes, it's IEnumerable, so iterate through its items
          foreach( object o in objectCollection )
          {
              // Is this item of a type derived from MyBase?
              MyBase c = o as MyBase;
              if( c != null )
              {
                  // Yes, so get its ConsolidatedState property
      	    state |= c.ConsolidatedState;
              }
          }
      }
      and it all seems to work fine now.

      Thank you Icesign :-)

      Comment

      Working...