⬆️ ⬇️

Dynamic calls: method comparison



Dynamic challenges: what is it and why?





I think for each developer working in static programming languages, sometimes there was a need to resort to dynamic calls — to call a method of something that nothing is yet known about. Or get some property from some object, which will be known only at run-time.



This is sometimes used in algorithms based on so-called duck typing:
If something looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck.


')

In this article I would like to review the main methods available in Microsoft .NET 4.0, compare their performance and syntax.





Main available options





First I will list the options that I will analyze in this article. If any of the options does not seem interesting, just skip. And if there are any other options worth considering, please write, I will add them to the text.



At once I will warn that the considered methods are not completely interchangeable: each has its own peculiarity and scope. I wanted to compare the complexity of use and the speed of accessing / calling dynamic methods. This will give the opportunity to choose a particular method based on a specific task.



So:



- Reflection (Reflection, RTTI, introspection)

- Dictionary of meanings

- Delegate Dictionary

- Dynamic object with a static container

- Dynamic object with Expando

- Dynamic object with call pickup

- Compilation of expressions (Expressions)



Testing method





So, as the goal is to compare the speed of access to fields / methods, I designed the tests in such a general structure:

- there is a testing container object (ObjectTester), which contains the test object

- the tested object must contain two properties - one whole (A) and one string (B)

- the testing object delegates calls to the A and B objects to be tested.

- the test environment creates an array of 2000 container objects, filling them with data

- the test environment performs a testing operation 2000 times on each object in the array

- the testing operation should receive at least two times the value of properties A and B (two times in order to give an opportunity to check the presence of a cache in the solution)

- for each test method, the test is run 5 times



The code is compiled under the Release configuration.

The tests were run on Windows 7 x64, .NET 4.0.30319, Intel Core 2 Quad Q9400 (2666 MHz), 4GB DDR2



Static (non dynamic) implementation



For comparison, as a reference point such an ObjectTester is added, which contains a simple class with two auto-properties and delegates calls to this class.



This method will be the fastest, since it is not at all dynamic. Therefore, we will compare other methods with respect to this.



The code is quite simple (by the way, remember the static class, we will use it):

private class StaticObject

{

public int A { get ; set ; }

public string B { get ; set ; }

}



private class StaticObjectTester : ObjectTester

{

private readonly StaticObject _o = new StaticObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .
private class StaticObject

{

public int A { get ; set ; }

public string B { get ; set ; }

}



private class StaticObjectTester : ObjectTester

{

private readonly StaticObject _o = new StaticObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .




Result: 00: 00: 06.6274538 (+ 0.00%)



Pros:

- As quickly as possible

Minuses

- Not dynamic



Reflection (Reflection, RTTI, Introspection)





I think this is the very first way that comes to mind when you need dynamic challenges.



In order to maximize speed, I used static caching PropertyInfo. Usually in real projects there is always the opportunity to somehow cache the information necessary for reflection.



So, as we are interested in the speed of work of exactly reflection, then for data storage we will use the ready-made class with two properties.



Test object code:

private class ReflectionObjectTester : ObjectTester

{

private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );

private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );

private static readonly object [] Empty = new object [0];

private readonly Object _o = new StaticObject();



public override int A

{

get { return ( int ) PropertyA.GetValue(_o, Empty); }

set { PropertyA.SetValue(_o, value , Empty); }

}



public override string B

{

get { return ( string ) PropertyB.GetValue(_o, Empty); }

set { PropertyB.SetValue(_o, value , Empty); }

}

}




* This source code was highlighted with Source Code Highlighter .

private class ReflectionObjectTester : ObjectTester

{

private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );

private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );

private static readonly object [] Empty = new object [0];

private readonly Object _o = new StaticObject();



public override int A

{

get { return ( int ) PropertyA.GetValue(_o, Empty); }

set { PropertyA.SetValue(_o, value , Empty); }

}



public override string B

{

get { return ( string ) PropertyB.GetValue(_o, Empty); }

set { PropertyB.SetValue(_o, value , Empty); }

}

}




* This source code was highlighted with Source Code Highlighter .

private class ReflectionObjectTester : ObjectTester

{

private static readonly PropertyInfo PropertyA = typeof (StaticObject).GetProperty( "A" );

private static readonly PropertyInfo PropertyB = typeof (StaticObject).GetProperty( "B" );

private static readonly object [] Empty = new object [0];

private readonly Object _o = new StaticObject();



public override int A

{

get { return ( int ) PropertyA.GetValue(_o, Empty); }

set { PropertyA.SetValue(_o, value , Empty); }

}



public override string B

{

get { return ( string ) PropertyB.GetValue(_o, Empty); }

set { PropertyB.SetValue(_o, value , Empty); }

}

}




* This source code was highlighted with Source Code Highlighter .






Result : 00: 01: 32.3217880 (+ 1293.02%)



In fact, the use of reflection has increased the execution time by almost 13 times! A good way to think before using this method.



Pros:

- fully run time

Minuses:

- too long

- problems may arise due to the requirement of “ReflectionPermission” rights



Dictionary of meanings





The meaning of the method is to simply store the values ​​in a dictionary (Dictionary <TKey, TValue>). As a key, we take the name of the field, and the value is the value of the field. You have to use Object as the value type.



Code:

private class DictionaryObjectTester : ObjectTester

{

private const string AName = "A" ;

private const string BName = "B" ;

private readonly Dictionary< string , Object> _o = new Dictionary< string , object >();



public override int A

{

get { return ( int ) _o[AName]; }

set { _o[AName] = value ; }

}



public override string B

{

get { return ( string ) _o[BName]; }

set { _o[BName] = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .
private class DictionaryObjectTester : ObjectTester

{

private const string AName = "A" ;

private const string BName = "B" ;

private readonly Dictionary< string , Object> _o = new Dictionary< string , object >();



public override int A

{

get { return ( int ) _o[AName]; }

set { _o[AName] = value ; }

}



public override string B

{

get { return ( string ) _o[BName]; }

set { _o[BName] = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .




Result: 00: 00: 10.8516518 (+ 63.64%)



Pros:

- completely dynamic: you can add and remove

Minuses:

- weak typing of values ​​(Object)

- only values, not methods



Delegates dictionary





In practice, a slightly more complicated version is often used - we do not store values, but methods for obtaining these values. Or just some dynamic set of functions / actions.



Implementation:

private class DictionaryDelegateTester : ObjectTester

{

private const string AName = "A" ;

private const string BName = "B" ;



private readonly Dictionary< string , Func<Object>> _getters;

private readonly Dictionary< string , Action<Object>> _setters;



private readonly StaticObject _o = new StaticObject();



public DictionaryDelegateTester()

{

_getters = new Dictionary< string , Func<Object>>

{

{AName, () => _o.A},

{BName, () => _o.B}

};

_setters = new Dictionary< string , Action< object >>

{

{AName, v => _o.A = ( int ) v},

{BName, v => _o.B = v.ToString()},

};

}



public override int A

{

get { return ( int ) _getters[AName](); }

set { _setters[AName]( value ); }

}



public override string B

{

get { return ( string ) _getters[BName](); }

set { _setters[BName]( value ); }

}

}




* This source code was highlighted with Source Code Highlighter .
private class DictionaryDelegateTester : ObjectTester

{

private const string AName = "A" ;

private const string BName = "B" ;



private readonly Dictionary< string , Func<Object>> _getters;

private readonly Dictionary< string , Action<Object>> _setters;



private readonly StaticObject _o = new StaticObject();



public DictionaryDelegateTester()

{

_getters = new Dictionary< string , Func<Object>>

{

{AName, () => _o.A},

{BName, () => _o.B}

};

_setters = new Dictionary< string , Action< object >>

{

{AName, v => _o.A = ( int ) v},

{BName, v => _o.B = v.ToString()},

};

}



public override int A

{

get { return ( int ) _getters[AName](); }

set { _setters[AName]( value ); }

}



public override string B

{

get { return ( string ) _getters[BName](); }

set { _setters[BName]( value ); }

}

}




* This source code was highlighted with Source Code Highlighter .




Result: 00: 00: 12.3299023 (+ 85.93%)



Pros:

- completely dynamic: you can add and remove

Minuses:

- complex syntax



Dynamic object with a static container





Well, go to the most new type of 'dynamic'. For him, I immediately apply three methods. The first is when we store that static type as a “storage” (backed object) for a dynamic:



private class DynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new StaticObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .
private class DynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new StaticObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .




Result: 00: 00: 10.7193446 (+ 61.65%)



Pros:

- can be transmitted in DLR (F #)

- simple syntax

Minuses:

- and still a half times longer



Dynamic object with Expando



In fact, in order to more conveniently use dynamic-and add the type ExpandoObject . He has a lot of nice little things - he and IDictionary, and IEnumerable.



Using:

private class ExpandoDynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new ExpandoObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .
private class ExpandoDynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new ExpandoObject();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}

}




* This source code was highlighted with Source Code Highlighter .




Result: 00: 00: 09.7034082 (+ 46.3%)



Pros:

- can be transmitted in DLR (F #)

- simple syntax and additional features (enumeration)

Minuses:

- practically absent, except for an increase in speed



Dynamic object with call pickup





Another of the nice features of dynamic is the ability to intercept calls to methods and properties. We will use:

private class PureDynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new DynamicContainer();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}



#region Nested type: DynamicContainer



private class DynamicContainer : DynamicObject

{

private int _a;

private string _b;



public override bool TryGetMember(GetMemberBinder binder, out object result)

{

if (binder.Name == "A" )

{

result = _a;

return true ;

}

if (binder.Name == "B" )

{

result = _b;

return true ;

}

return base .TryGetMember(binder, out result);

}



public override bool TrySetMember(SetMemberBinder binder, object value )

{

if (binder.Name == "A" )

{

_a = ( int ) value ;

return true ;

}

if (binder.Name == "B" )

{

_b = ( string ) value ;

return true ;

}

return base .TrySetMember(binder, value );

}

}



#endregion

}




* This source code was highlighted with Source Code Highlighter .
private class PureDynamicObjectTester : ObjectTester

{

private readonly dynamic _o = new DynamicContainer();



public override int A

{

get { return _o.A; }

set { _o.A = value ; }

}



public override string B

{

get { return _o.B; }

set { _o.B = value ; }

}



#region Nested type: DynamicContainer



private class DynamicContainer : DynamicObject

{

private int _a;

private string _b;



public override bool TryGetMember(GetMemberBinder binder, out object result)

{

if (binder.Name == "A" )

{

result = _a;

return true ;

}

if (binder.Name == "B" )

{

result = _b;

return true ;

}

return base .TryGetMember(binder, out result);

}



public override bool TrySetMember(SetMemberBinder binder, object value )

{

if (binder.Name == "A" )

{

_a = ( int ) value ;

return true ;

}

if (binder.Name == "B" )

{

_b = ( string ) value ;

return true ;

}

return base .TrySetMember(binder, value );

}

}



#endregion

}




* This source code was highlighted with Source Code Highlighter .




A bit long came out.



Result: 00: 00: 11.1040041 (+ 67.45%)



Pros:

- can be transmitted in DLR (F #)

- properties may not even exist

Minuses:

- requires significantly more code



Compilation of expressions (Expressions)





This method does not use dynamic (although it can be quite easily wrapped into DynamicObject), and is most closely related to the method using reflection. In fact, he uses reflection, but only to build an expression tree and compile it.



In fact, there is an "emitting" of the IL-code. As a result, we get a huge increase in speed.



I wrote the following implementation: I made two extension methods over propertyInfo to get the getter and setter, but not in the form of MethodInfo, as for ordinary reflection, but in the form of Func (getter) and Action (setter). In fact, my getter looks something like " o => ((T)o).{name} " and the setter is " (o, v) => ((T)o).{name} = v ".



To make these helper methods easy to read, I put each of the expression nodes in separate variables:

public static Func< object , T> GetValueGetter<T>( this PropertyInfo propertyInfo)

{

var instance = Expression.Parameter( typeof (Object), "i" );

var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);

var property = Expression.Property(castedInstance, propertyInfo);

var convert = Expression. Convert (property, typeof (T));

var expression = Expression.Lambda(convert, instance);

return (Func< object , T>)expression.Compile();

}



public static Action< object ,T> GetValueSetter<T>( this PropertyInfo propertyInfo)

{

var instance = Expression.Parameter( typeof (Object), "i" );

var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);

var argument = Expression.Parameter( typeof (T), "a" );

var setterCall = Expression.Call(

castedInstance,

propertyInfo.GetSetMethod(),

Expression. Convert (argument, propertyInfo.PropertyType));

return (Action< object ,T>)Expression.Lambda(setterCall, instance, argument)

.Compile();

}




* This source code was highlighted with Source Code Highlighter .
public static Func< object , T> GetValueGetter<T>( this PropertyInfo propertyInfo)

{

var instance = Expression.Parameter( typeof (Object), "i" );

var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);

var property = Expression.Property(castedInstance, propertyInfo);

var convert = Expression. Convert (property, typeof (T));

var expression = Expression.Lambda(convert, instance);

return (Func< object , T>)expression.Compile();

}



public static Action< object ,T> GetValueSetter<T>( this PropertyInfo propertyInfo)

{

var instance = Expression.Parameter( typeof (Object), "i" );

var castedInstance = Expression.ConvertChecked(instance, propertyInfo.DeclaringType);

var argument = Expression.Parameter( typeof (T), "a" );

var setterCall = Expression.Call(

castedInstance,

propertyInfo.GetSetMethod(),

Expression. Convert (argument, propertyInfo.PropertyType));

return (Action< object ,T>)Expression.Lambda(setterCall, instance, argument)

.Compile();

}




* This source code was highlighted with Source Code Highlighter .




As a result, the testing class:

private class ExpressionObjectTester : ObjectTester

{

private static readonly Func< object , int > AGetter =

typeof (StaticObject).GetProperty( "A" ).GetValueGetter< int >();



private static readonly Func< object , string > BGetter =

typeof (StaticObject).GetProperty( "B" ).GetValueGetter< string >();



private static readonly Action< object , int > ASetter =

typeof (StaticObject).GetProperty( "A" ).GetValueSetter< int >();



private static readonly Action< object , string > BSetter =

typeof (StaticObject).GetProperty( "B" ).GetValueSetter< string >();



private readonly StaticObject _o = new StaticObject();



public override int A

{

get { return AGetter(_o); }

set { ASetter(_o, value ); }

}



public override string B

{

get { return BGetter(_o); }

set { BSetter(_o, value ); }

}

}




* This source code was highlighted with Source Code Highlighter .
private class ExpressionObjectTester : ObjectTester

{

private static readonly Func< object , int > AGetter =

typeof (StaticObject).GetProperty( "A" ).GetValueGetter< int >();



private static readonly Func< object , string > BGetter =

typeof (StaticObject).GetProperty( "B" ).GetValueGetter< string >();



private static readonly Action< object , int > ASetter =

typeof (StaticObject).GetProperty( "A" ).GetValueSetter< int >();



private static readonly Action< object , string > BSetter =

typeof (StaticObject).GetProperty( "B" ).GetValueSetter< string >();



private readonly StaticObject _o = new StaticObject();



public override int A

{

get { return AGetter(_o); }

set { ASetter(_o, value ); }

}



public override string B

{

get { return BGetter(_o); }

set { BSetter(_o, value ); }

}

}




* This source code was highlighted with Source Code Highlighter .




Result : 00: 00: 08.5675928 (+ 29.20%)



Pros:

- amazing speed

Minuses

- slightly complicated implementation



Summary Table and Conclusions





Method nameTimeTime additive
Reflection00: 01: 33.60771391311.59%
Dictionary of meanings00: 00: 10.851651863.64%
Delegates dictionary00: 00: 12.329902385.93%
Dynamic object with a static container00: 00: 10.719344661.65%
Dynamic object with Expando00: 00: 09.703408246.33%
Dynamic object with call pickup00: 00: 11.104004167.45%
Compilation of expressions (Expressions)00: 00: 08.567592829.20%




The conclusions are pretty simple:

- If you need speed - use Expression. They are not so terrible, if you dig a little, but they give you just amazing results when compared with ordinary reflection (although even with dynamic, it is almost twice as fast on average)

- If you need to use dynamic objects (for example, in conjunction with DLR - F #, IronRuby), then it is better to use ExpandoObject. It gives excellent results.

- The implementation of dynamic s in .NET is quite effective, even when compared with a dictionary



But most importantly: Use dynamic calls only where they are really needed !



A good example is the processing of XML data with a weakly-defined structure. A bad example is the description of the algorithm. It is better to always select the necessary interfaces and already operate with concepts describing the behavior.



Do not forget - dynamic types practically negate the possibility of refactoring, the possibility of catching errors at the compilation stage, the possibility of effective optimization.



I hope this article will help those who think to take advantage of the dynamic capabilities of the language — either choose the right method or decide not to use it.



Code is available on Google Code for cloning or viewing.

Source: https://habr.com/ru/post/103558/



All Articles