27 February 2014
Last month I was looking back into one of my old projects that was inspired by AutoMapper but for mapping to immutable types, rather than mapping to mutable types (that typically have a parameterless constructor and are initialised by setting individual properties)* - see Bonus provocative headline: Like AutoMapper, but 100x faster.
When I was working on it, I had a fairly good idea of what scenarios I wanted to use it in and had no intention of trying to replicate the entire range of functionality that AutoMapper supports. However, something I was particularly taken with was the recently(ish) added support to AutoMapper for LINQ auto-projections.
* Note from the future: There was a time when AutoMapper didn't have good support for mapping to immutable types, it wouldn't apply automatic its name / type matching logic to the case where property values are read from the source type and used to provide constructor arguments on the destination type (and it was to fill that feature gap that I started writing the Compilable Type Converter). However, that situation changed at some point and now AutoMapper does have good support for mapping to immutable types - though I wasn't able to track down from the release notes when precisely that was.
Projections are when data is retrieved from an IQueryable source and mapped onto either a named class or an anonymous type - eg.
using (var context = new AlbumDataEntities())
{
var albums = context.Albums.Select(album => new {
Key = album.AlbumKey,
Name = album.Name
});
The clever thing about IQueryable and these projections is that the data source (Entity Framework, for example) can construct a query to retrieve only the data required. Without projections, the database querying and the mapping are completely separate steps and so it is very common for the ORM to have to retrieve much more data than is strictly necessary (and often to introduce the dreaded N+1 SELECT problem). Projections allows the mapping to directly affect how the database query is constructed so that precisely the right amount of data may be requested (and all in one query, not in N+1).
The code shown above could be referred to as a manual projection (where the properties - "Key" and "Name" in that example - are explicitly mapped). Auto-projection is the utilisation of a mapping library to automatically create this projection / mapping. This means that you don't have to write the boring "Key = album.AlbumKey, Name = album.Name" code for each property in a mapping. In essence, something like:
using (var context = new AlbumDataEntities())
{
var albums = context.Albums.MapTo<ImmutableAlbum>();
When you've only got a couple of properties (like Key and Name in the earlier code), it's not too bad but it's still the sort of manual work that gets boring (and error prone) very quickly.
Allow me to briefly go off on a tangent..
The IEnumerable type in .net allows for lazy initialisation such that data is only processed as it is requested. For a really simple example, the following code starts off with a data "source" that will generate up to ten million objects, returned as an enumerable set. On this we call Take(5), which returns another enumerable set that will only enumerate the first five items. Until ToArray is called, none of the data is actually delivered and none of the objects are created. Even when ToArray is called, only five of the source objects are actually initialised as that is how many are actually required - the remaining 9,999,995 objects that could have been created are not since they are not required (this is lazy evaluation in action).
var fiveItemsFromLargeEnumerableRange =
Enumerable.Range(1, 10000000).Select(i => new { Name = "Item" + i })
.Take(5)
.ToArray();
IEnumerable can be thought to operate on in-memory data sets. The data itself may not originate from an in-memory source. Above it does, but the source could also be something delivering lines from a log file or rows from a database. Each entry in the data, though, is fully loaded when required and then processed in memory. Although each entity is only loaded when required, if the loading of each entity is expensive and only a subset of its data is required for the operation at hand, then even this form of lazy evaluation can become a burden. IEnumerable sets do not inherently expose a way to "partially load" the entities.
It's worth noting at this point that many ORMs (including Entity Framework) support "lazy loading" of data for child properties to try to address this very point; the data for the properties of the returned objects is not loaded until the properties are accessed. At this point the database (or whatever data source is being used) is hit again to retrieve this information. The downside to this is that the data that is accessed may require multiple database hits for each entity when only a single query may have been required if "eagerly" loading all of the data for the entity. But if "eager loading" is used and only a subset of the data is required then too much data was being pulled down!
IQueryable sets have similar intentions to IEnumerable but a different approach, they are more tightly tied to the data source. Where IEnumerable sets may be considered to be in-memory (for each entity), IQueryable sets are all prepared in the data source and filtering may be applied there to prevent too much data from being sent.
To illustrate with an example, say we have data about albums. There's an Albums table with AlbumKey, Name, Year and ArtistKey fields. There's a Tracks table with TrackKey, AlbumKey, TrackNumber and Name fields. And there's an Artists table with fields ArtistKey and Name.
If I point Entity Framework at this then it will generate a model to dip into all this data. The simplest retrieval is probably for all Album names -
using (var context = new AlbumDataEntities())
{
var allAlbumNames = context.Albums
.Select(album => album.Name)
.OrderBy(name => name);
// Calling ToString on an Entity Framework IQueryable pointing at a SQL database
// returns the SQL that will be executed to perform the query
var allAlbumNamesQuery = allAlbumNames.ToString();
var allAlbumNamesResults = allAlbumNames.ToArray();
Console.WriteLine("Query:");
Console.WriteLine(allAlbumNamesQuery);
Console.WriteLine();
Console.WriteLine("Results:");
Console.WriteLine(string.Join(Environment.NewLine, allAlbumNamesResults));
}
This shows that the SQL executed was
SELECT
[Extent1].[Name] AS [Name]
FROM [dbo].[Albums] AS [Extent1]
ORDER BY [Extent1].[Name] ASC
Which is pretty much what you would hope for.. but clever when you think about it. It's done some analysis of the request we've described and realised that it only needs to consider one particular column from that one table, even though it's all configured to potentially do so much more.
If instead we request
var allCombinedAlbumAndTrackNames = context.Albums
.SelectMany(album => album.Tracks.Select(track => new {
AlbumName = album.Name,
TrackName = track.Name,
TrackNumber = track.TrackNumber
}))
.OrderBy(combinedEntry => combinedEntry.AlbumName)
.ThenBy(combinedEntry => combinedEntry.TrackNumber)
.Select(combinedEntry => combinedEntry.AlbumName + "/" + combinedEntry.TrackName);
then the following SQL is executed:
SELECT
[Project1].[C1] AS [C1]
FROM ( SELECT
[Extent1].[Name] + N'/' + [Extent2].[Name] AS [C1],
[Extent1].[Name] AS [Name],
[Extent2].[TrackNumber] AS [TrackNumber]
FROM [dbo].[Albums] AS [Extent1]
INNER JOIN [dbo].[Tracks] AS [Extent2]
ON [Extent1].[AlbumKey] = [Extent2].[AlbumKey]
) AS [Project1]
ORDER BY [Project1].[Name] ASC, [Project1].[TrackNumber] ASC
This was not such a simple translation to make - this query got mapped into an interim anonymous type, there are multiple sorts and the final values are constructed by concatenating two of the fields in the interim type. Nonetheless, the SQL that was generated was very efficient and a good reflection of the data that was requested.
One more, for fun..
var namesOfTheFiveAlbumsWithTheGreatestNumberOfTracks = context.Albums
.OrderByDescending(album => album.Tracks.Count())
.Select(album => album.Name)
.Take(5);
results in:
SELECT TOP (5)
[Project1].[Name] AS [Name]
FROM ( SELECT
[Extent1].[Name] AS [Name],
(SELECT
COUNT(1) AS [A1]
FROM [dbo].[Tracks] AS [Extent2]
WHERE [Extent1].[AlbumKey] = [Extent2].[AlbumKey]) AS [C1]
FROM [dbo].[Albums] AS [Extent1]
) AS [Project1]
ORDER BY [Project1].[C1] DESC
This not only performed an aggregate operation (by considering the number of Tracks per Album) but also incorporated the "Take(5)" into the query. This is an example of how a request may be translated into something handled by the data source that ensures that it can deliver the bare minimum data; if the "Take(5)" call had not been translated into part of the query then more rows might have been returned than we cared about. (If the "Take(5)" call could not have been translated into part of the database query then the first five results could have been isolated by a similar "in-memory" operation to that illustrated by the 1,000,000 item IEnumerable example earlier, but it wouldn't be as efficient to do so since the additional rows would have had to have been delivered from the database and then filtered out.. which would have been wasteful).
These examples demonstrate some of the ways in which use of IQueryable can ensure that the minimum amount of data required is transmitted from the data source. None of them even touch the Artists table since none of the requests asked for Artist data! The IQueryable implementation is what performs this magic, whether that be provided by Entity Framework, NHibernate, SubSonic or whatever - it is responsible for translating expressions into SQL (or whatever language the backing data source uses; it could be another SQL-like database or it could be a document database such as MongoDB).
In the above examples, ToArray() was used to force the retrieval / evaluation of the information. This could just as easily have been a call to ToList() or been a loop that enumerated through the data.
With IEnumerable sets, the source data is not run through until it is explicitly enumerated. With IQueryable, the data is not retrieved from the source until the IQueryable reference is treated as an IEnumerable. This is possible since IQueryable implements IEnumerable and so any method that can operate on IEnumerable may also operate on IQueryable. But what's important here is that as soon as this is done, the IQueryable reference will then "become" an IEnumerable reference and the underlying data request will have been made in order for this to happen.
The clever thing above, where the "Take(5)" method resulted in "SELECT TOP (5)" becoming part of the SQL query, comes about as LINQ has a load of extension methods for operating against IQueryable as well IEnumerable - so as well as
public static IEnumerable<TSource> Take<TSource>(
this IEnumerable<TSource> source,
int count
);
there is also
public static IQueryable<TSource> Take<TSource>(
this IQueryable<TSource> source,
int count
);
The latter ensures that an IQueryable remains as an IQueryable and so postpones its evaluation.
By the way, I am finally approaching the point of this post now, so bear with me! :)
The LINQ "Select" extension method similarly has alternative method signatures. The more common version is
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
);
where a particular transformation is performed upon each item in a IEnumerable set.
But there is a corresponding signature
public static IQueryable<TResult> Select<TSource, TResult>(
this IQueryable<TSource> source,
Expression<Func<TSource, TResult>> selector
);
where an Expression will be translated by the IQueryable provider into the language of the underlying data source (but since the IQueryable reference remains as an IQueryable this translation won't happen yet).
The difference between Expression<Func<TSource, TResult>> and Func<TSource, TResult> is subtle but important. The compiler is clever enough that often you needn't even be aware that you're passing an Expression. Above we were performing various manipulations (such as wrapping data up in anonymous types and combining fields with string concatenation) without having to think about it. But if we tried to do something like
var nextId = 0;
var allAlbumNamesWithExternallGeneratedIds = context.Albums
.Select(album => new { Name = album.Name, Id = ++nextId })
.OrderBy(name => name);
we'd get a compiler error
An expression tree may not contain an assignment operator
So, unfortunately, it's not just any old lambda (aka anonymous function) that can be translated into an Expression. A different problem is encountered if we attempt to use AutoMapper to process the data - eg.
Mapper.CreateMap<Album, AlbumStub>();
var allAlbumKeyAndNames = context.Albums
.Select(album => Mapper.Map<Album, AlbumStub>(album))
.OrderBy(name => name);
where the target class is
public class AlbumStub
{
public int AlbumKey { get; set; }
public string Name { get; set; }
}
This will result in a NotSupportedException being raised by Entity Framework with the following message:
LINQ to Entities does not recognize the method 'AlbumStub Map[Album,AlbumStub](ProjectionExamples.AlbumStub)' method, and this method cannot be translated into a store expression.
What has happened here is that the compiler has recognised
album => Mapper.Map<Album, AlbumStub>(album)
as a valid Expression but when the query provider has tried to work its magic and translate it into SQL, it doesn't know what to do.
We could try a different approach and call:
Mapper.CreateMap<Album, AlbumStub>();
var allAlbumKeyAndNames = context.Albums
.Select(Mapper.Map<Album, AlbumStub>)
.OrderBy(name => name);
But here the Select method that has been called is the Select method that works against IEnumerable and so all of the data in the context.Albums object graph has been evaluated. Even though we only want the Album Keys and Names, all of the Album, Track and Artist data has been retrieved. At the point at which the IQueryable was forced into operating as an IEnumerable it had to be evaluated, and the provider is given no way way of knowing that only the Album Keys and Names are required. What a waste!
(Incidentally, exactly the same problem was being exhibited by my "Compiler Type Converter" code, this isn't something particular to AutoMapper).
But back in February 2011, the author of AutoMapper wrote an article talking about this and how he'd been doing some work to improve the situation (Autoprojecting LINQ queries). I believe that it became a standard part of the library in the August 2013 3.0 release (according to the GitHub Release Notes).
The way it works is by adding some extension methods for IQueryable that work with AutoMapper. The above example now becomes:
Mapper.CreateMap<Album, AlbumStub>();
var allAlbumKeyAndNames = context.Albums
.OrderBy(name => name);
.Project().To<AlbumStub>();
The ".Project().To<AlbumStub>()" converts the IQueryable set into an IEnumerable but it does so in such a manner that only the minimum data is requested from the data source. So in this example, there will be no joins to the Tracks or Artists tables, nor will the ArtistKey field of the Album table even be mentioned in the underlying query! The "OrderBy" call is moved up so that it operates against the IQueryable and can be performed by SQL rather than retrieving the data from the db and having to sort it in-memory (which is what would happen if OrderBy was called after Project..To since it would be operating against an IEnumerable reference rather than an IQueryable).
There are some limitations to the projections that can be performed (which are documented in the AutoMapper GitHub wiki page Queryable Extensions). One problem that I found early on is that, while with IEnumerable mappings you could map to an immutable type such as
public class ImmutableAlbumStub
{
public ImmutableAlbumStub(int albumKey, string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Null/blank name specified");
AlbumKey = albumKey;
Name = name;
}
public int AlbumKey { get; private set; }
public string Name { get; private set; }
}
by using
Mapper.CreateMap<Album, ImmutableAlbumStub>()
.ConstructUsing(a => new ImmutableAlbumStub(a.AlbumKey, a.Name));
if you attempt this using this mapping with Project..To results you'll receive an ArgumentException with the message
'ProjectionExamples.ImmutableAlbumStub' does not have a default constructor
Hmm. Bummer.
But, on the whole, I thought that this general "autoprojecting" thing was an awesome idea! And one that I wanted to steal (er.. I mean incorporate into my own code :)
At its core, the problem is that we need to be able to provide Expression-based type converters that we can use with the IQueryable-based extension methods. Being able to do this will allow the IQueryable provider to analyse the Expressions and retrieve the bare minimum data required to satisfy the operation. I figured that this would be a walk in the park since the ICompilableTypeConverter is all about this - that's what enables its conversions to be compiled and be so fast!
Unfortunately, the very idea of analysing arbitrary expressions and translating them into SQL (or whatever) is a complex matter and, since this translation is handled by the query provider, it may vary from one provider to another. So far I've only tested this with Entity Framework and it's Entity Framework's limitations that I've encountered and worked with / around.
The first problem is to do with the handling of null values. If we continue with the album data model and imagine that it's actually optional to assign an artist to an album, then in such a case there would be a null ArtistKey on the Album database record. This would mean that the Artist property on the corresponding instance of the Entity-Framework-generated class would also be null. But if I try to map this onto another type structure such as with
var albumsWithArtists = context.Albums
.Select(a => new {
Name = a.Name,
Artist = (a.Artist == null) ? null : new { Name = a.Artist.Name }
});
then we get another NotSupportedException as soon as the data is evaluated, this time with the message
Unable to create a null constant value of type 'Anonymous type'. Only entity types, enumeration types or primitive types are supported in this context.
Unfortunately, this is - broadly speaking - what happens in the type converters that my code generates. And something similar happens with properties that are enumerable. The Tracks property, for example:
var albumsWithTrackNames = context.Albums
.Select(a => new {
Name = a.Name,
TrackNames = (a.Tracks == null) ? null : a.Tracks.Select(t => t.Name)
});
Cannot compare elements of type 'System.Collections.Generic.ICollection`1[[ProjectionExamples.Album, ProjectionExamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Only primitive types, enumeration types and entity types are supported.
This second one doesn't seem all that unreasonable from Entity Framework's side; if there are no tracks associated with an album then an empty Tracks list would be recorded against an Album instance, not a null Tracks reference. Unfortunately my conversion methods don't assume this and just performing this null checking makes Entity Framework throw its toys out of the pram. We can't even check for nulls and then resort to a default empty array -
var albumsWithTrackNames = context.Albums
.Select(a => new {
Name = a.Name,
TrackNames = (a.Tracks == null) ? new string[0] : a.Tracks.Select(t => t.Name)
});
as that will result in the same error.
And then, the killer:
var albumsAsImmutableTypes = context.Albums
.Select(a => new ImmutableAlbum(a.AlbumKey, a.Name));
This results in a NotSupportedException with the message
Only parameterless constructors and initializers are supported in LINQ to Entities.
Oh dear.
Soooooo...
The approach I took to address this was two-fold. First, assume that all lists will be empty if there is no data for them and so assume that lists will never be null. Second, perform two mappings for each translation. Firstly to interim objects that have only the required properties, this is done while dealing with IQueryable data. And then map these interim types to the real destination objects, this is done after pushing the interim results into an IEnumerable set so that the limitations of the query provider no longer apply. The interim objects all have an "is-initialised" property on them so that if the source object is null then it can be mapped to an interim object with its "is-initialised" flag set to false, otherwise the flag will be set to true. When the interim types are mapped to the destination types, instances with "is-initialised" set to false will be mapped to null references.
This means that only the minimum required data will be retrieved but that the data may be mapped to immutable objects and that Entity Framework's awkward behaviour around nulls can be side-stepped. It's a bit like an automated version of
var albumsAsImmutableTypes = context.Albums
.Select(a => (a == null) ? new { AlbumKey = a.AlbumKey, Name = a.Name })
.AsEnumerable()
.Select(a => new ImmutableAlbumStub(a.AlbumKey, a.Name));
but without having to write that interim mapping by hand.
When building the mappings, first an "ideal mapping" is generated from the source types (the Entity Framework types) to the destination types (the ImmutableAlbumStub). This will never be used directly but performing this work reveals what property mappings are required and allows the interim types to be constructed to expose only the minimum required data.
Since there is an overhead to performing this work (when not dealing with IQueryable data the "ideal mapping" is fine to use and none of this extra work is required) and since there are some tweaks to behaviour (such as the assumption that enumerable sets will never be null), I created a separate static class to use, the ProjectionConverter. It works as follows (this example includes a mapping of nested types so that it's not as simple as the album "stub" example above):
ProjectionConverter.CreateMap<Track, ImmutableTrack>();
ProjectionConverter.CreateMap<Album, ImmutableAlbum>();
using (var context = new ProjectionTestEntities1())
{
var albumsWithTrackListings = context.Albums
.Project().To<ImmutableAlbum>();
The target classes are:
public class ImmutableAlbum
{
public ImmutableAlbum(string name, IEnumerable<ImmutableTrack> tracks)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Null/blank name specified");
if (tracks == null)
throw new ArgumentNullException("tracks");
Name = name;
Tracks = tracks.ToList().AsReadOnly();
if (Tracks.Any(t => t == null))
throw new ArgumentException("Null reference encountered in tracks set");
}
/// <summary>
/// This will never be null or blank
/// </summary>
public string Name { get; private set; }
/// <summary>
/// This will never be null nor contain any null references
/// </summary>
public IEnumerable<ImmutableTrack> Tracks { get; private set; }
}
public class ImmutableTrack
{
public ImmutableTrack(int number, string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Null/blank name specified");
if (number < 1)
throw new ArgumentOutOfRangeException("number must be greater than zero");
Number = number;
Name = name;
}
/// <summary>
/// This will always be greater than zero
/// </summary>
public int Number { get; private set; }
/// <summary>
/// This will never be null or blank
/// </summary>
public string Name { get; private set; }
}
The Project and To methods are IQueryable extensions in my "Compilable Type Converter" project, not the ones in AutoMapper. All of the same options that I talked about last time are available for the projections (so some or all of the target types may be initialised by-property-setter instead of by-constructor), the big difference is that the ProjectionConverter must be used instead of the regular Converter.
And with that, I'm done! IQueryable-based mappings to immutable types are now possible in a simple and efficient manner!
The interim types that are generated by the code are created dynamically. The ProjectionConverter maintains a dictionary of generated types so if a mapping is required that requires an interim type with the exact same set of properties as an interim type that has been used before, then a new instance of that type will be created, rather than having to build an entirely new type and then creating an instance of that. Obviously, the first time that any mapping is generated, some new types will have to be built.
Since the C# compiler uses anonymous types, I'd wondered if there was some .net mechanism to generate these types on-the-fly. But after doing some testing (by compiling some code and investigating the output using ildasm), it would seem that the compiler analyses the source code at compile time and bakes in classes to the IL that may be used for all of the required anonymous types. So that was a no-go.
But a few years ago I'd been experimenting with a similar topic, so I was able to dust off and repurpose some old code. Which was convenient! All that I required was for a new type to be created with a particular set of non-indexed read-and-write properties. It doesn't need any methods, fields or events, it doesn't need any static properties, it doesn't need any read-only or write-only fields. It just requires a simple set of gettable/settable instance properties with particular names and types. I used the following to achieve this:
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace CompilableTypeConverter.QueryableExtensions.ProjectionConverterHelpers
{
public class AnonymousTypeCreator
{
public static AnonymousTypeCreator DefaultInstance
= new AnonymousTypeCreator("DefaultAnonymousTypeCreatorAssembly");
private readonly ModuleBuilder _moduleBuilder;
public AnonymousTypeCreator(string assemblyName)
{
if (string.IsNullOrWhiteSpace(assemblyName))
throw new ArgumentException("Null/blank assemblyName specified");
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(
new AssemblyName(assemblyName),
AssemblyBuilderAccess.Run
);
_moduleBuilder = assemblyBuilder.DefineDynamicModule(
assemblyBuilder.GetName().Name,
false // emitSymbolInfo (not required here)
);
}
public Type Get(AnonymousTypePropertyInfoSet properties)
{
if (properties == null)
throw new ArgumentNullException("properties");
var typeName = "<>AnonymousType-" + Guid.NewGuid().ToString("N");
var typeBuilder = _moduleBuilder.DefineType(
typeName,
TypeAttributes.Public
| TypeAttributes.Class
| TypeAttributes.AutoClass
| TypeAttributes.AnsiClass
| TypeAttributes.BeforeFieldInit
| TypeAttributes.AutoLayout
);
var ctorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes // constructor parameters
);
var ilCtor = ctorBuilder.GetILGenerator();
ilCtor.Emit(OpCodes.Ldarg_0);
ilCtor.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(Type.EmptyTypes));
ilCtor.Emit(OpCodes.Ret);
foreach (var property in properties)
{
// Prepare the property we'll add get and/or set accessors to
var propBuilder = typeBuilder.DefineProperty(
property.Name,
PropertyAttributes.None,
property.PropertyType,
Type.EmptyTypes
);
var backingField = typeBuilder.DefineField(
property.Name,
property.PropertyType,
FieldAttributes.Private
);
// Define get method
var getFuncBuilder = typeBuilder.DefineMethod(
"get_" + property.Name,
MethodAttributes.Public
| MethodAttributes.HideBySig
| MethodAttributes.NewSlot
| MethodAttributes.SpecialName
| MethodAttributes.Virtual
| MethodAttributes.Final,
property.PropertyType,
Type.EmptyTypes
);
var ilGetFunc = getFuncBuilder.GetILGenerator();
ilGetFunc.Emit(OpCodes.Ldarg_0);
ilGetFunc.Emit(OpCodes.Ldfld, backingField);
ilGetFunc.Emit(OpCodes.Ret);
propBuilder.SetGetMethod(getFuncBuilder);
// Define set method
var setFuncBuilder = typeBuilder.DefineMethod(
"set_" + property.Name,
MethodAttributes.Public
| MethodAttributes.HideBySig
| MethodAttributes.SpecialName
| MethodAttributes.Virtual,
null,
new Type[] { property.PropertyType }
);
var ilSetFunc = setFuncBuilder.GetILGenerator();
ilSetFunc.Emit(OpCodes.Ldarg_0);
ilSetFunc.Emit(OpCodes.Ldarg_1);
ilSetFunc.Emit(OpCodes.Stfld, backingField);
ilSetFunc.Emit(OpCodes.Ret);
propBuilder.SetSetMethod(setFuncBuilder);
}
return typeBuilder.CreateType();
}
private static MethodInfo MethodInfoInvokeMember = typeof(Type).GetMethod(
"InvokeMember",
new[] {
typeof(string),
typeof(BindingFlags),
typeof(Binder),
typeof(object),
typeof(object[])
}
);
}
}
The AnonymousTypePropertyInfoSet data that is used to generate new classes is just a set of PropertyInfo instances that don't have the same property name used for multiple different property types and that ensures that none of the properties are indexed. It also overrides the Equals and GetHashCode method so that it can be used as a key in a dictionary of interim types to prevent creating more types that necessary. In essence, really it's an IEnumerable<PropertyInfo> with a few bells and whistles.
(These files can be found in the Bitbucket project at AnonymousTypeCreator.cs and AnonymousTypePropertyInfoSet.cs while the dynamic type creation is required by the PropertyConverter.cs).
And on that note, I really am done!
Posted at 23:12
22 January 2014
I overheard someone at work bemoaning the fact that StructureMap doesn't seem to support optional constructor arguments (which, having a quick scout around the internet, does indeed seem to be the case, though there are solutions out there such as Teaching StructureMap About C# 4.0 Optional Parameters and Default Values).
This put me in mind of the "Compilable Type Converter" project I wrote a couple of years ago. It started off life as a way to try to easily extend AutoMapper to apply all of its cleverness to constructor arguments as well as properties. So instead of using the properties on one object to populate the properties of another object, it would call a constructor on the destination class and pass in values taken from properties on the source object. (AutoMapper allows constructors to be used for creating new instances, using the "ConvertUsing" method, but it doesn't do its magic with name mappings and type conversions*).
* Note from the future: There was a time when AutoMapper didn't have good support for mapping to immutable types, it wouldn't apply automatic its name / type matching logic to the case where property values are read from the source type and used to provide constructor arguments on the destination type (and it was to fill that feature gap that I started writing the Compilable Type Converter). However, that situation changed at some point and now AutoMapper does have good support for mapping to immutable types - though I wasn't able to track down from the release notes when precisely that was.
It then grew to generate conversion LINQ Expressions, which were compiled for performance. And from there it became a standalone component that could perform mappings without AutoMapper at all! It could still be used with AutoMapper since the converters it generated could be used in "ConvertUsing" calls but the property-to-constructor-argument mappings would be created automatically instead of manually. And if non-compilable type converters (not compiled to LINQ Expression and so functional but slower) were being generated with my project, there were classes to utilise AutoMapper to help perform the type conversions.
The last thing I had done to it was add in support so that it could generate compiled converters that would populate the destination object using property setters (like AutoMapper does) instead of by-constructor.
I wrote a couple of posts about this a long time ago, but they were early posts and they weren't written all that well, so I'm embarrassed to link to them here. :)
Anyway.. the point is, I was fairly confident that the Compilable Type Converter also did not support optional constructor arguments. And I didn't actually know how optional constructor arguments would look in the reflected information (the converter uses reflection to analyses the source and destination types and decide how to perform the conversion, but then generates a LINQ Expression to do the work which should have performance comparable to custom hand-written conversion code) so it seemed like a good opportunity to brush off an old project and have a bit of fun with it!
This is the easy bit. I hadn't known how easy until I looked into it, but very easy.
Say there is a class
public class TypeWithOptionalConstructorArguments
{
public TypeWithOptionalConstructorArguments(string name, int number = 1)
{
// Do initialisation work..
}
// Have the rest of the class here..
}
then to determine that the number argument is optional, we interrogate information about the constructor -
var constructorParameters = typeof(TypeWithOptionalConstructorArguments)
.GetConstructor(new[] { typeof(string), typeof(int) })
.GetParameters();
var numberParameter = constructorParameters[1];
var numberParameterType = numberParameter.ParameterType;
var isNumberParameterOptional = numberParameter.IsOptional;
var numberParameterDefaultValue = numberParameter.DefaultValue;
Here we find that numberParameterType is int, isNumberParameterOptional is true and numberParameterDefaultValue is 1. If we considered the first parameter then IsOptional would be false.
Before I give a quick run-down of how I made my code "optional-constructor-argument aware", I'll go quickly through the core concepts it uses when trying to generate a conversion.
There are Property Getters which will take a value from a property on the source type in order to satisfy a value required to generate the destination type (this value may be a constructor argument or a property, depending upon whether a by-constructor or by-property-setter conversion is desired). Property Getters come in several varieties; there is one that will map a source value if the source value's type may be assigned directly to the destination type (ie. the source value matches the destination value or inherits from it / implements it). There is one that will map enum values - from one enum to another, according to a naming convention (this convention is determined by a Name Matcher, see below). There is another one that will perform one specific type translation, so if a converter is generated for mapping between SNested and DNested then a new Property Getter may be created that will help convert type SParent to DParent if SParent has a property with type SNested that needs to be mapped to a property on DParent with type DNested. There's another that's very similar but for enumerable types, so given an SNested -> DNested converter it can help map SParent to DParent if SParent has a property of type IEnumerable<SNested> and DParent has a property of type IEnumerable<DNested>.
Property Getters are created by Property Getter Factories. When a conversion request is being analysed, the Property Getter Factories will be asked "can you perform a mapping from Src to Dest for the property on Dest named Prop?" (the property on Dest may be an actual property or it may be a constructor argument). The factory will look at all of the properties on the Src type and see which, if any, it would map onto Prop based upon the source property's name and type. The type matching depends upon what sort of Property Getter the factory can create (whether that be an assignable-to getter, an enum-translating getter, etc.. all of the options I just described above) and what name matching approach it will use. The name matching depends upon the Name Matcher that was provided to the factory at instantiation.
Name Matchers simply answer the question "are these property/argument names equivalent?", the basic implementation in the project is the CaseInsensitiveSkipUnderscoreNameMatcher. This ignores underscores and case when comparing names, so "Title" and "title" and considered to be the same, as are "Person_Name" and "personName".
Finally, when a by-constructor conversion is being generated, there may be multiple constructors which may be satisfied (ie. all of their constructor arguments may be provided with values from the source object's properties). In this case, a decision will need to be made as to which constructor to use. For this, there is a Constructor Prioritiser. Each may-be-satisfied-constructor is represented by the fully-generated converter that would use that constructor. The prioritiser must then pick one to be used as the converter that should be used for that translation.
The only Constructor Prioritiser implementation that I currently have is an ArgsLengthTypeConverterPrioritiser. This simply picks the constructor which has the most arguments, the thinking being that this must be the constructor that uses the most data from the source type and so will result in the best-populated destination instance possible.
However, if there are two constructors, one with four compulsory arguments and one with five arguments total, but two of them optional, then the five-argument constructor may no longer be the best bet. If a conversion is available that explicitly populates those five values with data from the source object, then this is probably still the best match. But if the only conversion that uses that five-argument constructor is actually relying on the default values for those two optional arguments then it's only actually populating three constructor arguments from the source data, so surely the four-argument constructor is better!
So I have a CompilableTypeConverterByConstructorFactory. This has a method Get<TSource, TDest>() which will try return an ICompilableTypeConverter<TSource, TDest> that maps from TSource to TDest. If it can't create such a type converter then it will throw an exception.
The particular implementation of ICompilableTypeConverter<TSource, TDest> returned from this class will be a CompilableTypeConverterByConstructor<TSource, TDest>.
This class previously required a ConstructorInfo and a set of Property Getters for each argument in that constructor. The factory's job was to select the best ConstructorInfo and provide those Property Getters from the Property Getter Factories that it had access to. The constructor of the CompilableTypeConverterByConstructor<TSource, TDest> class would do some validation to ensure that the number of Property Getters matched the number of constructor arguments for the specified constructor, and that the types returned by the Property Getters matched the constructor's arguments types.
The change I made was for the CompilableTypeConverterByConstructor<TSource, TDest> to also take a ICompilableConstructorDefaultValuePropertyGetter set - Property Getters which are associated with a particular constructor argument which has a default value, and which just return this default value when a value is requested.
These Default Value Property Getters would only be specified by the Type Converter Factory if there was no Property Getter that could otherwise provide that constructor argument with a value - if it's possible to get data from the source object for a constructor argument then there's no point using the argument's default value!
The benefit of providing two distinct sets of Property Getters (those relying upon default values and those not) to the CompilableTypeConverterByConstructor<TSource, TDest> is that it was possible to add another public property to it; the NumberOfConstructorArgumentsMatchedWithNonDefaultValues (this is the total number of arguments that the target constructor has minus the number of Default Value Property Getters). And the benefit of this is that it allows for a Constructor Prioritiser to consider the number of constructor arguments that were populated with data from the source object, as opposed to the total number of constructor arguments fulfilled, regardless of how many actually had to fall back on to using default values. Which addresses the problem I outlined in the section above.
While I was making these changes and experimenting with various scenarios (trying to re-familiarise myself with exactly how everything worked) I found it interesting to note how some I've changed some coding conventions over the years. Particularly, I disliked a method on the ITypeConverterFactory interface -
/// <summary>
/// This will return null if unable to generate the specified converter
/// </summary>
ITypeConverter<TSource, TDest> Get<TSource, TDest>();
From some sides, this doesn't sound all that bad. And it's not uncommon to find code out there that does the same sort of thing; try to get the requested value and return null if unable to.
As a rule, though, I don't like this at all. I prefer to avoid nulls wherever humanly possible and explicitly indicate the possibility of their presence where they must crop up.
If a class exists where a property may be null since the data is not required for that particular structure, then I will prefix that property with "Optional". If a method may be expected to return null then I will prefix it with "TryTo". This isn't a perfect system by any means but it's a convention that I've found useful.
So I could change the Get method above to
/// <summary>
/// This will return null if unable to generate the specified converter
/// </summary>
ITypeConverter<TSource, TDest> TryToGet<TSource, TDest>();
if not being able to return the requested converter is not an error condition.
However, for the cases a converter could not be generated to perform the specified TSource -> TDest mapping, the caller has no additional information - all they have is a null! And I suspect that someone trying to get a converter by calling a "Get" method would indeed consider it an error condition if it didn't actually return a converter.
So I changed it to
/// <summary>
/// This will throw an exception if unable to generate the specified converter, it will never
/// return null
/// </summary>
ITypeConverter<TSource, TDest> Get<TSource, TDest>();
I then changed the Type Converter Factories to throw custom exceptions indicating what property could not be set on the target type or what constructor arguments could not be mapped. Changing the contract so that it is considered an error condition when a mapping could not be created resulted in more information being available to the caller, more useful and important information.
Since I felt like I was cooking on gas at this point, I thought I'd address another problem with this project; trying to use this code for the first time (if you'd just cloned the project, for example) is difficult! I've got a ReadMe file in the project that tells you how to initialise a converter factory and then generate types but it's quite a lot of work to do so!
In some of my other projects I've included "convenience wrappers" to do the fiddly work of initialising everything for the most common case so that the code is as easy as possible to get working with. For example, the CSSParser has the static Parser class, with its method "ParseCSS" and "ParseLESS" (with method signatures that will read from strings or from TextReaders). The CSSMinifier has the DefaultNonCachedLessCssLoaderFactory and EnhancedNonCachedLessCssLoaderFactory which can be initialised with only an ASP.Net "Server" reference. And, of course, AutoMapper is phenomenally easy to get going with since there is a static Mapper class with CreateMap and Map methods (amongst many others). So I thought that my Type Converter library would benefit from something similar!
It can't get much simpler than this:
Converter.CreateMap<MutablePersonDetails.RoleDetails, ImmutablePersonDetails.RoleDetails>();
var dest = Converter.Convert<MutablePersonDetails, ImmutablePersonDetails>(source);
The "source" object in this example is initialised with
var source = new MutablePersonDetails
{
Name = "Henry",
Roles = new List<MutablePersonDetails.RoleDetails>
{
new MutablePersonDetails.RoleDetails
{
Title = "Head Penguin Cleaner",
ClearanceLevel = ClearanceLevelOptions.Maximum
}
}
};
(The actual classes for the source and destination types will be included later on for completion's sake).
The types MutablePersonDetails.RoleDetails and ImmutablePersonDetails.RoleDetails are considered "nested" as they are not the target of the primary mapping (which is from MutablePersonDetails to ImmutablePersonDetails). There are properties on the source and destination types which are sets of these RoleDetails nested types.
So first a mapping for the nested types is created. The Converter class is able to use this mapping to generate mappings between sets of these types; so creating a MutablePersonDetails.RoleDetails to ImmutablePersonDetails.RoleDetails mapping means that a List<MutablePersonDetails.RoleDetails> to IEnumerable<ImmutablePersonDetails.RoleDetails> becomes available as well.
The Convert call will implicitly try to create a suitable mapping if one is not already available, this is why no explicit call to CreateMap is required for MutablePersonDetails to ImmutablePersonDetails.
The mapping here was a "by-constructor" mapping (which is what I originally started this project for), it takes property values from the source object and uses them to populate constructor arguments on the destination type to create a new instance of it. But "by-property-setter" mappings are also supported, so we could also create a mapping in the opposite direction to that above:
Converter.CreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>();
var dest = Converter.Convert<ImmutablePersonDetails, MutablePersonDetails>(source);
The source and destination classes in the examples are as follow:
public class MutablePersonDetails
{
public string Name { get; set; }
public List<RoleDetails> Roles { get; set; }
public class RoleDetails
{
public string Title { get; set; }
public ClearanceLevelOptions ClearanceLevel { get; set; }
}
}
public class ImmutablePersonDetails
{
public ImmutablePersonDetails(string name, IEnumerable<RoleDetails> roles)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Null/blank name specified");
if (roles == null)
throw new ArgumentNullException("roles");
Name = name;
Roles = roles.ToList().AsReadOnly();
if (Roles.Any(role => role == null))
throw new ArgumentException("Null reference encountered in roles set");
}
public string Name { get; private set; }
public IEnumerable<RoleDetails> Roles { get; private set; }
public class RoleDetails
{
public RoleDetails(string title, ClearanceLevelOptions clearanceLevel)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("Null/blank title specified");
if (!Enum.IsDefined(typeof(ClearanceLevelOptions), clearanceLevel))
throw new ArgumentOutOfRangeException("clearanceLevel");
Title = title;
ClearanceLevel = clearanceLevel;
}
public string Title { get; private set; }
public ClearanceLevelOptions ClearanceLevel { get; private set; }
}
}
public enum ClearanceLevelOptions
{
Regular,
Maximum
}
If the above classes were changed such that MutablePersonDetails.RoleDetails no longer has a ClearanceLevel property and the ImmutablePersonDetails.RoleDetails constructor's clearanceLevel argument is assigned a default value..
// Nested type of MutablePersonDetails
public class RoleDetails
{
public string Title { get; set; }
}
// Nested type of ImmutablePersonDetails
public RoleDetails(
string title,
ClearanceLevelOptions clearanceLevel = ClearanceLevelOptions.Regular)
.. then the Converter will take this into account and still generate the expected mapping with:
Converter.CreateMap<MutablePersonDetails.RoleDetails, ImmutablePersonDetails.RoleDetails>();
var dest = Converter.Convert<MutablePersonDetails, ImmutablePersonDetails>(source);
If we reversed this such that the MutablePersonDetails.RoleDetails still has a ClearanceLevel property but the ImmutablePersonDetails.RoleDetails does not..
// Nested type of MutablePersonDetails
public class RoleDetails
{
public string Title { get; set; }
public ClearanceLevelOptions ClearanceLevel { get; set; }
}
// Nested type of ImmutablePersonDetails
public class RoleDetails
{
public RoleDetails(string title)
{
if (string.IsNullOrWhiteSpace(title))
throw new ArgumentException("Null/blank title specified");
Title = title;
}
public string Title { get; private set; }
}
.. then the mapping will fail as the Converter will throw an exception if it can't map every property on the target when performing a by-property-setter conversion. Unless it is explicitly instructed to ignore the property -
Converter.BeginCreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>()
.Ignore(
r => r.ClearanceLevel
)
.Create();
var dest = Converter.Convert<ImmutablePersonDetails, MutablePersonDetails>(source);
The BeginCreateMap allows for exceptions to be made to the normal mapping process. The Create call (at the end of the BeginCreateMap, Ignore, Create chain) is important since the work to try to generate the converter will not be performed without that call (and all of the BeginCreateMap and any subsequent calls in that chain will be ignored without Create being called).
This is different to the AutoMapper approach since AutoMapper will take in information about how the mappings should be created but not use it until a conversion is required. This means that mappings can be specified in any order with AutoMapper; the following would be fine, for example -
Mapper.CreateMap<ImmutablePersonDetails, MutablePersonDetails>();
Mapper.CreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>();
AutoMapper doesn't mind the mappings for the nested type appearing after the mapping for the "containing type" since it won't try to use this information until it actually performs a conversion.
My Converter class, however, generates the converters when CreateMap (or Convert is called). So a mapping for the nested types must be specified before the containing type as a converter for the containing type can't be generated without knowing how to convert the nested types! While I think there are advantages to the flexibility of AutoMapper's approach (not having to worry about converter dependencies; not having to worry about the order in which mappings are specified) I also think there are advantages to my approach since an exception will be raised as soon as a mapping is requested that can not be created (along with information about what properties or constructor arguments could not be satisfied).
Another advantage of the converters being generated as the mappings are specified is that the Converter is keeping track of them and can provide a reference to any of them through a call to GetConverter. The converters are all immutable and if a converter is returned from the GetConverter method then no further changes to the Converter class may affect it. This is reassuring in that the converter may be used elsewhere without having to worry about the mutability of the static Converter class but it also has performance benefits; calls to the Converter's Convert method (and CreateMap and GetConverter methods) require cache lookups and locks. If you use a converter reference delivered by the GetConverter method then you don't need to worry about these lookups and locks. Which brings me neatly to..
First off, the Compilable Type Converter isn't intended to compete feature-for-feature with AutoMapper. AutoMapper is a well-rounded library with all sorts of functionality that address all sorts of edge cases. For example, I only encountered the BeforeMap and AfterMap calls when looking into it more deeply to write this article! It also offers object model flattening and retrieval of data through Get methods rather than properties. I don't have any intention of supporting any of these, though I do intend to add some custom property mappings at some point. Something like
Converter.BeginCreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>()
.Custom(
dest => dest.ClearanceLevel,
src => src.GetClearanceLevel()
)
.Create();
(Let's not forget the killer feature of my library - for me, at least - is that it performs the name matching magic from properties onto constructor arguments so that immutable classes can be instantiated by the mappers).
So anyway.. making performance comparisons between the two libraries is probably not all that productive. But since I've banged on about the Compilable Type Converter producing LINQ-Expression-compiled converters, I'm going to anyway! :)
We'll stick with the ImmutablePersonDetails to MutablePersonDetails mapping that was in the earlier examples.
There are two aspects that need considering - the startup time and the conversion time. If the Compilable Type Converter can perform conversions faster than AutoMapper but with a greater initialisation cost (which we'd expect since there is expensive LINQ Expression compilation going on) then there will have to be a certain number of conversion performed before we "break even" on the startup time. But after that, it should be all win!
So I've set up a test program that times the initialisation processes, repeated in a loop. At the end of each loop, the Reset method is called for both the Mapper and Converter (these calls are outside of the initialisation work that is timed, since we're not interested in the efficiency of the Reset methods). The last loop doesn't call Reset so that everything is ready for the next section of the program, where I time a conversion from an ImmutablePersonDetails instance to a new MutablePersonDetails (over and over again).
The init sections looks like this (basically the same as we've already seen above). We have to actually perform one mapping in the init code since AutoMapper postpones doing work until a mapping is actually requested, as I've already spoken about.
Mapper.CreateMap<ImmutablePersonDetails, MutablePersonDetails>();
Mapper.CreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>();
var destAutoMapperInitialise = Mapper.Map<ImmutablePersonDetails, MutablePersonDetails>(source);
Converter.CreateMap<ImmutablePersonDetails.RoleDetails, MutablePersonDetails.RoleDetails>();
var converter = Converter.GetConverter<ImmutablePersonDetails, MutablePersonDetails>();
var destCompilableTypeConverterInitialise = converter.Convert(source);
Then there are three operations that are individually timed in the "convert loop":
// Convert using AutoMapper
Mapper.Map<ImmutablePersonDetails, MutablePersonDetails>(source);
// Convert using the Compilable Type Converter, through the static convenience wrapper
Converter.Convert<ImmutablePersonDetails, MutablePersonDetails>(source);
// Convert using the Compilable Type Converter, using the converter reference from a GetConverter
// call in the init phase (this will be quicker as the cache lookups and locks in the convenience
// wrapper are not required)
converter.Convert(source);
I've run this whole process half a dozen times and got comparable results each time. The last time I ran it, the average time to initialise AutoMapper was 8ms and to initialise the Compilable Type Converter was 41ms (average taken over 100 repeated initialisations). The average time (taken over 100,000 loops) to perform the conversions was 310 ticks for AutoMapper, 46 ticks for the Compilable Type Converter via the convenience wrapper and 3 ticks for the Compilable Type Converter via the converter reference that was obtained as part of the initialisation work.
The standout result here is that the Compilable Type Converter was able to perform the conversion 100x faster.
100x faster!
That's good times! :)
However, this ignores the initialisation overhead. If you were only ever going to perform a single conversion then speed of the initialised converter is more than offset by the additional initialisation time required. However, if you're expecting to perform a lot of these conversions then this initialisation overhead should be easily offset. (My original aim for this work was to translate a WCF web service's public-facing mutable classes into their internal immutable counterparts, so there would be many conversions in that case). In the example above, it would take 349 conversions to break even if using the Converter wrapper and only 300 if using the converter reference directly.
Another note from the future: AutoMapper 5.0 (released July 2016) has some significant performance improvements such that now the performance tests above (which would need tweaking to compile with modern AutoMapper) are only between 2x and 2.5x faster with the CompilableTypeMapper than with AutoMapper. This is fantastic work from the AutoMapper authors! See AutoMapper 5.0 speed increases for more details.
Posted at 23:19
26 February 2012
I've had a series of posts that was initiated by a desire to integrate AutoMapper more easily with classes that are instantiated with so-called "verbose constructors"..
.. that ended up going on somewhat of a tangent and enabled the generation of compilable converters (using LINQ Expressions) that didn't utilise AutoMapper for the majority of simple cases.
While the original intention of the project was to handle the conversion to these "verbose constructor"-based types, it struck me a few days ago that it shouldn't be much work to put together a class similar to the CompilableTypeConverterByConstructor that instead instantiates a type with a parameter-less constructor and sets the data through property-setters rather than by converter. The concept that started this all off in my head was a service that exposed xml-serialisable objects at the boundary but used "always-valid" internal representations (ie. immutable data where all values were specified and validated by constructor) - I wanted a way to convert to internal types. But with this property-setting approach the code could transform both ways.
(Just a quick side-node that for transformations to data-set-by-property types, AutoMapper is actually a much more full-featured package but for what I had in mind the simple name-matching in my project coupled with the significantly improved performance from the compiled converters was a better fit).
I envisaged something along the lines of a new class
public class CompilableTypeConverterByPropertySetting<TSource, TDest>
: ICompilableTypeConverter<TSource, TDest> where TDest : new()
{
public CompilableTypeConverterByPropertySetting(
IEnumerable<ICompilablePropertyGetter> propertyGetters,
IEnumerable<PropertyInfo> propertiesToSet)
{
// Do constructor work..
where the number of propertyGetters would match the number of propertiesToSet. I won't go back over the ICompilableTypeConverter since it's not that important right this second but the property getters are:
public interface ICompilablePropertyGetter : IPropertyGetter
{
/// <summary>
/// This must return a Linq Expression that retrieves the value from SrcType.Property as
/// TargetType - the specified "param" Expression must have a type that is assignable to
/// SrcType.
/// </summary>
Expression GetPropertyGetterExpression(Expression param);
}
public interface IPropertyGetter
{
/// <summary>
/// The type whose property is being accessed
/// </summary>
Type SrcType { get; }
/// <summary>
/// The property on the source type whose value is to be retrieved
/// </summary>
PropertyInfo Property { get; }
/// <summary>
/// The type that the property value should be converted to and returned as
/// </summary>
Type TargetType { get; }
/// <summary>
/// Try to retrieve the value of the specified Property from the specified object (which
/// must be of type SrcType) - this will throw an exception for null or if retrieval fails
/// </summary>
object GetValue(object src);
}
So this should be easy! All I need is to create LINQ Expressions that can take a ParameterExpression of type TSource, use it to instantiate a new TDest and set each of the properties that I already have. And I've already got Expressions to retrieve the data from the TSource instance for each of the properties!
private Func<TSource, TDest> GenerateCompiledConverter()
{
// Declare an expression to represent the src parameter
var src = Expression.Parameter(typeof(TSource), "src");
// Declare a local variable that will be used within the Expression block to have a new
// instance assigned to it and properties set
var dest = Expression.Parameter(typeof(TDest));
// Build up a list of Expressions that:
// 1. Instantiate a new TDest instance
var newInstanceGenerationExpressions = new List<Expression>
{
Expression.Assign(
dest,
Expression.New(typeof(TDest).GetConstructor(new Type[0]))
)
};
// 2 Set properties on the new instance
for (var index = 0; index < _propertiesToSet.Count; index++)
{
newInstanceGenerationExpressions.Add(
Expression.Call(
dest,
_propertiesToSet[index].GetSetMethod(),
_propertyGetters[index].GetPropertyGetterExpression(src)
)
);
}
// 3. Return the reference
newInstanceGenerationExpressions.Add(
dest
);
// Return compiled expression that instantiates a new object by retrieving properties
// from the source and passing as constructor arguments
return Expression.Lambda<Func<TSource, TDest>>(
Expression.Block(
new[] { dest },
newInstanceGenerationExpressions
),
src
).Compile();
}
(Take it as read that _propertiesToSet and _propertyGetters are PropertyInfo[] and ICompilablePropertyGetter[] that are validated and set as class-scoped members by the constructor).
And indeed it does look easy! And I'm kinda wondering what all the fuss was about, but it took me a fair bit of tinkering and reasoning to get here since the LINQ Expression tutorials and examples just aren't that easy to track down! And it's not like you can easily take apart arbitrary example code like when dealing with IL (see the IL Disassembler mention in Dynamically applying interfaces to objects).
But I got there in the end! The only slightly odd thing is that the last expression has to be the ParameterExpression "dest" that we've constructed, otherwise the block won't return anything - it just returns the result of the last expression.
Ok. I've actually lied. That isn't quite all of it. As an ICompilableTypeConverter, the CompilableTypeConverterByPropertySetting should be able to handle null values so that the CompilableTypeConverterPropertyGetter class can take any ICompilableTypeConverter reference and use it to retrieve and convert property values.. even when they're null. So the last section becomes:
// Return compiled expression that instantiates a new object by retrieving properties
// from the source and passing as constructor arguments
return Expression.Lambda<Func<TSource, TDest>>(
Expression.Condition
Expression.Equal(
src,
Expression.Constant(null)
),
Expression.Constant(default(TDest), typeof(TDest)),
Expression.Block(
new[] { dest },
newInstanceGenerationExpressions
)
),
src
).Compile();
.. so that it will return the default value to TDest (null unless TDest is a ValueType) if the TSource value is null.
As with the similar CompilableTypeConverterByConstructor class there's a factory class which will examine given TSource and TDest types and try to generate a CompilableTypeConverterByPropertySetting<TSource, TDest> instance based on the ICompilablePropertyGetter set it has (and the INameMatcher for matching source and destination properties).
I've also updated the ExtendableCompilableTypeConverterFactory (see The Less-Effort Extendable LINQ-compilable Mappers) such that it is more generic and doesn't insist on being based around CompilableTypeConverterByConstructorFactory. There is now a static helper class to instantiate an ExtendableCompilableTypeConverterFactory instance based upon whether the target type is to have its data set by-constructor or by-property-setting since the changes to ExtendableCompilableTypeConverterFactory have made it very abstract!
Since the majority of work in this solution no longer requires AutoMapper, I've broken out a separate project "AutoMapperIntegration" which houses the AutoMapperEnabledPropertyGetter and AutoMapperEnabledPropertyGetterFactory classes so now the main project has no AutoMapper reference. My original intention was improve how AutoMapper worked with by-constructor conversions and this functionality is still available - without taking advantage of the compiled converters - by referencing the main project along with AutoMapperIntegration (and so the example in Teaching AutoMapper about "verbose constructors" is still applicable).
And so I've renamed the solution itself to...
Yeah, yeah, not too imaginative a title, I will admit! :)
I've actually moved my code over to BitBucket (see upcoming post!) from GitHub, so the code that I've been talking about can now be found at:
https://bitbucket.org/DanRoberts/compilabletypeconverter
This has been a particularly dry and largely self-involved post but if the Compilable Type Converter sounds like it might be useful to you, check out that BitBucket link and there's an introduction on the Overview page which jumps straight into example code.
To demonstrate the generation of a converter from a generic SourceType class to one that is based upon verbose constructors:
// Prepare a converter factory using the base types (AssignableType and
// EnumConversion property getter factories)
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var converterFactory = ExtendableCompilableTypeConverterFactoryHelpers.GenerateConstructorBasedFactory(
nameMatcher,
new ArgsLengthTypeConverterPrioritiserFactory(),
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
);
// Extend the converter to handle SourceType.Sub1 to ConstructorDestType.Sub1 and
// IEnumerable<SourceType.Sub1> to IEnumerable<ConstructorDestType.Sub1>
// - This will raise an exception if unable to create the mapping
converterFactory = converterFactory.CreateMap<SourceType.Sub1, ConstructorDestType.Sub1>();
// This will enable the creation of a converter for SourceType to ConstructorDestType
// - This will return null if unable to generate an appropriate converter
var converter = converterFactory.Get<SourceType, ConstructorDestType>();
if (converter == null)
throw new Exception("Unable to obtain a converter");
var result = converter.Convert(new SourceType()
{
Value = new SourceType.Sub1() { Name = "Bo1" },
ValueList = new[]
{
new SourceType.Sub1() { Name = "Bo2" },
null,
new SourceType.Sub1() { Name = "Bo3" }
},
ValueEnum = SourceType.Sub2.EnumValue2
});
public class SourceType
{
public Sub1 Value { get; set; }
public IEnumerable<Sub1> ValueList { get; set; }
public Sub2 ValueEnum { get; set; }
public class Sub1
{
public string Name { get; set; }
}
public enum Sub2
{
EnumValue1,
EnumValue2,
EnumValue3,
EnumValue4,
EnumValue5,
EnumValue6,
EnumValue7,
EnumValue8
}
}
public class ConstructorDestType
{
public ConstructorDestType(Sub1 value, IEnumerable<Sub1> valueList, Sub2 valueEnum)
{
if (value == null)
throw new ArgumentNullException("value");
if (valueList == null)
throw new ArgumentNullException("valueList");
if (!Enum.IsDefined(typeof(Sub2), valueEnum))
throw new ArgumentOutOfRangeException("valueEnum");
Value = value;
ValueList = valueList;
ValueEnum = valueEnum;
}
public Sub1 Value { get; private set; }
public IEnumerable<Sub1> ValueList { get; private set; }
public Sub2 ValueEnum { get; private set; }
public class Sub1
{
public Sub1(string name)
{
name = (name ?? "").Trim();
if (name == "")
throw new ArgumentException("Null/empty name specified");
Name = name;
}
public string Name { get; private set; }
}
public enum Sub2 : uint
{
EnumValue1 = 99,
EnumValue2 = 100,
EnumValue3 = 101,
EnumValue4 = 102,
EnumValue5 = 103,
enumValue_6 = 104,
EnumValue7 = 105
}
}
.. and the equivalent where the destination types are based upon property-setting:
// Prepare a converter factory using the base types (AssignableType and EnumConversion property
// getter factories)
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var converterFactory = ExtendableCompilableTypeConverterFactoryHelpers.GeneratePropertySetterBasedFactory(
nameMatcher,
CompilableTypeConverterByPropertySettingFactory.PropertySettingTypeOptions.MatchAsManyAsPossible,
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
);
// Extend the converter to handle SourceType.Sub1 to ConstructorDestType.Sub1 and
// IEnumerable<SourceType.Sub1> to IEnumerable<ConstructorDestType.Sub1>
// - This will raise an exception if unable to create the mapping
converterFactory = converterFactory.CreateMap<SourceType.Sub1, PropertySettingDestType.Sub1>();
// This will enable the creation of a converter for SourceType to ConstructorDestType
// - This will return null if unable to generate an appropriate converter
var converter = converterFactory.Get<SourceType, PropertySettingDestType>();
if (converter == null)
throw new Exception("Unable to obtain a converter");
var result = converter.Convert(new SourceType()
{
Value = new SourceType.Sub1() { Name = "Bo1" },
ValueList = new[]
{
new SourceType.Sub1() { Name = "Bo2" },
null,
new SourceType.Sub1() { Name = "Bo3" }
},
ValueEnum = SourceType.Sub2.EnumValue2
});
public class SourceType
{
public Sub1 Value { get; set; }
public IEnumerable<Sub1> ValueList { get; set; }
public Sub2 ValueEnum { get; set; }
public class Sub1
{
public string Name { get; set; }
}
public enum Sub2
{
EnumValue1,
EnumValue2,
EnumValue3,
EnumValue4,
EnumValue5,
EnumValue6,
EnumValue7,
EnumValue8
}
}
public class PropertySettingDestType
{
public Sub1 Value { get; set; }
public IEnumerable<Sub1> ValueList { get; set; }
public Sub2 ValueEnum { get; set; }
public class Sub1
{
public string Name { get; set; }
}
public enum Sub2 : uint
{
EnumValue1 = 99,
EnumValue2 = 100,
EnumValue3 = 101,
EnumValue4 = 102,
EnumValue5 = 103,
enumValue_6 = 104,
EnumValue7 = 105
}
}
Posted at 21:39
3 January 2012
The last post almost finished off something I originally started back last April and enabled the creation of Compilable Type Converters which take properties from a source type and feed them in as constructor arguments on a destination type.
The only issue I had is that the final code to set up conversions was a bit verbose. To create a Converter from SourceEmployee to DestEmployee -
public class SourceEmployee
{
public string Name { get; set; }
public SourceRole Role { get; set; }
}
public class SourceRole
{
public string Description { get; set; }
}
public class DestEmployee
{
public DestEmployee(string name, DestRole role)
{
Name = name;
Role = role;
}
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
public class DestRole
{
public DestRole(string description)
{
Description = description;
}
public string Description { get; private set; }
}
the following code was required:
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var roleConverterFactory = new CompilableTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new CombinedCompilablePropertyGetterFactory(
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
)
);
var employeeConverterFactory = new CompilableTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new CombinedCompilablePropertyGetterFactory(
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher),
new CompilableTypeConverterPropertyGetterFactory<SourceRole, DestRole>(
nameMatcher,
roleConverterFactory.Get<SourceRole, DestRole>()
)
}
)
);
var employeeConverter = employeeConverterFactory.Get<SourceEmployee, DestEmployee>();
For more complicated type graphs this could quickly get tiring! What I really wanted to do was this:
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var converterFactory = new ExtendableCompilableTypeConverterFactory(
nameMatcher,
new ArgsLengthTypeConverterPrioritiserFactory(),
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
);
converterFactory = converterFactory.CreateMap<SourceRole, DestRole>();
var converter = converterFactory.Get<SourceEmployee, DestEmployee>();
This class basically wraps up the duplication seen above and returns a new ExtendableCompilableTypeConverterFactory instance each time that CreateMap is successfully called, the new instance having a Compilable Property Getter than can support that mapping. If the CreateMap calls was not successful then an exception will be raised - this will be case if there is no constructor on the destination type whose arguments can all be satisfied by properties on the source type (this also covers cases where additional mappings are required for referenced types). This exception is equivalent to the AutoMapperMappingException that AutoMapper throws in similar circumstances.
I'm just going to jump right in with this - if you've been reading this far then this will hold no challenges or surprises.
public class ExtendableCompilableTypeConverterFactory : ICompilableTypeConverterFactory
{
private INameMatcher _nameMatcher;
private ITypeConverterPrioritiserFactory _converterPrioritiser;
private List<ICompilablePropertyGetterFactory> _basePropertyGetterFactories;
private Lazy<ICompilableTypeConverterFactory> _typeConverterFactory;
public ExtendableCompilableTypeConverterFactory(
INameMatcher nameMatcher,
ITypeConverterPrioritiserFactory converterPrioritiser,
IEnumerable<ICompilablePropertyGetterFactory> basePropertyGetterFactories)
{
if (nameMatcher == null)
throw new ArgumentNullException("nameMatcher");
if (converterPrioritiser == null)
throw new ArgumentNullException("converterPrioritiser");
if (basePropertyGetterFactories == null)
throw new ArgumentNullException("basePropertyGetterFactories");
var basePropertyGetterFactoryList = new List<ICompilablePropertyGetterFactory>();
foreach (var basePropertyGetterFactory in basePropertyGetterFactories)
{
if (basePropertyGetterFactory == null)
throw new ArgumentException("Null entry encountered in basePropertyGetterFactories");
basePropertyGetterFactoryList.Add(basePropertyGetterFactory);
}
_nameMatcher = nameMatcher;
_converterPrioritiser = converterPrioritiser;
_basePropertyGetterFactories = basePropertyGetterFactoryList;
_typeConverterFactory = new Lazy<ICompilableTypeConverterFactory>(
getConverterFactory,
true
);
}
private ICompilableTypeConverterFactory getConverterFactory()
{
return new CompilableTypeConverterByConstructorFactory(
_converterPrioritiser,
new CombinedCompilablePropertyGetterFactory(_basePropertyGetterFactories)
);
}
/// <summary>
/// This will return null if a converter could not be generated
/// </summary>
public ICompilableTypeConverterByConstructor<TSource, TDest> Get<TSource, TDest>()
{
return _typeConverterFactory.Value.Get<TSource, TDest>();
}
ITypeConverter<TSource, TDest> ITypeConverterFactory.Get<TSource, TDest>()
{
return Get<TSource, TDest>();
}
/// <summary>
/// This will throw an exception if unable to generate the requested mapping - it will
/// never return null. If the successful, the returned converter factory will be able
/// to convert instances of TSourceNew as well as IEnumerable / Lists of them.
/// </summary>
public ExtendableCompilableTypeConverterFactory CreateMap<TSourceNew, TDestNew>()
{
// Try to generate a converter for the requested mapping
var converterNew = _typeConverterFactory.Value.Get<TSourceNew, TDestNew>();
if (converterNew == null)
throw new Exception("Unable to create mapping");
return AddNewConverter<TSourceNew, TDestNew>(converterNew);
}
/// <summary>
/// Generate a further extended converter factory that will be able to handle conversion
/// of instances of TSourceNew as well as IEnumerable / Lists of them. This will never
/// return null.
/// </summary>
public ExtendableCompilableTypeConverterFactory AddNewConverter<TSourceNew, TDestNew>(
ICompilableTypeConverter<TSourceNew, TDestNew> converterNew)
{
if (converterNew == null)
throw new ArgumentNullException("converterNew");
// Create a property getter factory that retrieves and convert properties using this
// converter and one that does the same for IEnumerable properties, where the
// IEnumerables' elements are the types handled by the converter
var extendedPropertyGetterFactories = new List<ICompilablePropertyGetterFactory>(
_basePropertyGetterFactories
);
extendedPropertyGetterFactories.Add(
new CompilableTypeConverterPropertyGetterFactory<TSourceNew, TDestNew>(
_nameMatcher,
converterNew
)
);
extendedPropertyGetterFactories.Add(
new ListCompilablePropertyGetterFactory<TSourceNew, TDestNew>(
_nameMatcher,
converterNew
)
);
// Return a new ExtendableCompilableTypeConverterFactory that can make use of these
// new property getter factories
return new ExtendableCompilableTypeConverterFactory(
_nameMatcher,
_converterPrioritiser,
extendedPropertyGetterFactories
);
}
}
Ok.. except one. I've sprung the ListCompilablePropertyGetterFactory. The ListCompilablePropertyGetter is similar to the CompilableTypeConverterPropertyGetter but will deal with properties and constructor arguments which are IEnumerable<SourceType> and IEnumerable<DestType>, resp.
This means that the ExtendableCompilableTypeConverterFactory setup code above would have worked if the SourceType and DestType were
public class SourceEmployee
{
public string Name { get; set; }
public SourceRole[] Role { get; set; }
}
public class DestEmployee
{
public DestEmployee(string name, IEnumerable<DestRole> role)
{
Name = name;
Role = role;
}
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
as the CreateMap would return a Converter Factory that could map SourceRole to DestRole and IEnumerable<SourceRole> to IEnumerable<DestRole>.
The CreateMap method will try to generate a new Converter and build new Property Getter Factories using that by passing it to AddNewConverter. If you need to add any custom mapping mechanisms then AddNewConverter may be called with an ICompilableTypeConverter.
For example, if our types now looked like
public class SourceEmployee
{
public int Id { get; set; }
public string Name { get; set; }
public SourceRole[] Role { get; set; }
}
public class DestEmployee
{
public DestEmployee(string id, string name, IEnumerable<DestRole> role)
{
Id = id;
Name = name;
Role = role;
}
public string Id { get; private set; }
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
then we would need a way to translate int to string when the name matcher identifies the potential "Id" to "id" mapping. We could do that with AddNewConverter and a custom ICompilableTypeConverter implementation -
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var converterFactory = new ExtendableCompilableTypeConverterFactory(
nameMatcher,
new ArgsLengthTypeConverterPrioritiserFactory(),
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
);
converterFactory = converterFactory.CreateMap<SourceRole, DestRole>();
converterFactory = converterFactory.AddNewConverter<int, string>(
new CompilableIntToStringTypeConverter()
);
var converter = converterFactory.Get<SourceEmployee, DestEmployee>();
public class CompilableIntToStringTypeConverter : ICompilableTypeConverter<int, string>
{
public string Convert(int src)
{
return src.ToString();
}
public Expression GetTypeConverterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
return Expression.Call(
param,
typeof(int).GetMethod("ToString", new Type[0])
);
}
}
See, I promised last time that splitting ICompilableTypeConverter away from ICompilableTypeConverterByConstructor at some point! :)
This has all turned into a bit of a saga! The final code for all this can be found at
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/
I've not done loads of performance testing but the generated Converters have consistently been around 1.1 or 1.2 times as slow as hand-rolled code (ie. approximately the same), not including the work required to generate the Converters. Compared to AutoMapper, this is quite a win (which was what originally inspired me to go on this journey). But out of the box it doesn't support all the many configurations that AutoMapper does! My main use case was to map legacy WebService objects (with parameter-less constructors) onto internal objects (with verbose constructors) which is all done. But there's currently no way to map back.. I think that's something to worry about another day! :)
Posted at 21:52
2 January 2012
To pick up from where I left off in a previous post, I was trying to write something that could automatically generate LINQ Expressions that could translate from (for example) -
public class SourceEmployee
{
public string Name { get; set; }
public SourceRole Role { get; set; }
}
public class SourceRole
{
public string Description { get; set; }
}
to
public class DestEmployee
{
public DestEmployee(string name, DestRole role)
{
Name = name;
Role = role;
}
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
public class DestRole
{
public DestRole(string description)
{
Description = description;
}
public string Description { get; private set; }
}
by applying name matching logic between properties on the source types and constructor arguments on the destination types. Having this all performed by LINQ Expressions should allow the final conversion to be comparatively fast to hand-rolled code.
This was all kicked off initially since I was using AutoMapper for some work and wasn't happy with its approach to mapping to types that have to be initialised with verbose constructors (as opposed to a parameter-less constructor and then the setting of individual properties). This much was achieved and the solution can be found here -
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/tree/FirstImplementation.
But I wanted to see if I could improve the performance by removing AutoMapper from the equation and using LINQ Expressions.
Where we left the code as of
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/tree/LinqExpressionPropertyGetters
we had the class
public class CompilableTypeConverterByConstructor<TSource, TDest>
: ITypeConverterByConstructor<TSource, TDest>
{
// ..
private Lazy<Func<TSource, TDest>> _converter;
public CompilableTypeConverterByConstructor(
IEnumerable<ICompilablePropertyGetter> propertyGetters,
ConstructorInfo constructor)
{
// ..
_converter = new Lazy<Func<TSource, TDest>>(generateCompiledConverter, true);
}
public ConstructorInfo Constructor
{
get
{
// ..
}
}
public TDest Convert(TSource src)
{
if (src == null)
throw new ArgumentNullException("src");
return _converter.Value(src);
}
private Func<TSource, TDest> generateCompiledConverter()
{
var srcParameter = Expression.Parameter(typeof(TSource), "src");
var constructorParameterExpressions = new List<Expression>();
foreach (var constructorParameter in _constructor.GetParameters())
{
var index = constructorParameterExpressions.Count;
constructorParameterExpressions.Add(
_propertyGetters[index].GetPropertyGetterExpression(srcParameter)
);
}
return Expression.Lambda<Func<TSource, TDest>>(
Expression.New(
_constructor,
constructorParameterExpressions.ToArray()
),
srcParameter
).Compile();
}
}
public interface ITypeConverterByConstructor<TSource, TDest>
{
ConstructorInfo Constructor { get; }
TDest Convert(TSource src);
}
which took a set of "Compilable Property Getters" that matched the arguments for a specified ConstructorInfo
public interface ICompilablePropertyGetter : IPropertyGetter
{
Expression GetPropertyGetterExpression(Expression param);
}
public interface IPropertyGetter
{
Type SrcType { get; }
PropertyInfo Property { get; }
Type TargetType { get; }
object GetValue(object src);
}
and generated an internal conversion using LINQ Expressions.
There were only two Compilable Property Getters - CompilableAssignableTypesPropertyGetter, which would work with property-to-constructor-arguments where no conversion was required (eg. the available property was a string array and the constructor argument was an IEnumerable<string>) and CompilableEnumConversionPropertyGetter, which mapped one enum to another using an INameMatcher implementation. (The enum mapping LINQ Expression is generated by first coming up with a set of mappings and then generating a LINQ Expression consisting of a set of nested "if" statements for each mapped enum value).
public class CompilableAssignableTypesPropertyGetter<TSourceObject, TPropertyAsRetrieved>
: AbstractGenericCompilablePropertyGetter<TSourceObject, TPropertyAsRetrieved>
{
private PropertyInfo _propertyInfo;
public CompilableAssignableTypesPropertyGetter(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentNullException("propertyInfo");
if (!propertyInfo.DeclaringType.Equals(typeof(TSourceObject)))
throw new ArgumentException("Invalid propertyInfo - DeclaringType must match TSourceObject");
_propertyInfo = propertyInfo;
}
public override PropertyInfo Property
{
get { return _propertyInfo; }
}
public override Expression GetPropertyGetterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
if (!typeof(TSourceObject).IsAssignableFrom(param.Type))
throw new ArgumentException("param.Type must be assignable to typeparam TSourceObject");
Expression getter = Expression.Property(
param,
_propertyInfo
);
var targetType = typeof(TPropertyAsRetrieved);
if (!targetType.IsAssignableFrom(_propertyInfo.PropertyType))
getter = Expression.Convert(getter, targetType);
if (!targetType.IsValueType && _propertyInfo.PropertyType.IsValueType)
getter = Expression.TypeAs(getter, typeof(object));
return getter;
}
}
public abstract class AbstractGenericCompilablePropertyGetter<TSourceObject, TPropertyAsRetrieved>
: ICompilablePropertyGetter
{
private Lazy<Func<TSourceObject, TPropertyAsRetrieved>> _getter;
public AbstractGenericCompilablePropertyGetter()
{
_getter = new Lazy<Func<TSourceObject, TPropertyAsRetrieved>>(generateGetter, true);
}
public Type SrcType
{
get { return typeof(TSourceObject); }
}
public abstract PropertyInfo Property { get; }
public Type TargetType
{
get { return typeof(TPropertyAsRetrieved); }
}
object IPropertyGetter.GetValue(object src)
{
if (src == null)
throw new ArgumentNullException("src");
if (!src.GetType().Equals(typeof(TSourceObject)))
throw new ArgumentException("The type of src must match typeparam TSourceObject");
return GetValue((TSourceObject)src);
}
public TPropertyAsRetrieved GetValue(TSourceObject src)
{
if (src == null)
throw new ArgumentNullException("src");
return _getter.Value(src);
}
public abstract Expression GetPropertyGetterExpression(Expression param);
private Func<TSourceObject, TPropertyAsRetrieved> generateGetter()
{
var param = Expression.Parameter(typeof(TSourceObject), "src");
return Expression.Lambda<Func<TSourceObject, TPropertyAsRetrieved>>(
GetPropertyGetterExpression(param),
param
).Compile();
}
}
public interface ICompilablePropertyGetter : IPropertyGetter
{
/// <summary>
/// This Linq Expression will retrieves the value from SrcType.Property as TargetType,
/// the specified "param" Expression must have a type that is assignable to SrcType.
/// </summary>
Expression GetPropertyGetterExpression(Expression param);
}
public interface IPropertyGetter
{
/// <summary>
/// This is the type whose property is being accessed
/// </summary>
Type SrcType { get; }
/// <summary>
/// This is the property on the source type whose value is to be retrieved
/// </summary>
PropertyInfo Property { get; }
/// <summary>
/// This is the type that the property value should be converted to and returned as
/// </summary>
Type TargetType { get; }
/// <summary>
/// Try to retrieve the value of the specified Property from the specified object
/// (which must be of type SrcType)
/// </summary>
object GetValue(object src);
}
and to generate instances of these classes we had some factories (CompilableTypeConverterByConstructorFactory, CompilableAssignableTypesPropertyGetterFactory and CompilableEnumConversionPropertyGetterFactory). These would do the work of examining the properties and constructors of specified source and destination type pairs and determining the best constructor that could be satisfied (if any) with the Compilable Property Getters. The code in these factories is none too exciting.
If the mappings we want to generate are for very simple structures (in this case, "simple" means that all property-to-constructor-argument mappings are either directly assignable-to or are enum mappings) then everything's rosy - eg.
public class SourceEmployee
{
public string Name { get; set; }
public SourceRole Role { get; set; }
}
public enum SourceRole
{
big_boss_man,
worker_bee
}
to
public class DestEmployee
{
public DestEmployee(string name, DestRole role)
{
Name = name;
Role = role;
}
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
public enum DestRole
{
BigBossMan,
WorkerBee
}
(The enum mapping in this example would be handled by specifying a CaseInsensitiveSkipUnderscoreNameMatcher for the CompilableEnumConversionPropertyGetterFactory).
But the problem I opened with does not come under this "simple structure" umbrella as in that case SourceRole and DestRole are types for which we have no Compilable Property Getter! Oh noes!
For inspiration, I go back to AutoMapper since it too can not magically handle nested types -
class Program
{
static void Main(string[] args)
{
AutoMapper.Mapper.CreateMap<SourceTypeSub1, DestTypeSub1>();
AutoMapper.Mapper.CreateMap<SourceType, DestType>();
var dest = AutoMapper.Mapper.Map<SourceType, DestType>(
new SourceType()
{
Value = new SourceTypeSub1() { Name = "N1" }
}
);
}
}
public class SourceType
{
public SourceTypeSub1 Value { get; set; }
}
public class SourceTypeSub1
{
public string Name { get; set; }
}
public class DestType
{
public DestTypeSub1 Value { get; set; }
}
public class DestTypeSub1
{
public string Name { get; set; }
}
without the CreateMap call for SourceTypeSub1 to DestTypeSub1, the Map call from SourceType to DestType would fail with an AutoMapperMappingException.
Following the same tack, a way to create a new Compilable Property Getter from a CompilableTypeConverterByConstructor (which could then be used alongside the existing AssignableType and Enum Compilable Property Getters) should solve the problem. A plan!
Step one is going to be to expose a way to request the LINQ Expression that the CompilableTypeConverterByConstructor uses in its conversion. To address this we'll update CompilableTypeConverterByConstructor to implement a new interface ICompilableTypeConverterByConstructor which in turn implements ITypeConverterByConstructor (which is all that CompilableTypeConverterByConstructor implemented previously) -
public interface ICompilableTypeConverterByConstructor<TSource, TDest>
: ICompilableTypeConverter<TSource, TDest>,
ITypeConverterByConstructor<TSource, TDest> { }
public interface ICompilableTypeConverter<TSource, TDest>
: ITypeConverter<TSource, TDest>
{
/// <summary>
/// This Linq Expression will generate a new TDest instance - the specified "param"
/// Expression must have a type that is assignable to TSource
/// </summary>
Expression GetTypeConverterExpression(Expression param);
}
public interface ITypeConverterByConstructor<TSource, TDest> : ITypeConverter<TSource, TDest>
{
ConstructorInfo Constructor { get; }
}
public interface ITypeConverter<TSource, TDest>
{
TDest Convert(TSource src);
}
The ITypeConverterByConstructor has now become a specialised form of ITypeConverter (with corresponding Compilable variants) which inherently makes sense but will also be useful where we're going (but let's not get ahead of ourselves, that's coming up later in the post).
More importantly is the ICompilableTypeConverter GetTypeConverterExpression method which allows the creation of a Compilable Property Getter that is based upon a conversion that we want to feed back into the mapper -
public class CompilableTypeConverterPropertyGetter<TSourceObject, TPropertyOnSource, TPropertyAsRetrieved>
: AbstractGenericCompilablePropertyGetter<TSourceObject, TPropertyAsRetrieved>
{
private PropertyInfo _propertyInfo;
private ICompilableTypeConverter<TPropertyOnSource, TPropertyAsRetrieved> _compilableTypeConverter;
public CompilableTypeConverterPropertyGetter(
PropertyInfo propertyInfo,
ICompilableTypeConverter<TPropertyOnSource, TPropertyAsRetrieved> compilableTypeConverter)
{
if (propertyInfo == null)
throw new ArgumentNullException("propertyInfo");
if (!propertyInfo.DeclaringType.Equals(typeof(TSourceObject)))
throw new ArgumentException("Invalid propertyInfo - DeclaringType must match TSourceObject");
if (!propertyInfo.PropertyType.Equals(typeof(TPropertyOnSource)))
throw new ArgumentException("Invalid propertyInfo - PropertyType must match TPropertyOnSource");
if (compilableTypeConverter == null)
throw new ArgumentNullException("compilableTypeConverter");
_propertyInfo = propertyInfo;
_compilableTypeConverter = compilableTypeConverter;
}
public override PropertyInfo Property
{
get { return _propertyInfo; }
}
/// <summary>
/// This Linq Expression will retrieves the value from SrcType.Property as TargetType,
/// the specified "param" Expression must have a type that is assignable to SrcType.
/// </summary>
public override Expression GetPropertyGetterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
if (typeof(TSourceObject) != param.Type)
throw new ArgumentException("param.NodeType must match typeparam TSourceObject");
// Get property value (from object of type TSourceObject) without conversion (this
// will be as type TPropertyOnSource)
// - If value is null, return default TPropertyAsRetrieved (not applicable if a
// value type)
// - Otherwise, pass through type converter (to translate from TPropertyOnSource
// to TPropertyAsRetrieved)
var propertyValue = Expression.Property(param, _propertyInfo);
var conversionExpression = _compilableTypeConverter.GetTypeConverterExpression(propertyValue);
if (typeof(TPropertyOnSource).IsValueType)
return conversionExpression;
return Expression.Condition(
Expression.Equal(
propertyValue,
Expression.Constant(null)
),
Expression.Constant(default(TPropertyAsRetrieved), typeof(TPropertyAsRetrieved)),
conversionExpression
);
}
}
A corresponding CompilableTypeConverterPropertyGetterFactory is straight-forward to write. Like the other Property Getter Factories, it doesn't do a huge amount - it will determine whether a named property can be retrieved from a specified type and converted into a specified type based upon name match rules and what kind of Property Getter that Factory can generate)
public class CompilableTypeConverterPropertyGetterFactory<TPropertyOnSource, TPropertyAsRetrieved>
: ICompilablePropertyGetterFactory
{
private INameMatcher _nameMatcher;
private ICompilableTypeConverter<TPropertyOnSource, TPropertyAsRetrieved> _typeConverter;
public CompilableTypeConverterPropertyGetterFactory(
INameMatcher nameMatcher,
ICompilableTypeConverter<TPropertyOnSource, TPropertyAsRetrieved> typeConverter)
{
if (nameMatcher == null)
throw new ArgumentNullException("nameMatcher");
if (typeConverter == null)
throw new ArgumentNullException("typeConverter");
_nameMatcher = nameMatcher;
_typeConverter = typeConverter;
}
/// <summary>
/// This will return null if unable to return an ICompilablePropertyGetter for the
/// named property that will return a value as the requested type
/// </summary>
public ICompilablePropertyGetter Get(
Type srcType,
string propertyName,
Type destPropertyType)
{
if (srcType == null)
throw new ArgumentNullException("srcType");
propertyName = (propertyName ?? "").Trim();
if (propertyName == "")
throw new ArgumentException("Null/empty propertyName specified");
if (destPropertyType == null)
throw new ArgumentNullException("destPropertyType");
// If destination type does not match type converter's destination type then can
// not handle the request; return null
if (destPropertyType != typeof(TPropertyAsRetrieved))
return null;
// Try to get a property we CAN retrieve and convert as requested..
var property = srcType.GetProperties().FirstOrDefault(p =>
p.GetIndexParameters().Length == 0
&& _nameMatcher.IsMatch(propertyName, p.Name)
&& p.PropertyType == typeof(TPropertyOnSource)
);
if (property == null)
return null;
// .. if successful, use to instantiate a CompilableTypeConverterPropertyGetter
return (ICompilablePropertyGetter)Activator.CreateInstance(
typeof(CompilableTypeConverterPropertyGetter<,,>).MakeGenericType(
srcType,
property.PropertyType,
destPropertyType
),
property,
_typeConverter
);
}
IPropertyGetter IPropertyGetterFactory.Get(
Type srcType,
string propertyName,
Type destPropertyType)
{
return Get(srcType, propertyName, destPropertyType);
}
}
Note: I skipped over actually altering the CompilableTypeConverterByConstructor class to implement the GetTypeConverterExpression but it wasn't anything too complex, the generateCompiledConverter method was changed from
private Func<TSource, TDest> generateCompiledConverter()
{
var srcParameter = Expression.Parameter(typeof(TSource), "src");
var constructorParameterExpressions = new List<Expression>();
foreach (var constructorParameter in _constructor.GetParameters())
{
var index = constructorParameterExpressions.Count;
constructorParameterExpressions.Add(
_propertyGetters[index].GetPropertyGetterExpression(srcParameter)
);
}
return Expression.Lambda<Func<TSource, TDest>>(
Expression.New(
_constructor,
constructorParameterExpressions.ToArray()
),
srcParameter
).Compile();
}
and expanded into
private Func<TSource, TDest> generateCompiledConverter()
{
var srcParameter = Expression.Parameter(typeof(TSource), "src");
return Expression.Lambda<Func<TSource, TDest>>(
GetTypeConverterExpression(srcParameter),
srcParameter
).Compile();
}
/// <summary>
/// This Linq Expression will generate a new TDest instance - the specified "param"
/// Expression must have a type that is assignable to TSource
/// </summary>
public Expression GetTypeConverterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
if (!typeof(TSource).IsAssignableFrom(param.Type))
throw new ArgumentException("param.Type must be assignable to typeparam TSource");
// Instantiate expressions for each constructor parameter by using each of the
// property getters against the source value
var constructorParameterExpressions = new List<Expression>();
foreach (var constructorParameter in _constructor.GetParameters())
{
var index = constructorParameterExpressions.Count;
constructorParameterExpressions.Add(
_propertyGetters[index].GetPropertyGetterExpression(param)
);
}
// Return an expression that to instantiate a new TDest by using property getters
// as constructor arguments
return Expression.Condition(
Expression.Equal(
param,
Expression.Constant(null)
),
Expression.Constant(default(TDest), typeof(TDest)),
Expression.New(
_constructor,
constructorParameterExpressions.ToArray()
)
);
}
The only notable difference is that GetTypeConverterExpression should return an Expression that can deal with null values - we need this so that null properties can be retrieved from source types and passed to destination type constructors. Previously there was a null check against the "src" parameter passed to the Convert method, but this can be relaxed now that nulls have to be supported for this class to work as part of a Property Getter.
With the introduction of a CombinedCompilablePropertyGetterFactory (which will run through a set a Compilable Property Getter Factories for each request until one of the returns a non-null value to the Get request), we end up with this structure:
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var converterFactory = new CompilableTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new CombinedCompilablePropertyGetterFactory(
new ICompilablePropertyGetterFactory[]
{
// Insert Compilable Property Getter Factories here..
}
)
);
which finally allows a setup such as:
var nameMatcher = new CaseInsensitiveSkipUnderscoreNameMatcher();
var roleConverterFactory = new CompilableTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new CombinedCompilablePropertyGetterFactory(
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher)
}
)
);
var employeeConverterFactory = new CompilableTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new CombinedCompilablePropertyGetterFactory(
new ICompilablePropertyGetterFactory[]
{
new CompilableAssignableTypesPropertyGetterFactory(nameMatcher),
new CompilableEnumConversionPropertyGetterFactory(nameMatcher),
new CompilableTypeConverterPropertyGetterFactory<SourceRole, DestRole>(
nameMatcher,
roleConverterFactory.Get<SourceRole, DestRole>()
)
}
)
);
var employeeConverter = employeeConverterFactory.Get<SourceEmployee, DestEmployee>();
var dest = employeeConverter.Convert(
new SourceEmployee()
{
Name = "Richard",
Role = new SourceRole() { Description = "Penguin Cleaner" }
}
);
Hoorah!
Now, there's a slight refinement that I want to look at next time but I think this post has gone on more than long enough.
For the super-observant, I mentioned that the use of ITypeConverter (as opposed to necessarily requiring ITypeConverterByConstructor) would be touched on again in this post. Since I've run out of steam that will be covered next time too.
Posted at 14:11
13 July 2011
I've been wanting to see if I can improve the performance of the by-constructor type converter I wrote about (here). The plan is to implement Property Getters that can retrieve the property values - translated, if required - from a source object using LINQ Expressions. Then to push these through a ConstructorInfo call using more LINQ Expressions such that a single expression can be constructed that converts from source to destination types at the same speed that hand-rolled code would. In a lot of cases, this could be merely academic but if 1000s of instances are being converted together, then the overhead of AutoMapper could make a signficant difference.
So I want to expand
public interface IPropertyGetter
{
Type SrcType { get; }
PropertyInfo Property { get; }
Type TargetType { get; }
object GetValue(object src);
}
with
public interface ICompilablePropertyGetter : IPropertyGetter
{
Expression GetPropertyGetterExpression(Expression param);
}
and to expand
public interface ITypeConverterByConstructor<TSource, TDest>
{
ConstructorInfo Constructor { get; }
TDest Convert(TSource src);
}
with
public interface ICompilableTypeConverterByConstructor<TSource, TDest>
: ITypeConverterByConstructor<TSource, TDest>
{
Expression GetTypeConverterExpression(Expression param);
}
Turns it out this was quite easy to implement if you know how.. but quite difficult to find examples out there if you don't! One of the things I like about LINQ Expressions code is that when you read it back it scans quite well and kinda makes sense. However, I'm still really not that experienced with it and when I want to try something new it takes me quite a while to get to grips with how I need to form the code.
The first property getter I've got will retrieve the value of a property from a specified source type TSourceObject, where the property value is of type TPropertyAsRetrieved. TPropertyAsRetrieved in this case must be assignable-to from the type of the property on TSourceObject. So TPropertyAsRetrieved could be a string IEnumerable if the property on TSourceObject was a string array, for example (as IEnumerable<string> is assignable-to from string[]).
public class CompilableAssignableTypesPropertyGetter<TSourceObject, TPropertyAsRetrieved>
: AbstractGenericCompilablePropertyGetter<TSourceObject, TPropertyAsRetrieved>
{
private PropertyInfo _propertyInfo;
public CompilableAssignableTypesPropertyGetter(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentNullException("propertyInfo");
if (!propertyInfo.DeclaringType.Equals(typeof(TSourceObject)))
throw new ArgumentException("Invalid propertyInfo - DeclaringType must match TSourceObject");
_propertyInfo = propertyInfo;
}
public override PropertyInfo Property
{
get { return _propertyInfo; }
}
public override Expression GetPropertyGetterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
if (!typeof(TSourceObject).IsAssignableFrom(param.Type))
throw new ArgumentException("param.Type must be assignable to typeparam TSourceObject");
// Prepare to grab the property value from the source object directly
Expression getter = Expression.Property(
param,
_propertyInfo
);
// Try to convert types if not directly assignable (eg. this covers some common enum type conversions)
var targetType = typeof(TPropertyAsRetrieved);
if (!targetType.IsAssignableFrom(_propertyInfo.PropertyType))
getter = Expression.Convert(getter, targetType);
// Perform boxing, if required (eg. when enum being handled and TargetType is object)
if (!targetType.IsValueType && _propertyInfo.PropertyType.IsValueType)
getter = Expression.TypeAs(getter, typeof(object));
return getter;
}
}
In order to keep the interesting compilable getter code separate from the boring stuff which implements the rest of IPropertyGetter, I've used a base class AbstractGenericCompilablePropertyGetter -
public abstract class AbstractGenericCompilablePropertyGetter<TSourceObject, TPropertyAsRetrieved>
: ICompilablePropertyGetter
{
private Lazy<Func<TSourceObject, TPropertyAsRetrieved>> _getter;
public AbstractGenericCompilablePropertyGetter()
{
_getter = new Lazy<Func<TSourceObject, TPropertyAsRetrieved>>(generateGetter, true);
}
public Type SrcType
{
get { return typeof(TSourceObject); }
}
public abstract PropertyInfo Property { get; }
public Type TargetType
{
get { return typeof(TPropertyAsRetrieved); }
}
object IPropertyGetter.GetValue(object src)
{
if (src == null)
throw new ArgumentNullException("src");
if (!src.GetType().Equals(typeof(TSourceObject)))
throw new ArgumentException("The type of src must match typeparam TSourceObject");
return GetValue((TSourceObject)src);
}
public TPropertyAsRetrieved GetValue(TSourceObject src)
{
if (src == null)
throw new ArgumentNullException("src");
return _getter.Value(src);
}
public abstract Expression GetPropertyGetterExpression(Expression param);
private Func<TSourceObject, TPropertyAsRetrieved> generateGetter()
{
var param = Expression.Parameter(typeof(TSourceObject), "src");
return Expression.Lambda<Func<TSourceObject, TPropertyAsRetrieved>>(
GetPropertyGetterExpression(param),
param
).Compile();
}
}
The general concept for this is straight-forward; a CompilableTypeConverterByConstructor<TSource, TDest> class will take a set of compilable property getters and a ConstructorInfo reference (that is used to instantiates instances of TDest and that takes the same number of parameters are there are property getters specified). The compilable type converter generates a LINQ Expression to perform the translation from TSource to TDest, given a ParameterExpression for the source object -
public Expression GetTypeConverterExpression(Expression param)
{
if (param == null)
throw new ArgumentNullException("param");
if (!typeof(TSource).IsAssignableFrom(param.Type))
throw new ArgumentException("param.Type must be assignable to typeparam TSource");
// Instantiate expressions for each constructor parameter by using each of the
// property getters against the source value
var constructorParameterExpressions = new List<Expression>();
foreach (var constructorParameter in _constructor.GetParameters())
{
var index = constructorParameterExpressions.Count;
constructorParameterExpressions.Add(
_propertyGetters[index].GetPropertyGetterExpression(param)
);
}
// Return an expression that to instantiate a new TDest by using property getters
// as constructor arguments
return Expression.Condition(
Expression.Equal(
param,
Expression.Constant(null)
),
Expression.Constant(default(TDest), typeof(TDest)),
Expression.New(
_constructor,
constructorParameterExpressions.ToArray()
)
);
}
There's some handling in there to return default(TDest) if a null source reference is passed in but there are no other particular areas of note.
There's a lot more work to be done down this avenue, since currently there's only Compilable Property Getters for Assignable Types (where no real conversion is happening) and Enums (where lookups from the source values to destination values are attempted by name before falling back to a straight numeric mapping). The code as described here is available in this tagged release:
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/tree/LinqExpressionPropertyGetters
However, there's more on the way! I want to be able to take these simple compilable classes and use them to create more complicated type converters, so that once we have a compilable converter from:
public class SourceRole
{
public string Description { get; set; }
}
to
public class DestRole
{
public DestRole(string description)
{
Description = description;
}
public string Description { get; private set; }
}
we could leverage it translate
public class SourceEmployee
{
public string Name { get; set; }
public SourceRole Role { get; set; }
}
to
public class DestEmployee
{
public DestEmployee(string name, DestRole role)
{
Name = name;
Roles = roles;
}
public string Name { get; private set; }
public DestRole Role { get; private set; }
}
or:
public class SourceRole
{
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
public class SourceEmployee
{
public string Name { get; set; }
public IEnumerable<SourceRole> Roles { get; set; }
}
to
public class DestRole
{
public DestRole(string description, DateTime startDate, DateTime endDate)
{
Description = description;
StartDate = startDate;
EndDate = endDate
}
public string Description { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime EndDate { get; private set; }
}
public class DestEmployee
{
public DestEmployee(string name, IEnumerable<DestRole> roles)
{
Name = name;
Roles = roles;
}
public string Name { get; private set; }
public IEnumerable<DestRole> Roles { get; private set; }
}
.. something similar to the way in which AutoMapper's CreateMap method works.
I've finally got round to writing up this conclusion; here.
Posted at 20:11
25 April 2011
As I alluded to in an earlier post (The joys of AutoMapper), I've been wanting to look into a way to get AutoMapper to work with these once-instantiated / always-valid / verbose-constructor classes I'm such a fan of. As I'd hoped, it's actually not that big of a deal and I've put together a demo project:
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1
There's an example in that download (and at the bottom of this post) if curiosity gets the better of you but I'm going to step through an outline of the solution here.
Before we get going, it's worth noting that I'm hoping to expand on this solution and improve it in a number of areas - to make life easier if you're starting with this post, I've tagged the repository as "FirstImplementation" in its current state, so for the solution in its current form (as I'm about to describe), it may be best to download it from here:
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/tree/FirstImplementation
There's a class that tries to locate a property on srcType which can be used as a particular constructor argument:
public interface IPropertyGetterFactory
{
IPropertyGetter Get(Type srcType, string propertyName, Type destPropertyType);
}
The IPropertyGetterFactory implementation will apply the name-matching criteria - it will compare "propertyName" to the actual names of properties on srcType - so it will have access to:
public interface INameMatcher
{
bool IsMatch(string from, string to);
}
If the IPropertyGetterFactory manages to find a property name / type match it return an IPropertyGetter:
public interface IPropertyGetter
{
Type SrcType { get; }
PropertyInfo Property { get; }
Type TargetType { get; }
object GetValue(object src);
}
We have a class which considers all of the constructors of destType and tries to match up their argument names to srcType properties using an IPropertyGetterFactory:
public interface ITypeConverterByConstructorFactory
{
ITypeConverterByConstructor<TSource, TDest> Get<TSource, TDest>();
}
If ITypeConverterByConstructorFactory is able to find destType constructors whose arguments can be fully populated by srcType data, it returns:
public interface ITypeConverterByConstructor<TSource, TDest>
{
TDest Convert(TSource src);
ConstructorInfo Constructor { get; }
IEnumerable<PropertyInfo> SrcProperties { get; }
}
The ITypeConverterByConstructor may make use of an IConstructorInvoker implementation which handles the passing of the arguments to the constructor to create the new destType instance.
public interface IConstructorInvokerFactory
{
IConstructorInvoker<T> Get<T>(ConstructorInfo constructor);
}
public interface IConstructorInvoker<TDest>
{
TDest Invoke(object[] args);
}
For the cases where multiple destType constructors where available, a way to decide which is best is required (in most cases, we'll probably be interested in the constructor which has the most arguments, but there might be special cases):
public interface ITypeConverterPrioritiserFactory
{
ITypeConverterPrioritiser<TSource, TDest> Get<TSource, TDest>();
}
public interface ITypeConverterPrioritiser<TSource, TDest>
{
ITypeConverterByConstructor<TSource, TDest> Get(IEnumerable<ITypeConverterByConstructor<TSource, TDest>> options);
}
Some of the key elements - ITypeConverterByConstructor, IConstructorInvoker, ITypeConverterPrioritiser - have generic typeparams specified but the ITypeConverterByConstructorFactory that prepares the ITypeConverterByConstructor does not; I wanted to be able to use one ITypeConverterByConstructorFactory instance to prepare converters for various combinations of srcType, destType. This is why these key elements have factory interfaces to instantiate them - the factory class will have no typeparam specification but will create "worker" classes that do. IPropertyGetter is an exception to this pattern as I was expecting to have to have to maintain a list of them in each ITypeConverterByConstructor and so they would have to at least share a interface without typeparams.
These interfaces and corresponding classes can all be found in the GitHub repository and hopefully it will make a reasonable amount of sense now that everything's been outlined here. With a basic knowledge of reflection and AutoMapper hopefully the code won't be too difficult to read through and there are examples both in the solution itself and in the Readme.
Again, there is a repository branch that only covers what's discussed here and not all the following work I'm planning for it:
https://github.com/ProductiveRage/AutoMapper-By-Constructor-1/tree/FirstImplementation
I'm happy I've solved the initial case I set out to, but it seems now like AutoMapper needn't be as key as I was first envisaging! For cases where the types don't all match up into nice assignable-to conversions, AutoMapper definitely comes in handy - but one class of cases I'd like to use this for would be converting from (asmx) webservice interface objects (where all properties have loose getters and setters) to a validated-by-constructor class. Most of the time the property types would match and wouldn't need AutoMapper. And then maybe the conversion could be compiled using IL generation or Linq Expressions so that it would be as fast as hand-written code, just without the opportunity for typos.. Intriguing!
// Get a no-frills, run-of-the-mill AutoMapper Configuration reference..
var mapperConfig = new Configuration(
new TypeMapFactory(),
AutoMapper.Mappers.MapperRegistry.AllMappers()
);
mapperConfig.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
// .. teach it the SourceType.Sub1 to DestType.Sub1 mapping (unfortunately AutoMapper can't
// magically handle nested types)
mapperConfig.CreateMap<SourceType.Sub1, ConstructorDestType.Sub1>();
// If the translatorFactory is unable to find any constructors it can use for the conversion,
// the translatorFactory.Get method will return null
var translatorFactory = new SimpleTypeConverterByConstructorFactory(
new ArgsLengthTypeConverterPrioritiserFactory(),
new SimpleConstructorInvokerFactory(),
new AutoMapperEnabledPropertyGetterFactory(
new CaseInsensitiveSkipUnderscoreNameMatcher(),
mapperConfig
)
);
var translator = translatorFactory.Get<SourceType, ConstructorDestType>();
if (translator == null)
throw new Exception("Unable to obtain a mapping");
// Make our translation available to the AutoMapper configuration
mapperConfig.CreateMap<SourceType, ConstructorDestType>().ConstructUsing(translator.Convert);
// Let AutoMapper do its thing!
var dest = (new MappingEngine(mapperConfig)).Map<SourceType, ConstructorDestType>(
new SourceType()
{
Value = new SourceType.Sub1() { Name = "Test1" },
ValueList = new[]
{
new SourceType.Sub1() { Name = "Test2" },
new SourceType.Sub1() { Name = "Test3" }
},
ValueEnum = SourceType.Sub2.EnumValue2
}
);
public class SourceType
{
public Sub1 Value { get; set; }
public IEnumerable<Sub1> ValueList { get; set; }
public Sub2 ValueEnum { get; set; }
public class Sub1
{
public string Name { get; set; }
}
public enum Sub2
{
EnumValue1,
EnumValue2,
EnumValue3
}
}
public class ConstructorDestType
{
private Sub1 _value;
private IEnumerable<Sub1> _valueList;
private Sub2 _valueEnum;
public ConstructorDestType(Sub1 value, IEnumerable<Sub1> valueList, Sub2 valueEnum)
{
if (value == null)
throw new ArgumentNullException("value");
if (valueList == null)
throw new ArgumentNullException("valueList");
if (!Enum.IsDefined(typeof(Sub2), valueEnum))
throw new ArgumentOutOfRangeException("valueEnum");
_value = value;
_valueList = valueList;
_valueEnum = valueEnum;
}
public Sub1 Value { get { return _value; } }
public IEnumerable<Sub1> ValueList { get { return _valueList; } }
public Sub2 ValueEnum { get { return _valueEnum; } }
public class Sub1
{
public string Name { get; set; }
}
public enum Sub2
{
EnumValue1,
EnumValue_2,
EnumValue3
}
}
Posted at 17:58
29 March 2011
Earlier this year I was introduced by someone I work with to AutoMapper. At a very convenient time it turned out since I was in the middle of a couple of projects that I had to do a lot of run-of-the-mill gluing together of request between web services where there were very similar object models in play but which came from different services - so I was looking at writing a load of code that basically took a request from one side and re-formed it into a very similar request to push elsewhere. Not particularly fun, and I find one of the places I'm most like to make stupid mistakes are when I'm not 100% mentally switched on because the task at hand makes me feel like I'm being a robot!
So, for one of these projects I was getting stuck into; AutoMapper to the rescue!
AutoMapper is an "object-to-object" mapper which, well.. maps from one object to another! :) If the source and destination objects have identical structure but different namespaces then most times AutoMapper will be able to translate from one to another as-if-by-magic, and there are several conventions that are applied by the default mapper that perform simple object flattening and other tricks.
There's loads of introductory tutorials out there for AutoMapper so this is just a dead simple example to get across the gist - I can use one call to a CreateMap method and use a nice fluent coding style to tweak it how I want, then conversion between lists or arrays or enumerables of mappable types are automatically handled:
var data = new Employee()
{
Name = new Employee.EmployeeName()
{
Title = "Mr",
First = "Andrew",
Last = "Test",
},
DateOfBirth = new DateTime(1990, 6, 14)
};
Mapper.CreateMap<Employee, Person>()
.ForMember(d => d.Name, o => o.MapFrom(s => s.Name.Title + " " + s.Name.First + " " + s.Name.Last));
var dataList = new Employee[] { data };
var translated = Mapper.Map<Employee[], List<Person>>(dataList);
public class Employee
{
public EmployeeName Name { get; set; }
public DateTime DateOfBirth { get; set; }
public class EmployeeName
{
public string Title { get; set; }
public string First { get; set; }
public string Last { get; set; }
}
}
public class Person
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
This doesn't even scratch the surface; it can handle nested types and complex object models, you can define custom naming conventions for property mappings, specify properties to ignore or map other than to the conventions, map onto existing instances rather than creating new, create distinct configuration instances, .. loads and loads of stuff.
An example of its use out-in-the-field is in the MVC Nerd Dinner demo project and Jimmy Bogard (who wrote AutoMapper) mentions how he uses it in his article "How we do MVC" -
AutoMapper to go from Domain -> ViewModel and Domain -> EditModel. This is again because the view and controller put constraints on our model that we didn't want in our domain. AutoMapper flattened our domain into very discrete ViewModel objects, containing only the data for our view, and only in the shape we want.
.. which sounds like a very sensible application for it to me! (The rest of that article's definitely worth a read, btw).
There was one gotcha using it that caught me out, but it made perfect sense when I reasoned it through afterward.
I was mapping from one large, most-flat object into another where the first was a subset of the second; it was an old legacy webservice where the interface accepted every property for several types of bookings, where maybe 60% of the properties were shared between types and then the rest were specific to different booking types. So a booking made through the web interface resulted in an HotelBooking being instantiated, for example, and this was mapped onto the "super" booking object of the legacy service interface.
var source = new Hotel(
Guid.NewGuid(),
// .. other properties
"Test"
// .. other properties
);
Mapper.CreateMap<Hotel, Booking>();
var dest = Mapper.Map<Hotel, Booking>(source);
public class Hotel
{
public Hotel(Guid id, /* .. other properties .. */ string network)
{
if ((network ?? "").Trim() == "")
throw new ArgumentException("Null/empty network specified");
// .. other validation ..
Id = id;
//.. other properties..
Network = network;
//.. other properties..
}
public Guid Id { get; private set; }
// .. other properties ..
/// <summary>
/// This will never be null
/// </summary>
public string Network { get; private set; }
// .. other properties ..
}
public class Booking
{
public Guid Id { get; set; }
// .. other properties
public string NetworkType { get; set; }
// .. other properties
}
On the translated "dest" instance, the NetworkType property is "System.String" - er, what??
Well it turns out that AutoMapper finds that there is no NetworkType property to map from Hotel to Booking but sees that there is a "Network" value. It then tries to see if it can perform some object flattening by checking whether the Network value has a Type property which, being a string, it doesn't. But it then consider a property retrieval method rather than a standard property getter so it looks for a GetType() method which, since string inherits from objects, it does! So it takes the .Network.GetType() value and assumes we want this for the Booking.NetworkType value!
Like I said, it all makes perfect sense but it took me a little while to work out what was happening in this case :)
Did I mention AutoMapper is open source? This is great cos it let me have a poke around the source code and get a feel for what magic seemed to be going on!
My biggest problem is with scenarios where I want to do the opposite of the above - instead of translating from an "always-valid" internal object to a webservice class I'd like to be able to instantiate a class through its constructor, using data from a source class.
Now, AutoMapper does have some sort of support for using constructors for mapping - eg.
Mapper.CreateMap<Booking, Hotel>()
.ConstructUsing(src => new Hotel(src.Id, /* .. other properties .. */));
But here I've got to manually map all of the properties from the source to arguments in destination's constructor! What I really want is all of that clever name convention malarkey done in AutoMapper to be applied to constructor arguments of destination types. I mean, argument names are always present in compiled C# code so it's not like that data is unavailable for examination by AutoMapper. And having conversions like this would save me having to write a lot of boring code at webservice boundaries!
Now, since I seem to think it's so easy - How Hard Can It Be? :) - I'm going to have a bit of a play around and see if I can slap something together to do this. If I don't end up reduced to tears (and maybe even if I do!) I'll see what I can do about posting the results!
Posted at 21:38
Dan is a big geek who likes making stuff with computers! He can be quite outspoken so clearly needs a blog :)
In the last few minutes he seems to have taken to referring to himself in the third person. He's quite enjoying it.