views:

53

answers:

1

Im creating a collection of (dynamically generated type) for display in a silverlight grid and one of the processes involves creating an import (dynamically generated type) type then mapping the properties on the import type to the collection of (dynamically generated type) both types share a Id property that identifies the item ( be it on the grid or in the import)

ie type bound to grid

 int Id {get; set}     
 string Foo {get;set;}
 string FooFoo {get;set;}

and import the type

 int Id {get; set}
 string Foo {get;set}

where ids match i want to copy foos.

What is a fast way to map properties from the one type to another in a collection?

EDIT

Heres the final Typemapper implementation with thanks to Stephan, as a feature will only map the two types when the keymembers are equal, mappings defined via a dictionary string string representing the member names, works in silverlight.

public class TypeMapper
{ 
    private readonly DynamicMethod _mapper;


    public static DynamicMethod BuildMapper(Type fromType, 
                                            Type toType,
                                            KeyValuePair<string, string> keyMemberMap,
                                            Dictionary<string, string> memberMappings)
    {

        var method = new DynamicMethod("Map", typeof(bool), new[] { fromType, toType });

        // Preparing Reflection instances
        MethodInfo getFromKeyMethod = fromType.GetMethod(
            string.Format("get_{0}", keyMemberMap.Key),
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        MethodInfo getToKeyMethod = toType.GetMethod(
            string.Format("get_{0}", keyMemberMap.Value),
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

        ILGenerator gen = method.GetILGenerator();

        // Preparing locals
        gen.DeclareLocal(typeof(Boolean));
        // Preparing labels
        Label labelNoMatch = gen.DefineLabel();
        // Writing body
        gen.Emit(OpCodes.Ldarg_0);
        gen.Emit(OpCodes.Callvirt, getFromKeyMethod);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Callvirt, getToKeyMethod);
        gen.Emit(OpCodes.Ceq);
        gen.Emit(OpCodes.Stloc_0);
        gen.Emit(OpCodes.Ldloc_0);
        gen.Emit(OpCodes.Brfalse_S, labelNoMatch);
        gen.Emit(OpCodes.Ldarg_1);
        gen.Emit(OpCodes.Ldarg_0);


        foreach (var mapping in memberMappings)
        {
            var getFromValueMethod = fromType.GetMethod(
               string.Format("get_{0}", mapping.Key),
               BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            var setToValueMethod = toType.GetMethod(
                string.Format("set_{0}", mapping.Value),
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            gen.Emit(OpCodes.Callvirt, getFromValueMethod);
            gen.Emit(OpCodes.Callvirt, setToValueMethod);
        }

        gen.MarkLabel(labelNoMatch);
        gen.Emit(OpCodes.Ldloc_0);
        gen.Emit(OpCodes.Ret);


        return method;
    }

    public void Map (object fromInstance, object toInstance)
    {
        _mapper.Invoke(null, new[] { fromInstance, toInstance });
    }


    public TypeMapper(Type fromType, Type toType, 
        KeyValuePair<string, string> keyMemberMap, 
        Dictionary<string, string> memberMappings)
    {
        _mapper = BuildMapper(fromType, toType, keyMemberMap, memberMappings); 
    }

}
+1  A: 
bound.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList()
    .ForEach(s => 
        {
            var prop = import.GetType().GetProperty(s.Name,BindingFlags.Public | BindingFlags.Instance);
            if(prop != null)
            {
                prop.SetValue(import,s.GetValue(bound,null),null);
            }
        });

That will map properties from one item to another. If you want to do it in a collection make that a method and do myCollection.Select(o => MapProperties(o,mapType));.

Note: The method currently uses an existing object and copies into it. You could have your method except a type and then call Activator.CreateInstance(type) and set that to the value of import for my snippet.

Edit

Dynamic Methods

This article has a good example of generating a DynamicMethod to do the deep copy of dynamic objects. It will have a much longer setup time then the reflection solution, but each subsequent call will be as fast as if compiled.

Edit

Actual Example:

DynamicMethod GetMapper(Type type1, Type type2)
{
DynamicMethod method = new DynamicMethod("junk", type2,
new Type[] { type1 });

ILGenerator il = method.GetILGenerator();

LocalBuilder obj0 = il.DeclareLocal(type2); //target

// create object and store in local 0
ConstructorInfo ctor = type2.GetConstructor(
  new Type[] { });
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Stloc_0);


PropertyInfo[] properties = type1.GetProperties(BindingFlags.Instance
| BindingFlags.Public | BindingFlags.FlattenHierarchy);
foreach (PropertyInfo prop in properties)
{
// local constructed object
il.Emit(OpCodes.Ldloc_0);

// load source argument
il.Emit(OpCodes.Ldarg_0);

// get property value
il.EmitCall(OpCodes.Callvirt, type1.GetMethod(
    "get_" + prop.Name), null);
il.EmitCall(OpCodes.Callvirt, type2.GetMethod(
    "set_" + prop.Name), null);
}

il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
return method;
}

You should be able to do GetMapper(sourceType,destinationType).Invoke(null,new [] { myObject});

Stephan
i did something quite similar, it turns out to be slow (using reflection) when we are considering a few 1000s. still +1
almog.ori
If the types are dynamically generated, this is likely the best you are going to do. You make be able to speed it up a little by using `Reflection.Emit` to generate a factory delegate and then running that, but I'm not sure the exact path to do that. You may want to take a look at the MSDN for `Reflection.Emit` as it could give a good direction.
Stephan
Forgot that this was a Silverlight question. My second solution will probably not work for Silverlight. It would be possible to do it as a server side operation (like with RIA), but as a purely client side it likely won't work.
Stephan
wow!! nice, simple, yet very useful use of emit
almog.ori