347 lines
No EOL
16 KiB
C#
347 lines
No EOL
16 KiB
C#
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<string, object> 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<Type, Type> ms_cache = new Dictionary<Type, Type>();
|
|
|
|
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<string, object>) };
|
|
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<DefaultValueAttribute>(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<PropertyInfo> 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
|
|
}
|
|
} |