Efficiently implementing generic getters and setters
I am going to tell you about how I worked on an object that can wrap any object, serialize it, deserialize it, send it to a database, all efficiently and generically. But first, let me start with the beginning: you need a way to efficiently set/get values of properties in objects of different types. That's where TypeCache comes in. I am sure not everything is perfect with the class, but hopefully it will put you on the right track.
I will start with some of the code, the class that does everything. Something will be missing, though.
(I used extension methods so that there would be no problems using a null value as the TypeCache) This class would be used something like this:
If you try to compile this, after adding all the namespaces required (System.Collections.Concurrent and System.Reflection), you will get an error, because you are missing the extension methods PropertyInfo.GetValueGetter and PropertyInfo.GetValueSetter. These should be returning Expressions that compiled get or set the value of an object. Here is the first attempt, using the normal PropertyInfo methods:
Before .Net 4.0 the only solution to do that would have been to emit IL and compile it inside the code. Actually, even in 4.0 it is the most efficient option. But given my incompetence in that direction, I will give you the (slightly) easier to understand solution. Here we sacrifice a little speed for the readability of code:
The rest of the article will be about explaining what these extension methods are doing. How fast were they? 300ms! 3.3 times faster. How did we do that?
Well, first of all let's get the New class out of the way. In this simple version it just creates instances of a type that has a parameterless constructor. In the case that I had, using simple objects to transfer information, I only needed that. What is does is create a lambda expression that represents the parameterless constructor of a type, compiles it and caches it. Even a simple thing like this is faster than Activator.GetInstance(type). Not by much, about 10%, but still. Anyway, we are only using this to delete the value of a property (by setting it to its default value), which is not really in the scope of our test. However, being a simple expression build, it shows you the things to come.
Now for the GetValueGetter and GetValueSetter methods. I will humanly read you the GetValueGetter method. You can correlate it with the code quite easily. Basically it says this:
The major difficulty, for me at least, was to grasp that there is no object (there is no spoon), but only a lambda expression that receives a parameter of type object and returns another object. The expression would be compiled and then applied on an actual instance.
And that's that. The object that does the serialization and transformation to SqlParameters and gets/sets the values of the wrapped object is more complicated and I will probably not write a blog entry about it, but think about the concept a little: an object that receives another object as the constructor and works like a dictionary. The keys and values of the dictionary are filled by the property names and values of the original object, while any change in the values of the dictionary will be set to the properties of the object. It makes it very easy to access objects generically, controlling what accessors do, but without the need for PostSharp or T4 templating, real time, programmatic. The task of serialization is taken by the ISerializable interface, which also uses a type of dictionary, meaning the object can be passed around from web services to C# code and then to SQL via SqlParameters.
I will start with some of the code, the class that does everything. Something will be missing, though.
/// <summary>
/// It caches the property setters and getters for the public properties of a type
/// </summary>
public class TypeCache
{
private static ConcurrentDictionary<Type, TypeCache> _typeCacheDict = new ConcurrentDictionary<Type, TypeCache>();
public static TypeCache Get(Type type)
{
TypeCache cache;
if (!_typeCacheDict.TryGetValue(type, out cache))
{
cache = new TypeCache(type);
_typeCacheDict[type] = cache;
}
return cache;
}
private TypeCache(Type type)
{
Type = type;
Setters = new ConcurrentDictionary<string, Action<object, object>>();
Getters = new ConcurrentDictionary<string, Func<object, object>>();
var props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
Properties = new ConcurrentDictionary<string, PropertyInfo>();
foreach (var prop in props)
{
if (prop.CanRead)
{
var objGetter = prop.GetValueGetter();
Getters[prop.Name] = objGetter.Compile();
}
if (prop.CanWrite)
{
var objSetter = prop.GetValueSetter();
Setters[prop.Name] = objSetter.Compile();
}
Properties[prop.Name] = prop;
}
}
public Type Type { get; private set; }
public ConcurrentDictionary<string, Action<object, object>> Setters { get; private set; }
public ConcurrentDictionary<string, Func<object, object>> Getters { get; private set; }
public ConcurrentDictionary<string, PropertyInfo> Properties { get; private set; }
}
public static class TypeCacheExtensions
{
/// <summary>
/// Set the value of a property by name
/// </summary>
/// <param name="cache"></param>
/// <param name="item"></param>
/// <param name="key"></param>
/// <param name="value"></param>
public static void Set<T>(this TypeCache cache, object item, string key, T value)
{
if (cache == null || item == null) return;
Action<object, object> setter;
if (!cache.Setters.TryGetValue(key, out setter)) return;
setter(item, (object)value);
}
/// <summary>
/// Get the value of a property by name
/// </summary>
/// <param name="cache"></param>
/// <param name="item"></param>
/// <param name="key"></param>
/// <returns></returns>
public static T Get<T>(this TypeCache cache, object item, string key)
{
if (cache == null || item == null) return default(T);
Func<object, object> getter;
if (!cache.Getters.TryGetValue(key, out getter)) return default(T);
return (T)getter(item);
}
/// <summary>
/// Set the value for a property to default by name
/// </summary>
/// <param name="cache"></param>
/// <param name="item"></param>
/// <param name="key"></param>
public static void Delete(this TypeCache cache, object item, string key)
{
if (cache == null || item == null) return;
Action<object, object> setter;
if (!cache.Setters.TryGetValue(key, out setter)) return;
var value = cache.Properties[key].PropertyType.GetDefaultValue();
setter(item, value);
}
/// <summary>
/// Set the values for all the public properties of a class to their default
/// </summary>
/// <param name="cache"></param>
/// <param name="item"></param>
public static void Clear(this TypeCache cache, object item)
{
if (cache == null || item == null) return;
Action<object, object> setter;
foreach (var pair in cache.Properties)
{
if (!cache.Setters.TryGetValue(pair.Key, out setter)) continue;
var value = pair.Value.PropertyType.GetDefaultValue();
setter(item, value);
}
}
}
(I used extension methods so that there would be no problems using a null value as the TypeCache) This class would be used something like this:
class Program
{
static void Main(string[] args)
{
var obj = new TestObject();
var cache = TypeCache.Get(obj.GetType());
Stopwatch sw = new Stopwatch();
sw.Start();
for (var i = 0; i < 100000; i++)
{
cache.Get<int>(obj, "TestProperty");
cache.Set<int>(obj, "TestProperty",i);
}
sw.Stop();
Console.WriteLine("Time: " + sw.Elapsed.TotalMilliseconds);
Console.ReadKey();
}
}
If you try to compile this, after adding all the namespaces required (System.Collections.Concurrent and System.Reflection), you will get an error, because you are missing the extension methods PropertyInfo.GetValueGetter and PropertyInfo.GetValueSetter. These should be returning Expressions that compiled get or set the value of an object. Here is the first attempt, using the normal PropertyInfo methods:
public static class ReflectionExtensionsWonderful thing! GetGet and GetSet :) Basically I am returning expressions that use reflection to get the getter/setter and then execute them. How long would the program with one million tries of get and set take? 1000 milliseconds. Could we improve on that?
{
public static Expression<Func<object, object>> GetValueGetter(this PropertyInfo propertyInfo)
{
var getter=propertyInfo.GetGetMethod();
return (obj) => getter.Invoke(obj, new object[] { });
}
public static Expression<Action<object, object>> GetValueSetter(this PropertyInfo propertyInfo)
{
var setter = propertyInfo.GetSetMethod();
return (obj,val) => setter.Invoke(obj, new[] { val });
}
public static object GetDefaultValue(this Type type)
{
return type.IsValueType ? Activator.CreateInstance(type) : null;
}
}
Before .Net 4.0 the only solution to do that would have been to emit IL and compile it inside the code. Actually, even in 4.0 it is the most efficient option. But given my incompetence in that direction, I will give you the (slightly) easier to understand solution. Here we sacrifice a little speed for the readability of code:
public static class ReflectionExtensions
{
/// <summary>
/// Get the expression of a value getter for a property. Compile the expression to execute or combine it with other expressions.
/// </summary>
/// <param name="propertyInfo"></param>
/// <returns></returns>
public static Expression<Func<object, object>> GetValueGetter(this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "i");
var convertObj = Expression.TypeAs(instance, propertyInfo.DeclaringType);
var property = Expression.Property(convertObj, propertyInfo);
var convert = Expression.Convert(property, typeof(object));
return (Expression<Func<object, object>>)Expression.Lambda(convert, instance);
}
/// <summary>
/// Get the expression of a value setter for a property. Compile the expression to execute or combine it with other expressions.
/// </summary>
/// <param name="propertyInfo"></param>
/// <returns></returns>
public static Expression<Action<object, object>> GetValueSetter(this PropertyInfo propertyInfo)
{
var instance = Expression.Parameter(typeof(object), "i");
var argument = Expression.Parameter(typeof(object), "a");
var convertObj = Expression.TypeAs(instance, propertyInfo.DeclaringType);
var convert = Expression.Convert(argument, propertyInfo.PropertyType);
var setterCall = Expression.Call(convertObj,propertyInfo.GetSetMethod(),convert);
return (Expression<Action<object, object>>)Expression.Lambda(setterCall, instance, argument);
}
/// <summary>
/// Get the default value of a type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static object GetDefaultValue(this Type type)
{
return type.IsValueType ? New.Instance(type) : null;
}
}
/// <summary>
/// Class used to get instances of a type
/// </summary>
public static class New
{
private static ConcurrentDictionary<Type, Func<object>> _dict = new ConcurrentDictionary<Type, Func<object>>();
public static object Instance(Type type)
{
Func<object> func;
if (!_dict.TryGetValue(type, out func))
{
func = Expression.Lambda<Func<object>>(Expression.New(type)).Compile();
_dict[type] = func;
}
return func();
}
}
The rest of the article will be about explaining what these extension methods are doing. How fast were they? 300ms! 3.3 times faster. How did we do that?
Well, first of all let's get the New class out of the way. In this simple version it just creates instances of a type that has a parameterless constructor. In the case that I had, using simple objects to transfer information, I only needed that. What is does is create a lambda expression that represents the parameterless constructor of a type, compiles it and caches it. Even a simple thing like this is faster than Activator.GetInstance(type). Not by much, about 10%, but still. Anyway, we are only using this to delete the value of a property (by setting it to its default value), which is not really in the scope of our test. However, being a simple expression build, it shows you the things to come.
Now for the GetValueGetter and GetValueSetter methods. I will humanly read you the GetValueGetter method. You can correlate it with the code quite easily. Basically it says this:
- the expression has one parameter, of type object
- we will attempt to safely convert it (as) into the declaring type (the object the property belongs to)
- we will access a property of an object of the declaring type
- which we then convert to object so that the resulting expression returns object not the specific type of the property
- transform it all into a lambda expression and return it
The major difficulty, for me at least, was to grasp that there is no object (there is no spoon), but only a lambda expression that receives a parameter of type object and returns another object. The expression would be compiled and then applied on an actual instance.
And that's that. The object that does the serialization and transformation to SqlParameters and gets/sets the values of the wrapped object is more complicated and I will probably not write a blog entry about it, but think about the concept a little: an object that receives another object as the constructor and works like a dictionary. The keys and values of the dictionary are filled by the property names and values of the original object, while any change in the values of the dictionary will be set to the properties of the object. It makes it very easy to access objects generically, controlling what accessors do, but without the need for PostSharp or T4 templating, real time, programmatic. The task of serialization is taken by the ISerializable interface, which also uses a type of dictionary, meaning the object can be passed around from web services to C# code and then to SQL via SqlParameters.
Comments
Be the first to post a comment