using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using JD.Reflection.MefImplementations.Hosting.Caching.Internal; namespace JD.Reflection.MefImplementations.Metadata { // // Assume TMetadataView is // // interface Foo // // { // // public typeRecord1 Record1 { get; } // // public typeRecord2 Record2 { get; } // // public typeRecord3 Record3 { get; } // // public typeRecord4 Record4 { get; } // // } // // // // The class to be generated will look approximately like: // public class __Foo__MedataViewProxy : TMetadataView // { // public __Foo__MedataViewProxy(IReadOnlyDictionary metadata) // { // Throw.IfNull(metadata, "metadata"); // // try // { // Record1 = (typeRecord1)Record1; // Record2 = (typeRecord2)Record2; // Record3 = (typeRecord3)Record3; // Record4 = (typeRecord4)Record4; // } // catch(InvalidCastException ice) // { // //Annotate exception .Data with diagnostic info // } // catch(NullReferenceException ice) // { // //Annotate exception .Data with diagnostic info // } // } // // // Interface // public typeRecord1 Record1 { get; } // public typeRecord2 Record2 { get; } // public typeRecord3 Record3 { get; } // public typeRecord4 Record4 { get; } // } internal static class MetadataViewGenerator { #region Constants public const string MetadataViewType = "MetadataViewType"; public const string MetadataItemKey = "MetadataItemKey"; public const string MetadataItemTargetType = "MetadataItemTargetType"; public const string MetadataItemSourceType = "MetadataItemSourceType"; public const string MetadataItemValue = "MetadataItemValue"; #endregion #region Variables private static readonly object ms_lock = new object(); private static readonly Dictionary ms_cache = new Dictionary(); private static readonly AssemblyName ms_proxyAssemblyName = new AssemblyName(string.Format(CultureInfo.InvariantCulture, "MetadataViewProxies_{0}", Guid.NewGuid())); private static readonly AssemblyBuilder ms_proxyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(ms_proxyAssemblyName, AssemblyBuilderAccess.Run); private static readonly ModuleBuilder ms_proxyModuleBuilder = ms_proxyAssemblyBuilder.DefineDynamicModule("MetadataViewProxiesModule"); private static readonly GenerationServices ms_generationServices = new GenerationServices(ms_proxyModuleBuilder); private static readonly Type[] ms_ctorArgumentTypes = { typeof(IReadOnlyDictionary) }; private static readonly ConstructorInfo ms_objectCtor = typeof(object).GetConstructor(Type.EmptyTypes); private static readonly MethodInfo ms_tryGetValueMethodInfo = ms_ctorArgumentTypes[0].GetMethod("TryGetValue"); private static readonly MethodInfo ms_exceptionGetData = typeof(Exception).GetProperty("Data").GetGetMethod(); private static readonly MethodInfo ms_dictionaryAdd = typeof(IDictionary).GetMethod("Add"); private static readonly MethodInfo ms_objectGetType = typeof(object).GetMethod("GetType", Type.EmptyTypes); #endregion #region Methods public static Type GenerateView(Type metadataViewType) { Debug.Assert(metadataViewType != null && metadataViewType.IsInterface); lock (ms_lock) { Type proxyType; if (ms_cache.TryGetValue(metadataViewType, out proxyType)) { return proxyType; } if (!ms_cache.TryGetValue(metadataViewType, out proxyType)) { proxyType = GenerateViewImpl(metadataViewType); ms_cache.Add(metadataViewType, proxyType); } Debug.Assert(proxyType != null); return proxyType; } } #region Generation private static Type GenerateViewImpl(Type metadataViewType) { TypeBuilder proxyTypeBuilder = ms_proxyModuleBuilder.DefineType( string.Format(CultureInfo.InvariantCulture, "_proxy_{0}_{1}", metadataViewType.FullName, Guid.NewGuid()), TypeAttributes.Public, typeof (object), new[] { metadataViewType }); // Implement Constructor ILGenerator proxyCtorIl = CreateGeneratorForPublicConstructor(proxyTypeBuilder, ms_ctorArgumentTypes); LocalBuilder exception = proxyCtorIl.DeclareLocal(typeof(Exception)); LocalBuilder exceptionData = proxyCtorIl.DeclareLocal(typeof(IDictionary)); LocalBuilder sourceType = proxyCtorIl.DeclareLocal(typeof(Type)); LocalBuilder value = proxyCtorIl.DeclareLocal(typeof(object)); LocalBuilder usesExportedMD = proxyCtorIl.DeclareLocal(typeof(bool)); Label tryConstructView = proxyCtorIl.BeginExceptionBlock(); // Implement interface properties foreach (PropertyInfo propertyInfo in GetAllProperties(metadataViewType)) { string fieldName = string.Format(CultureInfo.InvariantCulture, "_{0}_{1}", propertyInfo.Name, Guid.NewGuid()); string propertyName = string.Format(CultureInfo.InvariantCulture, "{0}", propertyInfo.Name); if (propertyInfo.CanWrite) { throw new NotSupportedException( $"The MetadataView '{metadataViewType.FullName}' is invalid because property '{propertyName}' has a property set method."); } if (!propertyInfo.CanRead) { continue; } Type[] propertyTypeArguments = new [] { propertyInfo.PropertyType }; Type[] optionalModifiers = propertyInfo.GetOptionalCustomModifiers(); Type[] requiredModifiers = propertyInfo.GetRequiredCustomModifiers(); Array.Reverse(optionalModifiers); Array.Reverse(requiredModifiers); // Generate field FieldBuilder proxyFieldBuilder = proxyTypeBuilder.DefineField(fieldName, propertyInfo.PropertyType, FieldAttributes.Private); // Generate property PropertyBuilder proxyPropertyBuilder = proxyTypeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyInfo.PropertyType, propertyTypeArguments); // Generate constructor code for retrieving the metadata value and setting the field Label tryCastValue = proxyCtorIl.BeginExceptionBlock(); var defaultValueAttribute = propertyInfo.GetAttribute(false); if (defaultValueAttribute != null) { proxyCtorIl.BeginExceptionBlock(); } // In constructor set the backing field with the value from the dictionary Label doneGettingDefaultValue = proxyCtorIl.DefineLabel(); GenerateLocalAssignmentFromFlag(proxyCtorIl, usesExportedMD, true); proxyCtorIl.Emit(OpCodes.Ldarg_1); proxyCtorIl.Emit(OpCodes.Ldstr, propertyInfo.Name); proxyCtorIl.Emit(OpCodes.Ldloca, value); proxyCtorIl.Emit(OpCodes.Callvirt, ms_tryGetValueMethodInfo); proxyCtorIl.Emit(OpCodes.Brtrue, doneGettingDefaultValue); proxyCtorIl.GenerateLocalAssignmentFromFlag(usesExportedMD, false); if (defaultValueAttribute != null) { proxyCtorIl.GenerateLocalAssignmentFromDefaultAttribute(defaultValueAttribute, value); } proxyCtorIl.MarkLabel(doneGettingDefaultValue); proxyCtorIl.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder); proxyCtorIl.Emit(OpCodes.Leave, tryCastValue); if (defaultValueAttribute != null) { proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException)); { Label notUsesExportedMd = proxyCtorIl.DefineLabel(); proxyCtorIl.Emit(OpCodes.Ldloc, usesExportedMD); proxyCtorIl.Emit(OpCodes.Brtrue, notUsesExportedMd); proxyCtorIl.Emit(OpCodes.Rethrow); proxyCtorIl.MarkLabel(notUsesExportedMd); proxyCtorIl.GenerateLocalAssignmentFromDefaultAttribute(defaultValueAttribute, value); proxyCtorIl.GenerateFieldAssignmentFromLocalValue(value, proxyFieldBuilder); } proxyCtorIl.EndExceptionBlock(); } // catch blocks for tryCast start here proxyCtorIl.BeginCatchBlock(typeof(NullReferenceException)); { proxyCtorIl.Emit(OpCodes.Stloc, exception); proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType); proxyCtorIl.Emit(OpCodes.Rethrow); } proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException)); { proxyCtorIl.Emit(OpCodes.Stloc, exception); proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemKey, propertyName); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataItemTargetType, propertyInfo.PropertyType); proxyCtorIl.Emit(OpCodes.Rethrow); } proxyCtorIl.EndExceptionBlock(); // Generate "get" method implementation. MethodBuilder getMethodBuilder = proxyTypeBuilder.DefineMethod( string.Format(CultureInfo.InvariantCulture, "get_{0}", propertyName), MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final, CallingConventions.HasThis, propertyInfo.PropertyType, requiredModifiers, optionalModifiers, Type.EmptyTypes, null, null); proxyTypeBuilder.DefineMethodOverride(getMethodBuilder, propertyInfo.GetGetMethod()); ILGenerator getMethodIL = getMethodBuilder.GetILGenerator(); getMethodIL.Emit(OpCodes.Ldarg_0); getMethodIL.Emit(OpCodes.Ldfld, proxyFieldBuilder); getMethodIL.Emit(OpCodes.Ret); proxyPropertyBuilder.SetGetMethod(getMethodBuilder); } proxyCtorIl.Emit(OpCodes.Leave, tryConstructView); // catch blocks for constructView start here proxyCtorIl.BeginCatchBlock(typeof(NullReferenceException)); { proxyCtorIl.Emit(OpCodes.Stloc, exception); proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataViewType, metadataViewType); proxyCtorIl.Emit(OpCodes.Rethrow); } proxyCtorIl.BeginCatchBlock(typeof(InvalidCastException)); { proxyCtorIl.Emit(OpCodes.Stloc, exception); proxyCtorIl.GetExceptionDataAndStoreInLocal(exception, exceptionData); proxyCtorIl.Emit(OpCodes.Ldloc, value); proxyCtorIl.Emit(OpCodes.Call, ms_objectGetType); proxyCtorIl.Emit(OpCodes.Stloc, sourceType); proxyCtorIl.AddItemToLocalDictionary(exceptionData, MetadataViewType, metadataViewType); proxyCtorIl.AddLocalToLocalDictionary(exceptionData, MetadataItemSourceType, sourceType); proxyCtorIl.AddLocalToLocalDictionary(exceptionData, MetadataItemValue, value); proxyCtorIl.Emit(OpCodes.Rethrow); } proxyCtorIl.EndExceptionBlock(); // Finished implementing interface and constructor proxyCtorIl.Emit(OpCodes.Ret); return proxyTypeBuilder.CreateType(); } private static IEnumerable GetAllProperties(Type type) { return type.GetInterfaces().Concat(new [] { type }).SelectMany(iface => iface.GetProperties()); } private static ILGenerator CreateGeneratorForPublicConstructor(TypeBuilder typeBuilder, Type[] ctrArgumentTypes) { ConstructorBuilder ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, ctrArgumentTypes); ILGenerator ctorIL = ctorBuilder.GetILGenerator(); ctorIL.Emit(OpCodes.Ldarg_0); ctorIL.Emit(OpCodes.Call, ms_objectCtor); return ctorIL; } private static void GenerateLocalAssignmentFromFlag(this ILGenerator il, LocalBuilder local, bool flag) { il.Emit(flag ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, local); } private static void GenerateLocalAssignmentFromDefaultAttribute(this ILGenerator il, DefaultValueAttribute attribute, LocalBuilder local) { Debug.Assert(attribute != null); ms_generationServices.LoadValue(il, attribute.Value); if (attribute.Value != null && attribute.Value.GetType().IsValueType) { il.Emit(OpCodes.Box, attribute.Value.GetType()); } il.Emit(OpCodes.Stloc, local); } private static void GenerateFieldAssignmentFromLocalValue(this ILGenerator il, LocalBuilder local, FieldInfo field) { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldloc, local); il.Emit(field.FieldType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, field.FieldType); il.Emit(OpCodes.Stfld, field); } private static void GetExceptionDataAndStoreInLocal(this ILGenerator ilGenerator, LocalBuilder exception, LocalBuilder dataStore) { ilGenerator.Emit(OpCodes.Ldloc, exception); ilGenerator.Emit(OpCodes.Callvirt, ms_exceptionGetData); ilGenerator.Emit(OpCodes.Stloc, dataStore); } private static void AddItemToLocalDictionary(this ILGenerator ilGenerator, LocalBuilder dictionary, object key, object value) { ilGenerator.Emit(OpCodes.Ldloc, dictionary); ms_generationServices.LoadValue(ilGenerator, key); ms_generationServices.LoadValue(ilGenerator, value); ilGenerator.Emit(OpCodes.Callvirt, ms_dictionaryAdd); } private static void AddLocalToLocalDictionary(this ILGenerator ilGenerator, LocalBuilder dictionary, object key, LocalBuilder value) { ilGenerator.Emit(OpCodes.Ldloc, dictionary); ms_generationServices.LoadValue(ilGenerator, key); ilGenerator.Emit(OpCodes.Ldloc, value); ilGenerator.Emit(OpCodes.Callvirt, ms_dictionaryAdd); } #endregion #endregion } }