0

I have structs which can contain other structs and a class with two properties, one for the field name and one for the field value.

Example structs:

    public struct MyStruct
    {
        public string Name;
        public ushort Code;
        public Details Info;
    }

    public struct Details
    {
        public string Architecture;
        public string Characteristics;
        public uint Size;
    }

My class:

public class Item
{
    public string Name { get; set; }
    public object Value { get; set; }
    public Item(string name, object value)
    {
        Name = name;
        Value = value;
    }
}

Now I need a function which input parameter is a struct (that could contain other structs) and returns a List of Item. I have a fully working function for structs without other structs in it:

    private static List<Item> structToList<T>(T structure)
    {
        List<Item> items = new List<Item>();
        foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            items.Add(new Item(field.Name, field.GetValue(structure)));
        return items;
    }

I'm pretty sure this can be solved recursively. My first thought was to check if the field is a struct or a value. If it's a struct, call the function again, if it's a value add it to the list. Furthermore i have to declare the instance of my List outside the function, haven't I? Here is the pseudo code I came up with, so I was not able to check if a FieldInfo is a struct and give the function its proper Generic.

    List<Item> items = new List<Item>();
    private static List<Item> structToList<T>(T structure, List<Item> items)
    {

        foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
        {
            //if(isStructure(field)
            //      structToList<?>(field, items);
            //else
                    items.Add(new Item(field.Name, field.GetValue(structure)));
        }  
        return items;
    }

EDIT:

The distinction of cases works now. For the else clause I went with this answer, where the type is known at execution time, but i get null reference exception. Also field.GetType() doesn't give me the type of the struct.

    private List<Item> structToList<T>(T structure, uint offset)
    {

        foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
        {
            if (IsSimple(field.FieldType))
                itemList.Add(new Item(field.Name, field.GetValue(structure)));
            else
            {
                // doesn't work
                Type t = field.GetType();
                MethodInfo method = GetType().GetMethod("structToList")
                         .MakeGenericMethod(new Type[] { t });  // null reference exception
                method.Invoke(this, new object[] { field, 0 });
            }
        }
        return itemList;
    }

    private static bool IsSimple(Type type)
    {
        return type.IsPrimitive
          || type.IsEnum
          || type.Equals(typeof(string))
          || type.Equals(typeof(decimal));
    }

EDIT 2: I came up with two solutions which fit my needs.

Community
  • 1
  • 1
PCFX
  • 1
  • 3

2 Answers2

0

There are to areas you need to address (as you mentioned):

  1. The first, you need to detect when the type of the field is a struct or a "simple" value. To do that, I suggest you read the answer to this question.

  2. Now, to do it recursively, I would drop the generic parameter and just do typof() on whatever struct is passed into the method. This will make calling the method with internal structs much easier, as you won't need to do some reflection "heavy lifting" just to set the structToList generic method with a specific type.

Community
  • 1
  • 1
Itay Podhajcer
  • 2,616
  • 2
  • 9
  • 14
  • I got the first part working (see edited post). I see your point with generics. Their type should be known at compile-time, which isn't the case in my example. How can I accomplish the second part, without generics? How can I pass an unkown type of struct to my function? – PCFX Dec 31 '16 at 18:38
  • You can use object for the type of the parameter. – Itay Podhajcer Dec 31 '16 at 20:22
0

I came up with two proper solution which imo should solve the my problem. Both ways abstain from the use of generics because we assume that we don't know the type of the struct within a struct at compile-time.

1) At first I used this function mentioned in this answer to differentiate if it's a primitive datatype which was expanded by some special cases e.g. string which is basically an array of chars. After you are able to determine if the next field is a structure you can recursively call your function again or add it to the list.

    private List<Item> _itemList = new List<Item>();

    private List<Item> structToList(object structure)
    {
        foreach (var field in structure.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
        {
            if (!IsSimple(field.FieldType))
                structToList(field.GetValue(structure));
            else
                _itemList.Add(new Item(field.Name, field.GetValue(structure)));
        }
        return _itemList;
    }

    private static bool IsSimple(Type type)
    {
        return type.IsPrimitive
          || type.IsEnum
          || type.Equals(typeof(string))
          || type.Equals(typeof(decimal));
    }

2) The second solution uses the Type.IsNested property to achieve the same.

    private List<Item> _itemList = new List<Item>();

    private List<Item> structToList1(object structure)
    {
        foreach (var field in structure.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
        {
            if (field.FieldType.IsNested)
                structToList(field.GetValue(structure));
            else
                _itemList.Add(new Item(field.Name, field.GetValue(structure)));
        }
        return _itemList;
    }
Community
  • 1
  • 1
PCFX
  • 1
  • 3