Maximum performance with the SmartConventionInjection

this is a new injection that I will include in the next release

It's similar to the ConventionInjection except this one remembers the properties that are used to inject value from each combination of types T1,T2 ( T1.InjectFrom<Smart>(T2) ), so it doesn't has to look for them each time.

It has one limitation comparing to the ConventionInjection, you don't have the values of the Source and Target Properties in the Match method but you have them in the SetValue Method and you can cancel the setting of the value to that property if you set false to the ref parameter setValue

Speed Test

for 10000 iterations

 Convention: 00:00:00.7606872
 AutoMapper: 00:00:00.6242320
 Smart convention: 00:00:00.3352158

The Test

comparing the SmartConventionInjection with ConventionInjection and AutoMapper
    public class PerformanceTests
    {
        public class Foo
        {
            public string Name { get; set; }
            public string Prop1 { get; set; }
            public object Prop2 { get; set; }
            public string Prop3 { get; set; }
            public int Prop4 { get; set; }
            public int Prop5 { get; set; }
        }

        public class Smart : SmartConventionInjection
        {
            protected override bool Match(SmartConventionInfo c)
            {
                return c.SourceProp.Name == c.TargetProp.Name;
            }
        }

        public class Conv : ConventionInjection
        {
            protected override bool Match(ConventionInfo c)
            {
                return c.SourceProp.Name == c.TargetProp.Name;
            }
        }

        [Test]
        public void SpeedTest()
        {
            var foo = new Foo { Prop3 = "Aaa", Name = "foo" };
            
            AutoMapper.Mapper.CreateMap<Foo, Foo>();

            var w = new Stopwatch();
            
            w.Reset();
            w.Start();
            for (var i = 0; i < 10000; i++)
            {
                new Foo().InjectFrom<Conv>(foo);
            }
            w.Stop();
            Console.WriteLine("Convention: " + w.Elapsed);

            w.Reset();
            w.Start();
            for (var i = 0; i < 10000; i++)
            {
                AutoMapper.Mapper.Map<Foo,Foo>(foo);
            }
            w.Stop();
            Console.WriteLine("Automapper: " + w.Elapsed);

            w.Reset();
            w.Start();
            for (var i = 0; i < 10000; i++)
            {
                new Foo().InjectFrom<Smart>(foo);
            }
            w.Stop();
            Console.WriteLine("Smart convention: " + w.Elapsed);
            w.Reset();
        }
}

The Injection

    public class SmartConventionInjection : ValueInjection
    {
        private class Path
        {
            public IDictionary<string, string> MatchingProps { get; set; }
        }

        private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<KeyValuePair<Type, Type>, Path>> WasLearned = new ConcurrentDictionary<Type, ConcurrentDictionary<KeyValuePair<Type, Type>, Path>>();

        protected virtual void SetValue(PropertyDescriptor prop, object component, object value)
        {
            prop.SetValue(component, value);
        }

        protected virtual object GetValue(PropertyDescriptor prop, object component)
        {
            return prop.GetValue(component);
        }

        protected virtual bool Match(SmartConventionInfo c)
        {
            return c.SourceProp.Name == c.TargetProp.Name && c.SourceProp.PropertyType == c.TargetProp.PropertyType;
        }

        protected virtual void ExecuteMatch(SmartMatchInfo mi)
        {
            SetValue(mi.TargetProp, mi.Target, GetValue(mi.SourceProp, mi.Source));
        }

        private Path Learn(object source, object target)
        {
            Path path = null;
            var sourceProps = source.GetProps();
            var targetProps = target.GetProps();
            var smartConventionInfo = new SmartConventionInfo
                {
                    SourceType = source.GetType(),
                    TargetType = target.GetType()
                };

            for (var i = 0; i < sourceProps.Count; i++)
            {
                var sourceProp = sourceProps[i];
                smartConventionInfo.SourceProp = sourceProp;

                for (var j = 0; j < targetProps.Count; j++)
                {
                    var targetProp = targetProps[j];
                    smartConventionInfo.TargetProp = targetProp;

                    if (!Match(smartConventionInfo)) continue;
                    if (path == null)
                        path = new Path
                            {
                                MatchingProps = new Dictionary<string, string> { { smartConventionInfo.SourceProp.Name, smartConventionInfo.TargetProp.Name } }
                            };
                    else path.MatchingProps.Add(smartConventionInfo.SourceProp.Name, smartConventionInfo.TargetProp.Name);
                }
            }
            return path;
        }

        protected override void Inject(object source, object target)
        {
            var sourceProps = source.GetProps();
            var targetProps = target.GetProps();

            var cacheEntry = WasLearned.GetOrAdd(GetType(), new ConcurrentDictionary<KeyValuePair<Type, Type>, Path>());

            var path = cacheEntry.GetOrAdd(new KeyValuePair<Type, Type>(source.GetType(), target.GetType()), pair => Learn(source, target));

            if (path == null) return;

            foreach (var pair in path.MatchingProps)
            {
                var sourceProp = sourceProps.GetByName(pair.Key);
                var targetProp = targetProps.GetByName(pair.Value);
                ExecuteMatch(new SmartMatchInfo
                    {
                        Source = source, 
                        Target = target, 
                        SourceProp = sourceProp, 
                        TargetProp = targetProp
                    });
            }
        }
    }

    public class SmartConventionInfo
    {
        public Type SourceType { get; set; }
        public Type TargetType { get; set; }

        public PropertyDescriptor SourceProp { get; set; }
        public PropertyDescriptor TargetProp { get; set; }
    }

    public class SmartMatchInfo
    {
        public PropertyDescriptor SourceProp { get; set; }
        public PropertyDescriptor TargetProp { get; set; }
        public object Source { get; set; }
        public object Target { get; set; }
    }

Last edited Mar 11, 2013 at 6:52 PM by o, version 6

Comments

o Mar 7, 2013 at 11:46 PM 
thnx for noticing this @digitalpacman

digitalpacman May 3, 2012 at 8:48 PM 
The cache breaks if you have multiple of these. The cache is not per type that inherits so every property that has ever matched gets ran against "SetValue". This is not intuitive.

Foo : SmartConventionInjection
Match()
// match anything that starts with Foo

Bar : SmartConventionInjection
Match()
// match anything that starts with Bar
SetValue()
// This will run for everything that starts with Foo, nothing that matches Bar will be ran