views:

200

answers:

4

I am having a lot of trouble with Reflection in C# at the moment. The app I am writing allows the user to modify the attributes of certain objects using a config file. I want to be able to save the object model (users project) to XML. The function below is called in the middle of a foreach loop, looping through a list of objects that contain all the other objects in the project within them. The idea is, that it works recursively to translate the object model into XML.

Dont worry about the call to "Unreal" that just modifes the name of the objects slightly if they contain certain words.

      private void ReflectToXML(object anObject, XmlElement parentElement)
  {
     Type aType = anObject.GetType();
     XmlElement anXmlElement = m_xml.CreateElement(Unreal(aType.Name));
     parentElement.AppendChild(anXmlElement);
     PropertyInfo[] pinfos = aType.GetProperties();
     //loop through this objects public attributes
     foreach (PropertyInfo aInfo in pinfos)
     {
        //if the attribute is a list
        Type propertyType = aInfo.PropertyType;
        if ((propertyType.IsGenericType)&&(propertyType.GetGenericTypeDefinition() == typeof(List<>)))
        {
           List<object> listObjects = (aInfo.GetValue(anObject,null) as List<object>);
           foreach (object aListObject in listObjects)
           {
              ReflectToXML(aListObject, anXmlElement);
           }
        }
        //attribute is not a list
        else
           anXmlElement.SetAttribute(aInfo.Name, "");
     }
  }

If an object attributes are just strings then it should be writing them out as string attributes in the XML. If an objects attributes are lists, then it should recursively call "ReflectToXML" passing itself in as a parameter, thereby creating the nested structure I require that nicely reflect the object model in memory.

The problem I have is with the line

List<object> listObjects = (aInfo.GetValue(anObject,null) as List<object>);

The cast doesn't work and it just returns null. While debugging I changed the line to

object temp = aInfo.GetValue(anObject,null);

slapped a breakpoint on it to see what "GetValue" was returning. It returns a "Generic list of objects" Surely I should be able to cast that? The annoying thing is that temp becomes a generic list of objects but because i declared temp as a singular object, I can't loop through it because it has no Enumerator.

How can I loop through a list of objects when I only have it as a propertyInfo of a class?

I know at this point I will only be saving a list of empty strings out anyway, but thats fine. I would be happy to see the structure save out for now.

Thanks in advance

A: 

In C# 3, you can't cast Lists<T> to other types of Lists<T> even when casting to something like List<Object>. It's explicitly not allowed even when casting to List.

In 4.0 variance changes a little with interfaces with the addition of the in and out keywords.

Here is a link to Eric Lippert explaining how and why this is the case.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx

Kevin
C# 3.0. There is no C# 3.5.
Adam Robinson
Unfortunately I'm restricted to C# express 2008
DrLazer
@Kevin - the list interfaces don't have variance, as they are neither `in` nor `out`.
Marc Gravell
@Marc, yeah what I was trying to imply was that variance in general changes a little between 3.0 and 4.0 concerning interfaces, although the change is not necessarily applicable in this situation.
Kevin
+3  A: 

I'm assuming that the actual value is not a List<object> but is something like a List<string> or List<int> or some other type that isn't exactly object?

If so, then the reason that the cast is failing is because generic classes are neither co- nor contravariant.

In C# 4.0, however, you will be able to make the foreach loop work by casting to IEnumerable<object> because interfaces can be co/contravariant.

(Much) more information here: http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx


Edit:

Thinking about it, you don't need generic variance here. List<T> implements the non-generic IEnumerable. This is all you need for the foreach loop to operate, and you only need the elements as type object so just cast it to an IEnumerable instead of List<object> and everything should work fine.

Greg Beech
It will be a list of user customised objects. (Those are the only lists under the top classes) the reason I am using "object" is because it could be any one of a number of different lists containing different object types. If i could loop through that list and pass the objects back into the same function then it should figure out its name with "aType.Name" and write it out to XML.Unfortunately I'm restricted to C# express 2008
DrLazer
+1  A: 

Couldn't you just cast them to objects with OfType()?

List<object> listObjects = anObject.OfType<object>().ToList();
Nick Gotch
my propertyinfo class does not seem to have an OfType method. I am using c# express 2008
DrLazer
You need to reference System.Data.Linq.dll and add using System.Linq;
Nick Gotch
I hate to say it, but this is a hugely wasteful approach. Duplicating the list just to read it? Ouch.
Marc Gravell
+2  A: 

Generics and reflection don't mix well, especially for lists. I would strongly suggest using (non-generic) IList, or if that fails (some generic lists don't implement it), IEnumerable (since IEnumerable<T> : IEnumerable) and invoke the Add manually.

I feel your pain, really (I maintain an OSS serialization API).

Marc Gravell
Thanks man, that got me thinking about other ways of approaching the problem. I switched the lists to ArrayLists and all is well.
DrLazer
@DrLazer - To be honest you shouldn't even have needed that; since `List<>` and `ArrayList` both implement `IList`, talk to the `IList` interface and it doesn't *matter* what the concrete list is.
Marc Gravell
Oh well! It's all a done deal now. Thanks for the help
DrLazer