From 101ba8f620fd97cbadee252d7e88d759692ee4b6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 27 Sep 2017 21:16:09 +0200 Subject: [PATCH] Facade cleanup and refactoring --- ...ttribute.cs => PublishedModelAttribute.cs} | 62 ++--- .../PublishedContent/PublishedModelFactory.cs | 32 ++- src/Umbraco.Core/ReflectionUtilities.cs | 255 ++++++++++++++---- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../CtorInvokeBenchmarks.cs | 2 + .../ReflectionTests.cs | 130 ++++----- .../Clr/ReflectionUtilitiesTests.cs | 126 +++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 3 +- src/Umbraco.Web.UI/web.Template.Debug.config | 26 +- .../NestedContentManyValueConverter.cs | 2 +- 10 files changed, 452 insertions(+), 188 deletions(-) rename src/Umbraco.Core/Models/PublishedContent/{PublishedContentModelAttribute.cs => PublishedModelAttribute.cs} (97%) rename src/Umbraco.Tests/{DynamicsAndReflection => Clr}/ReflectionTests.cs (63%) create mode 100644 src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs similarity index 97% rename from src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs rename to src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs index 2e0b037a94..4b4ce120c6 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentModelAttribute.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelAttribute.cs @@ -1,31 +1,31 @@ -using System; -using Umbraco.Core.Exceptions; - -namespace Umbraco.Core.Models.PublishedContent -{ - /// - /// - /// Indicates that the class is a published content model for a specified content type. - /// - /// By default, the name of the class is assumed to be the content type alias. The - /// PublishedContentModelAttribute can be used to indicate a different alias. - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class PublishedContentModelAttribute : Attribute - { - /// - /// - /// Initializes a new instance of the class with a content type alias. - /// - /// The content type alias. - public PublishedContentModelAttribute(string contentTypeAlias) - { - if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); - ContentTypeAlias = contentTypeAlias; - } - - /// - /// Gets or sets the content type alias. - /// - public string ContentTypeAlias { get; } - } -} +using System; +using Umbraco.Core.Exceptions; + +namespace Umbraco.Core.Models.PublishedContent +{ + /// + /// + /// Indicates that the class is a published content model for a specified content type. + /// + /// By default, the name of the class is assumed to be the content type alias. The + /// PublishedContentModelAttribute can be used to indicate a different alias. + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class PublishedContentModelAttribute : Attribute + { + /// + /// + /// Initializes a new instance of the class with a content type alias. + /// + /// The content type alias. + public PublishedContentModelAttribute(string contentTypeAlias) + { + if (string.IsNullOrWhiteSpace(contentTypeAlias)) throw new ArgumentNullOrEmptyException(nameof(contentTypeAlias)); + ContentTypeAlias = contentTypeAlias; + } + + /// + /// Gets or sets the content type alias. + /// + public string ContentTypeAlias { get; } + } +} diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs index 4aebeb1a3e..4c86848e5a 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedModelFactory.cs @@ -44,13 +44,27 @@ namespace Umbraco.Core.Models.PublishedContent foreach (var type in types) { + // fixme - annoying - we want the xpression to be of a give type + // fixme - but then do we need ctor(IPublishedElement x) and what about ctor(IPublishedContent x)? + //var expr = ReflectionUtilities.GetCtorExpr>(type, false); + //if (expr == null) + // throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type IPublishedElement."); + + //var ccc = ReflectionUtilities.EmitCtor(type, false); + //if (ccc == null) + // throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type IPublishedElement."); + + // so... the model type has to implement a ctor with one parameter being, or inheriting from, + // IPublishedElement - but it can be IPublishedContent - so we cannot get one precise ctor, + // we have to iterate over all ctors and try to find the right one + ConstructorInfo constructor = null; Type parameterType = null; foreach (var ctor in type.GetConstructors()) { var parms = ctor.GetParameters(); - if (parms.Length == 1 && typeof (IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) + if (parms.Length == 1 && typeof(IPublishedElement).IsAssignableFrom(parms[0].ParameterType)) { if (constructor != null) throw new InvalidOperationException($"Type {type.FullName} has more than one public constructor with one argument of type, or implementing, IPublishedElement."); @@ -62,21 +76,21 @@ namespace Umbraco.Core.Models.PublishedContent if (constructor == null) throw new InvalidOperationException($"Type {type.FullName} is missing a public constructor with one argument of type, or implementing, IPublishedElement."); - var attribute = type.GetCustomAttribute(false); // fixme rename FacadeModelAttribute + var attribute = type.GetCustomAttribute(false); var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias; - if (modelInfos.TryGetValue(typeName, out ModelInfo modelInfo)) + if (modelInfos.TryGetValue(typeName, out var modelInfo)) throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\"."); - exprs.Add(Expression.Lambda>(Expression.New(constructor))); - modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type }; + //exprs.Add(Expression.Lambda>(Expression.New(constructor))); + modelInfos[typeName] = new ModelInfo { ParameterType = parameterType, ModelType = type, Ctor = ReflectionUtilities.EmitCtor>(constructor) }; ModelTypeMap[typeName] = type; } - var compiled = ReflectionUtilities.CompileToDelegates(exprs.ToArray()); - var i = 0; - foreach (var modelInfo in modelInfos.Values) - modelInfo.Ctor = compiled[i++]; + //var compiled = ReflectionUtilities.CompileToDelegates(exprs.ToArray()); + //var i = 0; + //foreach (var modelInfo in modelInfos.Values) + // modelInfo.Ctor = compiled[i++]; _modelInfos = modelInfos.Count > 0 ? modelInfos : null; } diff --git a/src/Umbraco.Core/ReflectionUtilities.cs b/src/Umbraco.Core/ReflectionUtilities.cs index 45d0b05cef..cc544b4dd2 100644 --- a/src/Umbraco.Core/ReflectionUtilities.cs +++ b/src/Umbraco.Core/ReflectionUtilities.cs @@ -12,10 +12,13 @@ namespace Umbraco.Core /// public static class ReflectionUtilities { + public const AssemblyBuilderAccess DefaultAssemblyBuilderAccess = AssemblyBuilderAccess.Run; + public const AssemblyBuilderAccess NoAssembly = 0; + public static Func GetPropertyGetter(string propertyName) { - var type = typeof(TInstance); - var type0 = typeof(TValue); + var type = typeof (TInstance); + var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); @@ -24,8 +27,8 @@ namespace Umbraco.Core public static Action GetPropertySetter(string propertyName) { - var type = typeof(TInstance); - var type0 = typeof(TValue); + var type = typeof (TInstance); + var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); @@ -34,8 +37,8 @@ namespace Umbraco.Core public static void GetPropertyGetterSetter(string propertyName, out Func getter, out Action setter) { - var type = typeof(TInstance); - var type0 = typeof(TValue); + var type = typeof (TInstance); + var type0 = typeof (TValue); var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (property == null || property.PropertyType != type0) throw new InvalidOperationException($"Could not get property {type}.{propertyName} : {type0}."); @@ -43,40 +46,155 @@ namespace Umbraco.Core setter = GetPropertySetter(property); } - public static Func GetCtor() + public static Expression> GetCtorExpr(bool mustExist = true) { - var type = typeof(TInstance); + var type = typeof (TInstance); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (ctor == null) - throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + { + if (!mustExist) return null; + throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + } var exprNew = Expression.New(ctor); - var expr = Expression.Lambda>(exprNew); - return expr.CompileToDelegate(); + return Expression.Lambda>(exprNew); } - public static Func GetCtor(Type type) + public static Expression GetCtorExpr(Type type, bool mustExist = true) { // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (ctor == null) + { + if (!mustExist) return null; throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + } var exprNew = Expression.New(ctor); - var expr = Expression.Lambda>(exprNew); - return expr.CompileToDelegate(); + return Expression.Lambda(exprNew); } + public static Func GetCtor(bool mustExist = true, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) + { + var expr = GetCtorExpr(mustExist); + if (expr == null) return null; + return access == NoAssembly ? expr.Compile() : CompileToDelegate(expr, access); + } + + public static TLambda GetCtor(Type type, bool mustExist = true, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) + { + var expr = GetCtorExpr(type, mustExist); + if (expr == null) return default(TLambda); + return access == NoAssembly ? expr.Compile() : CompileToDelegate(expr, access); + } + + public static Func EmitCtor(bool mustExist = true) + { + var type = typeof (TInstance); + + // get the constructor infos + var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, + null, Type.EmptyTypes, null); + + if (ctor == null) + { + if (!mustExist) return null; + throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + } + + var dm = new DynamicMethod(string.Empty, type, Array.Empty(), type.Module, true); + var gen = dm.GetILGenerator(); + gen.Emit(OpCodes.Newobj, ctor); + gen.Emit(OpCodes.Ret); + return (Func) dm.CreateDelegate(typeof (Func)); + } + + public static Func EmitCtor(bool mustExist = true) + { + var type = typeof (TInstance); + var type0 = typeof (TArg0); + + // get the constructor infos + var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, + null, new[] { type0 }, null); + + if (ctor == null) + { + if (!mustExist) return null; + throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + } + + var dm = new DynamicMethod(string.Empty, type, new[] { type0 }, type.Module, true); + var gen = dm.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Newobj, ctor); + gen.Emit(OpCodes.Ret); + return (Func) dm.CreateDelegate(typeof (Func)); + } + + public static Func EmitCtor(Type type, bool mustExist = true) + { + var type0 = typeof (TArg0); + + // get the constructor infos + var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, + null, new[] { type0 }, null); + + if (ctor == null) + { + if (!mustExist) return null; + throw new InvalidOperationException($"Could not find constructor {type}.ctor()."); + } + + var dm = new DynamicMethod(string.Empty, type, new[] { type0 }, type.Module, true); + var gen = dm.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Newobj, ctor); + gen.Emit(OpCodes.Ret); + return (Func) dm.CreateDelegate(typeof (Func)); + } + + public static TLambda EmitCtor(ConstructorInfo constructorInfo) + { + var type = constructorInfo.DeclaringType; + var args = constructorInfo.GetParameters().Select(x => x.ParameterType).ToArray(); + + var dm = new DynamicMethod(string.Empty, type, args, type.Module, true); + var gen = dm.GetILGenerator(); + + if (args.Length < 5) + { + if (args.Length > 0) + gen.Emit(OpCodes.Ldarg_0); + if (args.Length > 1) + gen.Emit(OpCodes.Ldarg_1); + if (args.Length > 2) + gen.Emit(OpCodes.Ldarg_2); + if (args.Length > 3) + gen.Emit(OpCodes.Ldarg_3); + } + else + { + for (var i = 0; i < args.Length; i++) + gen.Emit(OpCodes.Ldarg, i); + } + gen.Emit(OpCodes.Newobj, constructorInfo); + gen.Emit(OpCodes.Ret); + return (TLambda) (object) dm.CreateDelegate(typeof (TLambda)); + } + + // fixme others need it too? + public static Func GetCtor() { - var type = typeof(TInstance); - var type0 = typeof(TArg0); + var type = typeof (TInstance); + var type0 = typeof (TArg0); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, @@ -94,9 +212,9 @@ namespace Umbraco.Core public static Func GetCtor() { - var type = typeof(TInstance); - var type0 = typeof(TArg0); - var type1 = typeof(TArg1); + var type = typeof (TInstance); + var type0 = typeof (TArg0); + var type1 = typeof (TArg1); // get the constructor infos var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, @@ -130,7 +248,7 @@ namespace Umbraco.Core public static TMethod GetMethod(string methodName) { - var type = typeof(TInstance); + var type = typeof (TInstance); GetMethodParms(out var parameterTypes, out var returnType); @@ -142,7 +260,7 @@ namespace Umbraco.Core private static Func GetPropertyGetter(PropertyInfo property) { - var type = typeof(TInstance); + var type = typeof (TInstance); var getMethod = property.GetMethod; if (getMethod == null) @@ -156,14 +274,14 @@ namespace Umbraco.Core private static Action GetPropertySetter(PropertyInfo property) { - var type = typeof(TInstance); + var type = typeof (TInstance); var setMethod = property.SetMethod; if (setMethod == null) throw new InvalidOperationException($"Property {type}.{property.Name} : {property.PropertyType} does not have a setter."); var exprThis = Expression.Parameter(type, "this"); - var exprArg0 = Expression.Parameter(typeof(TValue), "value"); + var exprArg0 = Expression.Parameter(typeof (TValue), "value"); var exprCall = Expression.Call(exprThis, setMethod, exprArg0); var expr = Expression.Lambda>(exprCall, exprThis, exprArg0); return expr.CompileToDelegate(); @@ -171,14 +289,16 @@ namespace Umbraco.Core private static void GetMethodParms(out Type[] parameterTypes, out Type returnType) { - var typeM = typeof(TMethod); + var typeM = typeof (TMethod); var typeList = new List(); - returnType = typeof(void); + returnType = typeof (void); - if (!typeof(MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof(MulticastDelegate)) + if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate)) throw new InvalidOperationException("Invalid TMethod, must be a Func or Action."); var typeName = typeM.FullName; + if (typeName == null) + throw new InvalidOperationException($"Could not get {typeM} type name."); if (typeName.StartsWith("System.Func`")) { var i = 0; @@ -204,20 +324,22 @@ namespace Umbraco.Core private static void GetMethodParms(out Type[] parameterTypes, out Type returnType) { - var type = typeof(TInstance); + var type = typeof (TInstance); - var typeM = typeof(TMethod); + var typeM = typeof (TMethod); var typeList = new List(); - returnType = typeof(void); + returnType = typeof (void); - if (!typeof(MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof(MulticastDelegate)) + if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate)) throw new InvalidOperationException("Invalid TMethod, must be a Func or Action."); var typeName = typeM.FullName; + if (typeName == null) + throw new InvalidOperationException($"Could not get {typeM} type name."); if (!typeM.IsGenericType) - throw new InvalidOperationException(typeName); + throw new InvalidOperationException($"Type {typeName} is not generic."); if (typeM.GenericTypeArguments[0] != type) - throw new InvalidOperationException("Invalid TMethod, the first generic argument must be TInstance."); + throw new InvalidOperationException($"Invalid type {typeName}, the first generic argument must be {type.FullName}."); if (typeName.StartsWith("System.Func`")) { var i = 1; @@ -435,11 +557,19 @@ namespace Umbraco.Core propInfo.SetValue(obj, val, null); } - */ + */ - public static Action CompileToDelegate(Expression expr) + // fixme dont think this can work at all + public static TLambda Compile(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + return access == NoAssembly + ? expr.Compile() + : CompileToDelegate(expr, access); + } + + public static Action CompileToDelegate(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) + { + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static); // CompileToMethod requires a static method @@ -449,9 +579,9 @@ namespace Umbraco.Core return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } - public static Action CompileToDelegate(Expression> expr) + public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method @@ -462,35 +592,35 @@ namespace Umbraco.Core return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } - public static Action CompileToDelegate(Expression> expr) + public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method - typeof (void), new[] { typeof(T1), typeof (T2) }); + typeof (void), new[] { typeof (T1), typeof (T2) }); expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } - public static Action CompileToDelegate(Expression> expr) + public static Action CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method - typeof (void), new[] { typeof (T1), typeof (T2) , typeof (T3) }); + typeof (void), new[] { typeof (T1), typeof (T2), typeof (T3) }); expr.CompileToMethod(builder); return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method")); } - public static Func CompileToDelegate(Expression> expr) + public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, @@ -501,9 +631,9 @@ namespace Umbraco.Core return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } - public static Func CompileToDelegate(Expression> expr) + public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method @@ -511,25 +641,25 @@ namespace Umbraco.Core expr.CompileToMethod(builder); - return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); + return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } - public static Func CompileToDelegate(Expression> expr) + public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method - typeof(TResult), new[] { typeof (T1), typeof(T2) }); + typeof (TResult), new[] { typeof (T1), typeof (T2) }); expr.CompileToMethod(builder); return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } - public static Func CompileToDelegate(Expression> expr) + public static Func CompileToDelegate(Expression> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); var builder = typeBuilder.DefineMethod("Method", MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method @@ -540,9 +670,9 @@ namespace Umbraco.Core return (Func) Delegate.CreateDelegate(typeof (Func), typeBuilder.CreateType().GetMethod("Method")); } - public static TMethod CompileToDelegate(Expression expr) + public static TMethod CompileToDelegate(Expression expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); GetMethodParms(out var parameterTypes, out var returnType); @@ -556,8 +686,11 @@ namespace Umbraco.Core } public static TMethod[] CompileToDelegates(params Expression[] exprs) + => CompileToDelegates(AssemblyBuilderAccess.RunAndCollect, exprs); + + public static TMethod[] CompileToDelegates(AssemblyBuilderAccess access, params Expression[] exprs) { - var typeBuilder = CreateTypeBuilder(); + var typeBuilder = CreateTypeBuilder(access); GetMethodParms(out var parameterTypes, out var returnType); @@ -579,11 +712,15 @@ namespace Umbraco.Core return methods; } - private static TypeBuilder CreateTypeBuilder() + private static TypeBuilder CreateTypeBuilder(AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess) { var assemblyName = new AssemblyName("Umbraco.Core.DynamicAssemblies." + Guid.NewGuid().ToString("N")); - var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save); - var module = assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll"); + var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, access); + + var module = (access & AssemblyBuilderAccess.Save) > 0 + ? assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll") + : assembly.DefineDynamicModule(assemblyName.Name); // has to be transient + return module.DefineType("Class", TypeAttributes.Public | TypeAttributes.Abstract); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index be744de397..7a7664f5c4 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -652,7 +652,7 @@ - + diff --git a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs index 46fe21f2c9..8fc7c92b7d 100644 --- a/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/CtorInvokeBenchmarks.cs @@ -140,6 +140,8 @@ namespace Umbraco.Tests.Benchmarks // but, our utilities know how to do it! _expressionMethod3 = expr.CompileToDelegate(); _expressionMethod4 = ReflectionUtilities.GetCtor(); + + // however, unfortunately, the generated "compiled to delegate" code cannot access private stuff :( } public IFoo IlCtor(IFoo foo) diff --git a/src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs b/src/Umbraco.Tests/Clr/ReflectionTests.cs similarity index 63% rename from src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs rename to src/Umbraco.Tests/Clr/ReflectionTests.cs index 974ae5ddfd..af721ae3d8 100644 --- a/src/Umbraco.Tests/DynamicsAndReflection/ReflectionTests.cs +++ b/src/Umbraco.Tests/Clr/ReflectionTests.cs @@ -1,73 +1,57 @@ -using System.Linq; -using NUnit.Framework; -using Umbraco.Core; - -namespace Umbraco.Tests.DynamicsAndReflection -{ - [TestFixture] - public class ReflectionTests - { - [Test] - public void GetBaseTypesIsOk() - { - // tests that the GetBaseTypes extension method works. - - var type = typeof(Class2); - var types = type.GetBaseTypes(true).ToArray(); - Assert.AreEqual(3, types.Length); - Assert.Contains(typeof(Class2), types); - Assert.Contains(typeof(Class1), types); - Assert.Contains(typeof(object), types); - - types = type.GetBaseTypes(false).ToArray(); - Assert.AreEqual(2, types.Length); - Assert.Contains(typeof(Class1), types); - Assert.Contains(typeof(object), types); - } - - [Test] - public void GetInterfacesIsOk() - { - // tests that GetInterfaces gets _all_ interfaces - // so the AllInterfaces extension method is useless - - var type = typeof(Class2); - var interfaces = type.GetInterfaces(); - Assert.AreEqual(2, interfaces.Length); - Assert.Contains(typeof(IInterface1), interfaces); - Assert.Contains(typeof(IInterface2), interfaces); - } - - // TypeExtensions.AllInterfaces was broken an not used, has been commented out - // - //[Test] - //public void AllInterfacesIsBroken() - //{ - // // tests that the AllInterfaces extension method is broken - // - // var type = typeof(Class2); - // var interfaces = type.AllInterfaces().ToArray(); - // Assert.AreEqual(3, interfaces.Length); // should be 2! - // Assert.Contains(typeof(IInterface1), interfaces); - // Assert.Contains(typeof(IInterface2), interfaces); - // Assert.AreEqual(2, interfaces.Count(i => i == typeof(IInterface1))); // duplicate! - // Assert.AreEqual(1, interfaces.Count(i => i == typeof(IInterface2))); - //} - - interface IInterface1 - { } - - interface IInterface2 : IInterface1 - { - void Method(); - } - - class Class1 : IInterface2 - { - public void Method() { } - } - - class Class2 : Class1 - { } - } -} +using System.Linq; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests.Clr +{ + [TestFixture] + public class ReflectionTests + { + [Test] + public void GetBaseTypesIsOk() + { + // tests that the GetBaseTypes extension method works. + + var type = typeof(Class2); + var types = type.GetBaseTypes(true).ToArray(); + Assert.AreEqual(3, types.Length); + Assert.Contains(typeof(Class2), types); + Assert.Contains(typeof(Class1), types); + Assert.Contains(typeof(object), types); + + types = type.GetBaseTypes(false).ToArray(); + Assert.AreEqual(2, types.Length); + Assert.Contains(typeof(Class1), types); + Assert.Contains(typeof(object), types); + } + + [Test] + public void GetInterfacesIsOk() + { + // tests that GetInterfaces gets _all_ interfaces + // so the AllInterfaces extension method is useless + + var type = typeof(Class2); + var interfaces = type.GetInterfaces(); + Assert.AreEqual(2, interfaces.Length); + Assert.Contains(typeof(IInterface1), interfaces); + Assert.Contains(typeof(IInterface2), interfaces); + } + + interface IInterface1 + { } + + interface IInterface2 : IInterface1 + { + void Method(); + } + + class Class1 : IInterface2 + { + public void Method() { } + } + + class Class2 : Class1 + { } + } +} diff --git a/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs new file mode 100644 index 0000000000..154f95d3fd --- /dev/null +++ b/src/Umbraco.Tests/Clr/ReflectionUtilitiesTests.cs @@ -0,0 +1,126 @@ +using System; +using NUnit.Framework; +using Umbraco.Core; + +namespace Umbraco.Tests.Clr +{ + [TestFixture] + public class ReflectionUtilitiesTests + { + [Test] + public void Test() + { + // cannot ctor a private class + var ctor1 = ReflectionUtilities.GetCtor(); + Assert.Throws(() => _ = ctor1()); + + // cannot private ctor a public class + var ctor2 = ReflectionUtilities.GetCtor(); + Assert.Throws(() => _ = ctor2(0)); + + // can public ctor a public class + var ctor3 = ReflectionUtilities.GetCtor(); + Assert.IsNotNull(ctor3(string.Empty)); + + // works if not a dynamic assembly + var ctor4 = ReflectionUtilities.GetCtor(false); + Assert.IsNotNull(ctor4()); + + // we need the dynasm flag everywhere, because in some cases we do not + // want to create a dynasm that will stay around - eg when using the + // generated stuff only a few times + + // collectible assemblies - created with RunAndCollect... + // https://msdn.microsoft.com/en-us/library/dd554932(v=vs.100).aspx + // with restrictions, but we could try it? + + // so... + // GetCtor(ReflectionUtilities.Compile.None) + + // should we find a way for a dynamic assembly to access private stuff? + } + + [Test] + public void SingleDynAsmTest() + { + var expr1 = ReflectionUtilities.GetCtorExpr(); + //var ctor2 = ReflectionUtilities.GetCtorExpr(); + //var ctor3 = ReflectionUtilities.GetCtorExpr(); + + // creates one single dynamic assembly containing all methods + var ctors = ReflectionUtilities.CompileToDelegates(expr1); + var ctor1 = ctors[0]; + + // still, cannot access private stuff + Assert.Throws(() => _ = ctor1()); + } + + [Test] + public void MoreTest() + { + // can get a ctor via generic and compile + var expr1 = ReflectionUtilities.GetCtorExpr(); + var ctor1 = ReflectionUtilities.Compile(expr1); + Assert.IsInstanceOf(ctor1()); + + // direct + var ctor1A = ReflectionUtilities.GetCtor(); + Assert.IsInstanceOf(ctor1A()); + + // can get a ctor via type and compile + var expr2 = ReflectionUtilities.GetCtorExpr>(typeof (Class1)); + var ctor2 = ReflectionUtilities.Compile(expr2); + Assert.IsInstanceOf(ctor2()); + + // direct + var ctor2A = ReflectionUtilities.GetCtor>(typeof (Class1)); + Assert.IsInstanceOf(ctor2A()); + + // if type is unknown (in a variable) + var ctor2B = ReflectionUtilities.GetCtor>(typeof (Class1)); + Assert.IsInstanceOf(ctor2B()); + + // cannot get a ctor for a private class + var ctorP1 = ReflectionUtilities.GetCtor(); + Assert.Throws(() => ctorP1()); + + // unless we don't compile to an assembly + var ctorP1A = ReflectionUtilities.GetCtor(access: ReflectionUtilities.NoAssembly); + Assert.IsInstanceOf(ctorP1A()); + + // so... + // if I create a dynamic method delegate by writing IL it's fast and I can access private stuff + // if I create an expression it's easier than IL but it's slower + // if I compile the expression, the speed is same as delegate but then I cannot access private stuff + // so ... what should I do?! + } + + // todo + // - figure out the private/public thing + // - implement the dynasm enumeration + // - figure out ctors in model factory - ie the casting thing + + // this is how we could indicate what to do? + private enum DynAsm + { + None, + Run, + RunAndCollect + } + + private class PrivateClass { } + + public class PublicClass + { + private PublicClass(int i) { } + + public PublicClass(string s) { } + } + + public class Class1 { } + + public class Class2 { } + + private class Class1P { } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 05d1d22175..63a6f31443 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -217,6 +217,7 @@ + @@ -388,7 +389,7 @@ - + diff --git a/src/Umbraco.Web.UI/web.Template.Debug.config b/src/Umbraco.Web.UI/web.Template.Debug.config index ffdeb94e26..9431209d3d 100644 --- a/src/Umbraco.Web.UI/web.Template.Debug.config +++ b/src/Umbraco.Web.UI/web.Template.Debug.config @@ -12,8 +12,15 @@ file everytime Umbraco builds. --> + - + - @@ -47,7 +52,6 @@ - @@ -67,14 +71,12 @@ - - - + @@ -330,7 +332,7 @@ - + @@ -338,10 +340,8 @@ - - @@ -440,8 +440,8 @@ - - + + @@ -457,5 +457,5 @@ - +--> \ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs index d9079e53a6..75230e6c59 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/NestedContentManyValueConverter.cs @@ -80,7 +80,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var ctor = _listCtors.GetOrAdd(type, t => { var listType = typeof(List<>).MakeGenericType(t); - return ReflectionUtilities.GetCtor(listType); + return ReflectionUtilities.GetCtor>(listType); }); elements = (IList) ctor();