- Home /
How do you access the elements of a List at runtime using reflection?
I'm writing a sort of generic serialization system that utilizes the C# Reflection class and have run into an issue.
My design pattern is this: Take a MonoBehaviour -> Use Reflection to get fieldInfo -> break down any "complex" fields (Vector3, custom structs) into simpler forms (float/int/string) -> Use Binary Formatter to Save to Disk
This works great for pretty much everything except Lists. The problem I'm having is that I don't really understand how to access the variables in a reflected List where I may not necessarily know the type.
What I'd like to do is a be able to take a List and iterate through each member using reflection. I've searched online, but everyone else seems to already know what type of data they're reflecting beforehand, whereas I don't necessarily know what type of list I'm going to grab with Reflection. It could be a
List<float> or a List<GameObject> or a List<BananCreamPie>
So what I THINK I need to do is take the List which is stored in FieldInfo field. Then I need to get fieldInfo[] representing each member of the List. How do I accomplish this? Is this the correct method? Is there a better way of saving lists at runtime?
ADDED NOTE: I am writing my own generic serializer because I have a complex data set which needs to be saved and have run into a few bugs with WhyDoIDoIt's serializer.
public class SomeClass
{
List<SomeType> list = new List<SomeType>(){bunch of stuff}
}
public static class Serializer()
{
public static void Serialize(MonoBehaviour passedScript)
{//Take in a MonoBehaviour then grab all the fields off of it
FieldInfo[] fields = passedScript.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach(FieldInfo field in fields)
{
if(field.FieldType.IsGenericType)
{//If this is a list, get an instance of it from the passedScript
//Then, get information about each individual member of the list
//??????
//Profit
}
}
}
}
Have you checked that one?
http://stackoverflow.com/questions/1043755/c-sharp-generic-list-t-how-to-get-the-type-of-t
Yes, I've been all over trying to figure out what I'm doing wrong. I've come very close to solving my problem, but I haven't really gained the insight on Reflection that I'd hoped for.
Answer by BadAssGames · Apr 01, 2015 at 12:56 PM
Thanks everyone for your help!
I don't know if this is the best way of doing things, but this is the solution I arrived at. It has a few wonderful strengths in that it can serialize/deserialize lists of any kind in a generic manner. Below are the functions I'm using to do this. The SerializeList function takes in a Generic List of any type. This is then sent off to be processed using SerializeField()(NOT SHOWN).
DeserializeList works in the opposite way. It takes in a FieldInfo representing the list that it's supposed to be deserializing information for. These classes are then constructed in the DeserializeField()(NOT SHOWN).
At the end, the member is added to the list and the deserializedList is reassigned to the MonoBehaviour.
Hopefully someone else can find some use in this. I learned a huge amount about Serialization/Deserialization and Reflection with this exercise!
private static void SerializeList(FieldInfo field)
{
Debug.Log("Is a List");
ICollection list = field.GetValue(focusedScript) as ICollection;
serializedScript.ints.Add(list.Count);//Store the length of this list for later access
foreach(var c in list)
{//For every member of the list, get all the info from it
FieldInfo[] subInfo = c.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach(FieldInfo sub in subInfo)
{//Then send every fieldInfo off to be processed
target = c;//Set our collection to be the current target
SerializeField(sub);//Send the field off to be serialized
target = focusedScript;//When we get back here, set the target to be the focusedScript
}
}
}
private static void DeserializeList(FieldInfo field)
{
int listLength = serializedScript.GetInt();//Get the length of this list
System.Type type = field.FieldType.GetGenericArguments()[0];//Get the type of field
Debug.Log("Deserializing a List of type " + type);
var instancedList = (IList)typeof(List<>)//Create a Generic List that can hold our type
.MakeGenericType(type)
.GetConstructor(System.Type.EmptyTypes)
.Invoke(null);
for (int i = 0; i < listLength; i++ )
{//Then, create listLength instances of our deserialized class/struct
FieldInfo[] subInfo = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
var member = System.Activator.CreateInstance(type);//Create a new member which will be added to our instancedList
foreach (FieldInfo sub in subInfo)
{//Then
target = member;
DeserializeField(sub);
target = focusedScript;
}
instancedList.Add(member);
}
field.SetValue(target, instancedList);//Once we get here, assign our deserialized list to our target script
}
Answer by Bunny83 · Apr 01, 2015 at 12:32 PM
Generic Lists is nothing special. They are just like other custom classes. A List uses an array internally to store the items. This array field is of course private, but with reflection you can easily access it. Keep in mind that the length of the internal array is not necessarily the item count in the list. If you serialize the List you should store the array as well as it's count value.
The Lists internal state consists of 3 things:
private T[] _items;
private int _size;
private int _version;
Keep in mind that the naming of those internal fields or implementation can change at any time, so if you want a general purpose serializer, you might simply serialize all private fields along with the public fields. (Hopefully you track down circular references ^^).
_items is simply a native array of the generic type which acts as "buffer" for the list. The List's Capacity property simply returns _items.Length which the Count property returns _size. So only "_size" elements of the _items array are actually used.
The _version field is simply increased whenever the List is changed (Adding, Removing, Clearing, ...). This is used internally by the implemented IEnumerator to detect changes while iterating over it. It's value is quite unimportant when it comes to serialization.
The problems what general purpose serializers face is that you also have to store the type itself so you can recreate it on deserialization. Generic types require that you analyse the type and get the parameter information for each generic parameter.
Answer by Pangamini · Apr 01, 2015 at 08:51 AM
Objects in the list are indeed no part of the class definition, thus you can't find it with the reflection. But you can find the getter function or property that will return the item once called (on a list instance of course). So use the reflection to get Count and Item properties.
Once you invoke the Item getter and get the object from the list, you can do GetType().GetFields() on this object and continue serializing the value from the list.
What i see as a problem in our code that you don't actually know if the object you try to serialize is a list. All you do is that you check if the type is generic. That can be anything. I wrote a custom serialization too. Containers are tricky to serialize, because you don't see all their content as class fields. I got inspired by the .net serialization framework and created a system of surrogates. Basically you register an object that will represent other objects of a certain type (or its base type, depends on what level of automation you want). Then, when serializing an object, you first check if there's a surrogate registered for its type. If there's none, you continue serialization by reading all public fields (or all fields with(out) some attribute, it's on you). If there's a surrogate for the type (eg. List, or, if you check in class hierarchy, IList interface for generic approach), you take the object and pass it to the surrogate instead. This surrogate's overriden methods will know that your object is a list and will know how to read it and generate serialization data. It doesn't even need to use reflection on the list itself, because it will have a reference to the actual object. It will simply iterate over all its items and invoke a serialization for each one of them, recursively repeating the process of surrogate search for each one of them.
A very quick and rich description i guess, maybe not clear enough to understand at first, so ask away
Hey Panga$$anonymous$$i thanks for the reply. The above code is a very small part of a much larger serialization system I've been setting up. In a lot of cases I'm using surrogates just as you did (my system refers to them as DataSets), but their basic purpose is to know how to properly store a complex script/data structure.
Objects in the list are indeed no part of the class definition, thus you can't find it with the reflection. But you can find the getter function or property that will return the item once called (on a list instance of course). So use the reflection to get Count and Item properties.
Once you invoke the Item getter and get the object from the list, you can do GetType().GetFields() on this object and continue serializing the value from the list.
This is exactly where I'm getting stuck.Could you provide a more direct code example?
$$anonymous$$y code snippet can be seen here:
private static void SerializeList(FieldInfo field)//The passed field will always represent a List
{
ICollection list = field.GetValue(focusedScript) as ICollection;
foreach(var c in list)
{//For every member of the list, get all the info from it
FieldInfo[] subInfo = c.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach(FieldInfo sub in subInfo)
{//Then send every fieldInfo off to be processed
SerializeField(sub);//Send the field off to be serialized
}
}
}
However, I don't think this is exactly what you meant. And the method I wrote has a few issues. For one, When I send the subField off to be serialized, I lose the reference to the ICollection and the "c" variable. I have no way of knowing what type "c" will be, and therefore can't store a reference to it anywhere. I'm fairly certain I'm being obtuse about this.
ETA: Also, I was hoping I could avoid having to write surrogates for EVERY type of data structure (even small structs with 2 or 3 variables). Thus, my serialization system could be used in future projects with $$anonymous$$imal overhead.
Here's a quick little flowchart I made to show the flow of my serialization (deserialization will obviously flow in reverse order).
I think the major problem with my design pattern is that, at any given moment I will not know what Type I am getting the value of. It's unclear to me how I can store a reference to a List whose type I don't know ahead of time.
Let's do it like this:
SerializeObject(obj):
surrogate = GetSurrogateForType(obj.GetType())
if surrogate:
surrogate.Serialize(obj)
else:
SerializeByReflection(obj)
ListSurrogate.Serialize(listObj as obj):
for item in listObj:
Serializeobject(item)
You will not use reflection and field anywhere but in the SerializeByReflection method, which is your fallback for most cases where there are no surrogates (no need for it, only for special cases)
SerializeByReflection will just read all fields (like you do) and just call SerializeObject on its values. $$anonymous$$eep the code clear, do the reflection in one place. Everything nicely recursive
But doesn't this require me to write a new surrogate for every class/struct (Vector3, BananSplitRecipes, ApartmentNumbers) that I want to serialize? I'm trying to take a more generic approach.