Examples using List<T>, IList<T>, Dictionary, IEnumerable

Aug 25, 2010 at 10:53 PM

It would be most helpful to have some examples that show mapping of a List or Dictionary of objects.  It looks like "Stuff.cs" was a start in this direction?

Coordinator
Aug 26, 2010 at 7:50 AM

well, there are lots of ways you could do this, and the basic thing is that you have to cycle through the collection and map each element and add it to another collection, so you could do this with a simple foreach/linq/plinq etc. You could also put this algorithm in a method or inside an injection, you just do it the way it's fits better to you.

Here is a basic sample :

[TestFixture]
    public class CollectionsTest
    {
        [Test]
        public void Test()
        {
            var p = new Person
                        {
                            Name = "a",
                            Age = 3,
                            Children = new[] 
                                           { 
                                               new Person { Age = 1 }, 
                                               new Person { Age = 2 }, 
                                               new Person { Age = 3 } 
                                           }
                        };

            var pwm = new PersonViewModel();

            pwm.InjectFrom(p);
            pwm.Children = p.Children.Select(c => new PersonViewModel().InjectFrom(c)).Cast<PersonViewModel>();

            pwm.Name.IsEqualTo(p.Name);
            pwm.Age.IsEqualTo(p.Age);
            pwm.Children.Count().IsEqualTo(3);
            pwm.Children.ToArray()[0].Age.IsEqualTo(1);
        }

        public class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public IEnumerable<Person> Children { get; set; }
        }

        public class PersonViewModel
        {
            public string Name { get; set; }
            public int Age { get; set; }
            public IEnumerable<PersonViewModel> Children { get; set; }
        }
    }
please let me know your opinion about this solution

Omu

Aug 26, 2010 at 8:52 AM

I have something like this:

    public class Project
    {
        public double Cost { get; set; }
        public double? Percent { get; set; }
        public string Name { get; set; }
    }

    public class BigProject
    {
        public int cost { get; set; }
        public int percent { get; set; }
        public string Name { get; set; }
    }

var projects = new Project[10];

var bigprojects = new BigProject[] { new BigProject { Name = "Theta", cost = 99, percent = 20 },
                                                 new BigProject { Name = "Phi", cost = 18, percent = 47 } };

Now I want to do something like projects.InjectFrom<BIGINJECT>(bigprojects)

Note: that I do not want to exceed the array bounds of projects.

I would also like to be able to start with an empty projects array and grow it as the source is being mapped into it.

 

Coordinator
Aug 26, 2010 at 9:29 AM

I did it with a List<> instead of array, hope it will help:

[TestFixture]
    public class Noemata
    {
        [Test]
        public void Test()
        {
            var projects = new List<Project>();

            var bigprojects = new[] { new BigProject { Name = "Theta", Cost = 99, Percent = 20 },
                                      new BigProject { Name = "Phi", Cost = 18, Percent = 47 } };
            projects = ProjectMapper.Map(bigprojects).ToList();

            projects[0].Name.IsEqualTo(bigprojects[0].Name);
            projects[0].Cost.IsEqualTo((double)bigprojects[0].Cost);
            projects[0].Percent.IsEqualTo((double?)bigprojects[0].Percent);
            projects[1].Name.IsEqualTo(bigprojects[1].Name);
        }

        public class Project
        {
            public double Cost { get; set; }
            public double? Percent { get; set; }
            public string Name { get; set; }
        }

        public class BigProject
        {
            public int Cost { get; set; }
            public int Percent { get; set; }
            public string Name { get; set; }
        }

        
        public class IntToDouble : LoopValueInjection<int, double>
        {
            protected override double SetValue(int sourcePropertyValue)
            {
                return sourcePropertyValue;
            }
        }

        public class IntToNDouble : LoopValueInjection<int, double?>
        {
            protected override double? SetValue(int sourcePropertyValue)
            {
                return sourcePropertyValue;
            }
        }

        public static class ProjectMapper
        {
            public static IEnumerable<Project> Map(BigProject[] bigProjects)
            {
                foreach (var bigProject in bigProjects)
                {
                    var p = new Project();
                    p.InjectFrom(bigProject)
                        .InjectFrom<IntToDouble>(bigProject)
                        .InjectFrom<IntToNDouble>(bigProject);
                    yield return p;
                }
            }
        }
    }

Cheers,

Omu

Aug 26, 2010 at 9:45 AM

Nice!

I was hoping to hide more of the details of the mapping process from the "user".  I thought I could interrogate the properties of the bigprojects array (it could just as well be a list), figure out its size etc., do likewise for projects, and proceed to do the mapping.

Here's something the Telerik folks came up with ... I thought you might be doing similar things under the hood:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace RowsAsColumns
{
    public abstract class DynamicClass
    {
        public override string ToString()
        {
            PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var sb = new StringBuilder();
            sb.Append("{");
            for (int i = 0; i < props.Length; i++)
            {
                if (i > 0) sb.Append(", ");
                sb.Append(props[i].Name);
                sb.Append("=");
                sb.Append(props[i].GetValue(this, null));
            }
            sb.Append("}");
            return sb.ToString();
        }
    }

    public class DynamicProperty
    {
        private readonly string name;
        private readonly Type type;

        /// <exclude/>
        /// <excludeToc/>
        public DynamicProperty(string name, Type type)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (type == null) throw new ArgumentNullException("type");
            this.name = name;
            this.type = type;
        }

        public string Name
        {
            get
            {
                return this.name;
            }
        }

        public Type Type
        {
            get
            {
                return this.type;
            }
        }
    }

    internal class ClassFactory
    {
        public static readonly ClassFactory Instance = new ClassFactory();

        // Trigger lazy initialization of static fields

        private readonly Dictionary<Signature, Type> classes;
        private readonly ModuleBuilder module;
        private int classCount;

        private ClassFactory()
        {
            var name = new AssemblyName("DynamicClasses");
            AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
            this.module = assembly.DefineDynamicModule("Module");
            this.classes = new Dictionary<Signature, Type>();
        }

        public Type GetDynamicClass(IEnumerable<DynamicProperty> properties)
        {
            var signature = new Signature(properties);
            Type type;
            if (!this.classes.TryGetValue(signature, out type))
            {
                type = this.CreateDynamicClass(signature.properties);
                this.classes.Add(signature, type);
            }
            return type;
        }

        private Type CreateDynamicClass(DynamicProperty[] properties)
        {
            string typeName = "DynamicClass" + (this.classCount + 1);
            TypeBuilder tb = this.module.DefineType(
                typeName,
                TypeAttributes.Class |
                TypeAttributes.Public,
                typeof(DynamicClass));
            FieldInfo[] fields = this.GenerateProperties(tb, properties);
            this.GenerateEquals(tb, fields);
            this.GenerateGetHashCode(tb, fields);
            Type result = tb.CreateType();
            this.classCount++;
            return result;
        }

        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private void GenerateEquals(TypeBuilder tb, FieldInfo[] fields)
        {
            MethodBuilder mb = tb.DefineMethod(
                "Equals",
                MethodAttributes.Public | MethodAttributes.ReuseSlot |
                MethodAttributes.Virtual | MethodAttributes.HideBySig,
                typeof(bool),
                new[] { typeof(object) });
            ILGenerator gen = mb.GetILGenerator();
            LocalBuilder other = gen.DeclareLocal(tb);
            Label next = gen.DefineLabel();
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Isinst, tb);
            gen.Emit(OpCodes.Stloc, other);
            gen.Emit(OpCodes.Ldloc, other);
            gen.Emit(OpCodes.Brtrue_S, next);
            gen.Emit(OpCodes.Ldc_I4_0);
            gen.Emit(OpCodes.Ret);
            gen.MarkLabel(next);
            foreach (FieldInfo field in fields)
            {
                Type ft = field.FieldType;
                Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
                next = gen.DefineLabel();
                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, field);
                gen.Emit(OpCodes.Ldloc, other);
                gen.Emit(OpCodes.Ldfld, field);
                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new[] { ft, ft }), null);
                gen.Emit(OpCodes.Brtrue_S, next);
                gen.Emit(OpCodes.Ldc_I4_0);
                gen.Emit(OpCodes.Ret);
                gen.MarkLabel(next);
            }
            gen.Emit(OpCodes.Ldc_I4_1);
            gen.Emit(OpCodes.Ret);
        }

        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields)
        {
            MethodBuilder mb = tb.DefineMethod(
                "GetHashCode",
                MethodAttributes.Public | MethodAttributes.ReuseSlot |
                MethodAttributes.Virtual | MethodAttributes.HideBySig,
                typeof(int),
                Type.EmptyTypes);
            ILGenerator gen = mb.GetILGenerator();
            gen.Emit(OpCodes.Ldc_I4_0);
            foreach (FieldInfo field in fields)
            {
                Type ft = field.FieldType;
                Type ct = typeof(EqualityComparer<>).MakeGenericType(ft);
                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, field);
                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new[] { ft }), null);
                gen.Emit(OpCodes.Xor);
            }
            gen.Emit(OpCodes.Ret);
        }

        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        private FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties)
        {
            FieldInfo[] fields = new FieldBuilder[properties.Length];
            for (int i = 0; i < properties.Length; i++)
            {
                DynamicProperty dp = properties[i];
                FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private);
                PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);
                MethodBuilder mbGet = tb.DefineMethod(
                    "get_" + dp.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    dp.Type,
                    Type.EmptyTypes);
                ILGenerator genGet = mbGet.GetILGenerator();
                genGet.Emit(OpCodes.Ldarg_0);
                genGet.Emit(OpCodes.Ldfld, fb);
                genGet.Emit(OpCodes.Ret);
                MethodBuilder mbSet = tb.DefineMethod(
                    "set_" + dp.Name,
                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                    null,
                    new[] { dp.Type });
                ILGenerator genSet = mbSet.GetILGenerator();
                genSet.Emit(OpCodes.Ldarg_0);
                genSet.Emit(OpCodes.Ldarg_1);
                genSet.Emit(OpCodes.Stfld, fb);
                genSet.Emit(OpCodes.Ret);
                pb.SetGetMethod(mbGet);
                pb.SetSetMethod(mbSet);
                fields[i] = fb;
            }
            return fields;
        }
    }

    internal class Signature : IEquatable<Signature>
    {
        public int hashCode;
        public DynamicProperty[] properties;

        [SuppressMessage("Microsoft.Performance", "CA1805:DoNotInitializeUnnecessarily")]
        public Signature(IEnumerable<DynamicProperty> properties)
        {
            this.properties = properties.ToArray();
            this.hashCode = 0;
            foreach (DynamicProperty p in properties)
            {
                this.hashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode();
            }
        }

        public override bool Equals(object obj)
        {
            return obj is Signature ? this.Equals((Signature) obj) : false;
        }

        public override int GetHashCode()
        {
            return this.hashCode;
        }

        public bool Equals(Signature other)
        {
            if (this.properties.Length != other.properties.Length) return false;
            for (int i = 0; i < this.properties.Length; i++)
            {
                if (this.properties[i].Name != other.properties[i].Name ||
                    this.properties[i].Type != other.properties[i].Type) return false;
            }
            return true;
        }
    }
}

Aug 26, 2010 at 12:01 PM

I just noticed one detail that was not addressed by your solution.  In my original query, the field values were named differently (case difference).

    public class Project
    {
        public double Cost { get; set; }
        public double? Percent { get; set; }
        public string Name { get; set; }
    }

    public class BigProject
    {
        public int cost { get; set; }
        public int percent { get; set; }
        public int filler1;
        public int filler2;
        public string Name { get; set; }
}


It would be nice to be able to inject by field index.  Something like, project.InjectFromField(bigproject).Fields(0,1,4) with automatic conversion being done on the selected fields.  Similarly, arrays should be handled without having to resort to an iterator.  Having a field selector mechanism would be very useful.

Coordinator
Aug 26, 2010 at 12:27 PM

ok, if you need to inject from props that start with lower case into props that start with upper (but they both are equal without considering the case)

ovveride this method in the LoopValueInjection

 

         protected override string TargetPropName(string sourcePropName)
        {
            return sourcePropName[0].ToString().ToUpper() + sourcePropName.Substring(1);
        }

 

if you want to inject by property index, do like this:

 public class MyUberInjection : ValueInjection
{
protected override void Inject(object source, object target)
{
var sprops = source.GetProps();
var tprops = target.GetProps();

tprops[3].SetValue(target, sprops[2].GetValue(source));
}
}
Coordinator
Aug 26, 2010 at 12:36 PM

in case you want to send parameters to the injection do like this:

 public class MyUberInjection : ValueInjection
    {
        private string _s;
        private int _i;
        private int[] _arr;

        public MyUberInjection(string s, int i, params int[] arr)
        {
            _s = s;
            _i = i;
            _arr = arr;
        }

        protected override void Inject(object source, object target)
        {
           ///stuff
        }
    }

and use it like this:

o.InjectFrom(new MyUberInjection("aaa", 12, 1, 2, 2, 2, 2));

of course this is just a sample, but I think you get the idea,

about hiding mapping details from the user, I think that with limit the possibility of doing anything you want with the ValueInjecter,

as you can see now,

you can do everything

Cheers,

Omu