views:

600

answers:

2

Hi, for auditory reasons I stores the arguments of the business methods serialized into the database using the binaryformatter.

The problem is that when an argument is a generic list I don't find the way to cast the deserialized object because I don't know the type, or If I will know the type I don't know how to cast the object at runtime.

Anybody knows how to cast an object containing a generic list dinamically at runtime?

I need to do this because I need to show the deserialized data in a property grid:

object objArg = bformatter.Deserialize(memStr);

//If the type is a clr type (int, string, etc)
if (objArg.GetType().Module.Name == "mscorlib.dll")
{                 
    //If the type is a generic type (List<>, etc) 
    //(I'm only use List for these cases)
    if (objArg.GetType().IsGenericType)
    {
         // here is the problem
         pgArgsIn.SelectedObject = new { Value = objArg};                    

         //In the previous line I need to do something like... 
         //new { Value = (List<objArg.GetYpe()>) objArg};
     }
     else
     {
         pgArgsIn.SelectedObject = new { Value = objArg.ToString() };                    
     }
}
else
{
    //An entity object
    pgArgsIn.SelectedObject = objArg;                
}
+1  A: 

If the serializer you are using does not retain the type - at the least, you must store the type of T along with the data, and use that to create the generic list reflectively:

//during storage:
Type elementType = myList.GetType().GetGenericTypeDefinition().GetGenericArguments[0];
string typeNameToSave = elementType.FullName;

//during retrieval
string typeNameFromDatabase = GetTypeNameFromDB();
Type elementType = Type.GetType(typeNameFromDatabase);
Type listType = typeof(List<>).MakeGenericType(new Type[] { elementType });

Now you have listType, which is the exact List<T> you used (say, List<Foo>). You can pass that type into your deserialization routine.

Rex M
Yes! this is a good aproach, I don't have the solution yet. I have the deserialized object, and I have the generic type (List<Entidades.Diagnosticos>). Now, I need to cast the object at runtime, I need to do something like MyTypedObjectArg = objArg as List<Entidades.Diagnosticos>;Why?.. If I bind the property grid to the object I get: Value = [System.Collection.Generic.List`1], but if I set typed object I have a collection and I can see entities inside and the properties inside the entities, etc.
Ariel Larraburu
+1  A: 

With BinaryFormatter you don't need to know the type; the metadata is included in the stream (making it bigger, but hey!). However, you can't cast unless you know the type. Often in this scenario you have to use common known interfaces (non-generic IList etc) and reflection. And lots of it.

I also can't think of a huge requirement to know the type to show in a PropertyGrid - since this accepts object, just give it what BinaryFormatter provides. Is there a specific issue you are seeing there? Again, you might want to check for IList (non-generic) - but it isn't worth worrying about IList<T>, since this isn't what PropertyGrid checks for!

You can of course find the T if you want (like so) - and use MakeGenericType() and Activator.CreateInstance - not pretty.


OK; here's a way using custom descriptors that doesn't involve knowing anything about the object or the list type; if you really want it is possible to expand the list items directly into the properties, so in this example you'd see 2 fake properties ("Fred" and "Wilma") - that is extra work, though ;-p

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;

class Person
{
    public string Name { get; set; }
    public DateTime DateOfBirth { get; set; }

    public override string ToString() {
        return Name;
    }
}

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Person fred = new Person();
        fred.Name = "Fred";
        fred.DateOfBirth = DateTime.Today.AddYears(-23);
        Person wilma = new Person();
        wilma.Name = "Wilma";
        wilma.DateOfBirth = DateTime.Today.AddYears(-20);

        ShowUnknownObject(fred, "Single object");
        List<Person> list = new List<Person>();
        list.Add(fred);
        list.Add(wilma);
        ShowUnknownObject(list, "List");
    }
    static void ShowUnknownObject(object obj, string caption)
    {
        using(Form form = new Form())
        using (PropertyGrid grid = new PropertyGrid())
        {
            form.Text = caption;
            grid.Dock = DockStyle.Fill;
            form.Controls.Add(grid);
            grid.SelectedObject = ListWrapper.Wrap(obj);
            Application.Run(form);
        }
    }
}

[TypeConverter(typeof(ListWrapperConverter))]
public class ListWrapper
{
    public static object Wrap(object obj)
    {
        IListSource ls = obj as IListSource;
        if (ls != null) obj = ls.GetList(); // list expansions

        IList list = obj as IList;
        return list == null ? obj : new ListWrapper(list);
    }
    private readonly IList list;
    private ListWrapper(IList list)
    {
        if (list == null) throw new ArgumentNullException("list");
        this.list = list;
    }
    internal class ListWrapperConverter : TypeConverter
    {
        public override bool GetPropertiesSupported(ITypeDescriptorContext context)
        {
            return true;
        }
        public override PropertyDescriptorCollection GetProperties(
            ITypeDescriptorContext context, object value, Attribute[] attributes) {
            return new PropertyDescriptorCollection(
                new PropertyDescriptor[] { new ListWrapperDescriptor(value as ListWrapper) });
        }
    }
    internal class ListWrapperDescriptor : PropertyDescriptor {
        private readonly ListWrapper wrapper;
        internal ListWrapperDescriptor(ListWrapper wrapper) : base("Wrapper", null)
        {
            if (wrapper == null) throw new ArgumentNullException("wrapper");
            this.wrapper = wrapper;
        }
        public override bool ShouldSerializeValue(object component) { return false; }
        public override void ResetValue(object component) {
            throw new NotSupportedException();
        }
        public override bool CanResetValue(object component) { return false; }
        public override bool IsReadOnly {get {return true;}}
        public override void SetValue(object component, object value) {
            throw new NotSupportedException();
        }
        public override object GetValue(object component) {
            return ((ListWrapper)component).list;
        }
        public override Type ComponentType {
            get { return typeof(ListWrapper); }
        }
        public override Type PropertyType {
            get { return wrapper.list.GetType(); }
        }
        public override string DisplayName {
            get {
                IList list = wrapper.list;
                if (list.Count == 0) return "Empty list";

                return "List of " + list.Count
                    + " " + list[0].GetType().Name;
            }
        }
    }
}
Marc Gravell
Hi Marc, I don't know why, but If I set the property grid like this:pgArgsIn.SelectedObject = new { Value = arg}; //When arg is the deserializaed argument.I have something like this in the property grid:Value = [system.collections....], without anything more that brings me a chance to know what the object have inside.But, if I set the property grid like this:pgArgsIn.SelectedObject = new { Value = arg as List<Entities.Diagnostico>}; I have in the property grid:Value = (Collection) [...] with a button that show me the list of entities whit her properties.
Ariel Larraburu
Then, I need to cast the deserialized objet at runtime. The activator an other things returns an object, then I dont't have what I need.
Ariel Larraburu
Why do you think you need to cast? PropertyGrid doesn't care about your variable type...
Marc Gravell