tools/AutoMapper/Execution/ProxyGenerator.cs (208 lines of code) (raw):
namespace AutoMapper.Execution
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text.RegularExpressions;
public static class ProxyGenerator
{
private static readonly byte[] privateKey =
StringToByteArray(
"002400000480000094000000060200000024000052534131000400000100010079dfef85ed6ba841717e154f13182c0a6029a40794a6ecd2886c7dc38825f6a4c05b0622723a01cd080f9879126708eef58f134accdc99627947425960ac2397162067507e3c627992aa6b92656ad3380999b30b5d5645ba46cc3fcc6a1de5de7afebcf896c65fb4f9547a6c0c6433045fceccb1fa15e960d519d0cd694b29a4");
private static readonly byte[] privateKeyToken = StringToByteArray("be96cd2c38ef1005");
private static readonly MethodInfo delegate_Combine = typeof(Delegate).GetDeclaredMethod("Combine", new[] { typeof(Delegate), typeof(Delegate) });
private static readonly MethodInfo delegate_Remove = typeof(Delegate).GetDeclaredMethod("Remove", new[] { typeof(Delegate), typeof(Delegate) });
private static readonly EventInfo iNotifyPropertyChanged_PropertyChanged =
typeof(INotifyPropertyChanged).GetRuntimeEvent("PropertyChanged");
private static readonly ConstructorInfo proxyBase_ctor =
typeof(ProxyBase).GetDeclaredConstructor(new Type[0]);
private static readonly ModuleBuilder proxyModule = CreateProxyModule();
private static readonly LockingConcurrentDictionary<TypeDescription, Type> proxyTypes = new LockingConcurrentDictionary<TypeDescription, Type>(EmitProxy);
private static ModuleBuilder CreateProxyModule()
{
AssemblyName name = new AssemblyName("AutoMapper.Proxies");
name.SetPublicKey(privateKey);
name.SetPublicKeyToken(privateKeyToken);
#if NET40
AssemblyBuilder builder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
#else
AssemblyBuilder builder = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
#endif
return builder.DefineDynamicModule("AutoMapper.Proxies.emit");
}
private static Type EmitProxy(TypeDescription typeDescription)
{
var interfaceType = typeDescription.Type;
var additionalProperties = typeDescription.AdditionalProperties;
var propertyNames = string.Join("_", additionalProperties.Select(p => p.Name));
string name =
$"Proxy{propertyNames}<{Regex.Replace(interfaceType.AssemblyQualifiedName ?? interfaceType.FullName ?? interfaceType.Name, @"[\s,]+", "_")}>";
var allInterfaces = new List<Type> { interfaceType };
allInterfaces.AddRange(interfaceType.GetTypeInfo().ImplementedInterfaces);
Debug.WriteLine(name, "Emitting proxy type");
TypeBuilder typeBuilder = proxyModule.DefineType(name,
TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public, typeof(ProxyBase),
interfaceType.IsInterface() ? new[] { interfaceType } : new Type[0]);
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public,
CallingConventions.Standard, new Type[0]);
ILGenerator ctorIl = constructorBuilder.GetILGenerator();
ctorIl.Emit(OpCodes.Ldarg_0);
ctorIl.Emit(OpCodes.Call, proxyBase_ctor);
ctorIl.Emit(OpCodes.Ret);
FieldBuilder propertyChangedField = null;
if(typeof(INotifyPropertyChanged).IsAssignableFrom(interfaceType))
{
propertyChangedField = typeBuilder.DefineField("PropertyChanged", typeof(PropertyChangedEventHandler),
FieldAttributes.Private);
MethodBuilder addPropertyChangedMethod = typeBuilder.DefineMethod("add_PropertyChanged",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.NewSlot | MethodAttributes.Virtual, typeof(void),
new[] { typeof(PropertyChangedEventHandler) });
ILGenerator addIl = addPropertyChangedMethod.GetILGenerator();
addIl.Emit(OpCodes.Ldarg_0);
addIl.Emit(OpCodes.Dup);
addIl.Emit(OpCodes.Ldfld, propertyChangedField);
addIl.Emit(OpCodes.Ldarg_1);
addIl.Emit(OpCodes.Call, delegate_Combine);
addIl.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
addIl.Emit(OpCodes.Stfld, propertyChangedField);
addIl.Emit(OpCodes.Ret);
MethodBuilder removePropertyChangedMethod = typeBuilder.DefineMethod("remove_PropertyChanged",
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.NewSlot | MethodAttributes.Virtual, typeof(void),
new[] { typeof(PropertyChangedEventHandler) });
ILGenerator removeIl = removePropertyChangedMethod.GetILGenerator();
removeIl.Emit(OpCodes.Ldarg_0);
removeIl.Emit(OpCodes.Dup);
removeIl.Emit(OpCodes.Ldfld, propertyChangedField);
removeIl.Emit(OpCodes.Ldarg_1);
removeIl.Emit(OpCodes.Call, delegate_Remove);
removeIl.Emit(OpCodes.Castclass, typeof(PropertyChangedEventHandler));
removeIl.Emit(OpCodes.Stfld, propertyChangedField);
removeIl.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(addPropertyChangedMethod,
iNotifyPropertyChanged_PropertyChanged.GetAddMethod());
typeBuilder.DefineMethodOverride(removePropertyChangedMethod,
iNotifyPropertyChanged_PropertyChanged.GetRemoveMethod());
}
var propertiesToImplement = new List<PropertyDescription>();
// first we collect all properties, those with setters before getters in order to enable less specific redundant getters
foreach(var property in
allInterfaces.Where(intf => intf != typeof(INotifyPropertyChanged))
.SelectMany(intf => intf.GetProperties())
.Select(p => new PropertyDescription(p))
.Concat(additionalProperties))
{
if(property.CanWrite)
{
propertiesToImplement.Insert(0, property);
}
else
{
propertiesToImplement.Add(property);
}
}
var fieldBuilders = new Dictionary<string, PropertyEmitter>();
foreach(var property in propertiesToImplement)
{
PropertyEmitter propertyEmitter;
if(fieldBuilders.TryGetValue(property.Name, out propertyEmitter))
{
if((propertyEmitter.PropertyType != property.Type) &&
((property.CanWrite) || (!property.Type.IsAssignableFrom(propertyEmitter.PropertyType))))
{
throw new ArgumentException(
$"The interface has a conflicting property {property.Name}",
nameof(interfaceType));
}
}
else
{
fieldBuilders.Add(property.Name,
propertyEmitter =
new PropertyEmitter(typeBuilder, property, propertyChangedField));
}
}
return typeBuilder.CreateType();
}
public static Type GetProxyType(Type interfaceType)
{
var key = new TypeDescription(interfaceType);
if(!interfaceType.IsInterface())
{
throw new ArgumentException("Only interfaces can be proxied", nameof(interfaceType));
}
return proxyTypes.GetOrAdd(key);
}
public static Type GetSimilarType(Type sourceType, IEnumerable<PropertyDescription> additionalProperties)
{
return proxyTypes.GetOrAdd(new TypeDescription(sourceType, additionalProperties));
}
private static byte[] StringToByteArray(string hex)
{
int numberChars = hex.Length;
byte[] bytes = new byte[numberChars / 2];
for(int i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
}
public struct TypeDescription : IEquatable<TypeDescription>
{
public TypeDescription(Type type) : this(type, PropertyDescription.Empty)
{
}
public TypeDescription(Type type, IEnumerable<PropertyDescription> additionalProperties)
{
Type = type ?? throw new ArgumentNullException(nameof(type));
AdditionalProperties = additionalProperties?.ToArray() ?? throw new ArgumentNullException(nameof(additionalProperties));
}
public Type Type { get; }
public PropertyDescription[] AdditionalProperties { get; }
public override int GetHashCode()
{
var hashCode = Type.GetHashCode();
foreach(var property in AdditionalProperties)
{
hashCode = HashCodeCombiner.CombineCodes(hashCode, property.GetHashCode());
}
return hashCode;
}
public override bool Equals(object other) => other is TypeDescription && Equals((TypeDescription)other);
public bool Equals(TypeDescription other) => Type == other.Type && AdditionalProperties.SequenceEqual(other.AdditionalProperties);
public static bool operator ==(TypeDescription left, TypeDescription right) => left.Equals(right);
public static bool operator !=(TypeDescription left, TypeDescription right) => !left.Equals(right);
}
[DebuggerDisplay("{Name}-{Type.Name}")]
public struct PropertyDescription : IEquatable<PropertyDescription>
{
internal static PropertyDescription[] Empty = new PropertyDescription[0];
public PropertyDescription(string name, Type type, bool canWrite = true)
{
Name = name;
Type = type;
CanWrite = canWrite;
}
public PropertyDescription(PropertyInfo property)
{
Name = property.Name;
Type = property.PropertyType;
CanWrite = property.CanWrite;
}
public string Name { get; }
public Type Type { get; }
public bool CanWrite { get; }
public override int GetHashCode()
{
var code = HashCodeCombiner.Combine(Name, Type);
return HashCodeCombiner.CombineCodes(code, CanWrite.GetHashCode());
}
public override bool Equals(object other) => other is PropertyDescription && Equals((PropertyDescription)other);
public bool Equals(PropertyDescription other) => Name == other.Name && Type == other.Type && CanWrite == other.CanWrite;
public static bool operator ==(PropertyDescription left, PropertyDescription right) => left.Equals(right);
public static bool operator !=(PropertyDescription left, PropertyDescription right) => !left.Equals(right);
}
}