diff --git a/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs
index a00950a..962a568 100644
--- a/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs
+++ b/src/Our.Umbraco.Ditto/Extensions/Internal/PropertyInfoExtensions.cs
@@ -66,8 +66,13 @@ public static bool IsMappable(this PropertyInfo source)
/// True if a lazy load attempt should be make
public static bool ShouldAttemptLazyLoad(this PropertyInfo source)
{
- return source.HasCustomAttribute() ||
- ((source.DeclaringType.HasCustomAttribute() || Ditto.LazyLoadStrategy == LazyLoad.AllVirtuals)
+ if (source.HasCustomAttribute())
+ {
+ return false;
+ }
+
+ return source.HasCustomAttribute() ||
+ ((source.DeclaringType.HasCustomAttribute() || Ditto.LazyLoadStrategy == LazyLoad.AllVirtuals)
&& source.IsVirtualAndOverridable());
}
}
diff --git a/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs b/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs
index cd8eb37..a44d57b 100644
--- a/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs
+++ b/src/Our.Umbraco.Ditto/Extensions/PublishedContentExtensions.cs
@@ -8,7 +8,6 @@
using System.Reflection;
using Umbraco.Core;
using Umbraco.Core.Models;
-using Umbraco.Web;
namespace Our.Umbraco.Ditto
{
@@ -197,7 +196,7 @@ public static class PublishedContentExtensions
// Ensure instance is of target type
if (instance != null && !type.IsInstanceOfType(instance))
{
- throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", "instance");
+ throw new ArgumentException($"The instance parameter does not implement Type '{type.Name}'", nameof(instance));
}
// Get the context accessor (for access to ApplicationContext, UmbracoContext, et al)
@@ -206,15 +205,9 @@ public static class PublishedContentExtensions
// Check if the culture has been set, otherwise use from Umbraco, or fallback to a default
if (culture == null)
{
- if (contextAccessor.UmbracoContext != null && contextAccessor.UmbracoContext.PublishedContentRequest != null)
- {
- culture = contextAccessor.UmbracoContext.PublishedContentRequest.Culture;
- }
- else
- {
- // Fallback
- culture = CultureInfo.CurrentCulture;
- }
+ culture = contextAccessor.UmbracoContext?.PublishedContentRequest != null
+ ? contextAccessor.UmbracoContext.PublishedContentRequest.Culture
+ : CultureInfo.CurrentCulture;
}
// Ensure a chain context
@@ -285,99 +278,114 @@ public static class PublishedContentExtensions
Action onConverted,
DittoChainContext chainContext)
{
- // Get the default constructor, parameters and create an instance of the type.
+ // Collect all the properties of the given type and loop through writable ones.
+ PropertyInfo[] properties;
+ PropertyCache.TryGetValue(type, out properties);
+
+ if (properties == null)
+ {
+ properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
+ .Where(x => x.CanWrite && x.GetSetMethod() != null).ToArray();
+
+ PropertyCache.TryAdd(type, properties);
+ }
+
+ // Check the validity of the mpped type constructor as early as possible.
ParameterInfo[] constructorParams = type.GetConstructorParameters();
+ bool validConstructor = false;
bool hasParameter = false;
+ bool isType = false;
+ bool hasLazy = false;
- // If not already an instance, create an instance of the object
- if (instance == null)
+ if (constructorParams != null)
{
- if (constructorParams != null && constructorParams.Length == 0)
- {
- // Internally this uses Activator.CreateInstance which is heavily optimized.
- instance = type.GetInstance();
- }
- else if (constructorParams != null && constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent))
+ // Is it PublishedContentmModel or similar?
+ if (constructorParams.Length == 1 && constructorParams[0].ParameterType == typeof(IPublishedContent))
{
- // This extension method is about 7x faster than the native implementation.
- instance = type.GetInstance(content);
hasParameter = true;
}
- else
+
+ if (constructorParams.Length == 0 || hasParameter)
{
- // No valid constructor, but see if the value can be cast to the type
- if (type.IsInstanceOfType(content))
- {
- instance = content;
- }
- else
- {
- throw new InvalidOperationException($"Can't convert IPublishedContent to {type} as it has no valid constructor. A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.");
- }
+ validConstructor = true;
}
}
- // Collect all the properties of the given type and loop through writable ones.
- PropertyInfo[] properties;
- PropertyCache.TryGetValue(type, out properties);
-
- if (properties == null)
+ // No valid constructor, but see if the value can be cast to the type
+ if (type.IsInstanceOfType(content))
{
- properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
- .Where(x => x.CanWrite && x.GetSetMethod() != null).ToArray();
+ isType = true;
+ validConstructor = true;
+ }
- PropertyCache.TryAdd(type, properties);
+ if (!validConstructor)
+ {
+ throw new InvalidOperationException(
+ $"Cannot convert IPublishedContent to {type} as it has no valid constructor. " +
+ "A valid constructor is either an empty one, or one accepting a single IPublishedContent parameter.");
}
// Gets the default processor for this conversion.
var defaultProcessorType = DittoProcessorRegistry.Instance.GetDefaultProcessorType(type);
+ PropertyInfo[] lazyProperties = null;
- // A dictionary to store lazily invoked values.
- var lazyProperties = new Dictionary>();
-
- // process lazy load properties first
- foreach (var propertyInfo in properties.Where(x => x.ShouldAttemptLazyLoad()))
+ // If not already an instance, create an instance of the object
+ if (instance == null)
{
- // Configure lazy properties
- using (DittoDisposableTimer.DebugDuration($"Lazy Property ({content.Id} {propertyInfo.Name})"))
- {
- // Ensure it's a virtual property (Only relevant to property level lazy loads)
- if (!propertyInfo.IsVirtualAndOverridable())
- {
- throw new InvalidOperationException($"Lazy property '{propertyInfo.Name}' of type '{type.AssemblyQualifiedName}' must be declared virtual in order to be lazy loadable.");
- }
+ // We can only proxy new instances.
+ lazyProperties = properties.Where(x => x.ShouldAttemptLazyLoad()).ToArray();
- // Check for the ignore attribute (Only relevant to class level lazy loads).
- if (propertyInfo.HasCustomAttribute())
- {
- continue;
- }
+ if (lazyProperties.Any())
+ {
+ hasLazy = true;
- // Create a Lazy to deferr returning our value.
- var deferredPropertyInfo = propertyInfo;
- var localInstance = instance;
+ var factory = new ProxyFactory();
+ instance = hasParameter
+ ? factory.CreateProxy(type, lazyProperties.Select(x => x.Name), content)
+ : factory.CreateProxy(type, lazyProperties.Select(x => x.Name));
- // ReSharper disable once PossibleMultipleEnumeration
- lazyProperties.Add(propertyInfo.Name, new Lazy(() => GetProcessedValue(content, culture, type, deferredPropertyInfo, localInstance, defaultProcessorType, contextAccessor, chainContext)));
}
- }
-
- if (lazyProperties.Any())
- {
- // Create a proxy instance to replace our object.
- var interceptor = new LazyInterceptor(instance, lazyProperties);
- var factory = new ProxyFactory();
-
- instance = hasParameter
- ? factory.CreateProxy(type, interceptor, content)
- : factory.CreateProxy(type, interceptor);
+ else if (isType)
+ {
+ instance = content;
+ }
+ else
+ {
+ // 1: This extension method is about 7x faster than the native implementation.
+ // 2: Internally this uses Activator.CreateInstance which is heavily optimized.
+ instance = hasParameter
+ ? type.GetInstance(content) // 1
+ : type.GetInstance(); // 2
+ }
}
// We have the instance object but haven't yet populated properties
// so fire the on converting event handlers
OnConverting(content, type, culture, instance, onConverting);
- // Process any non lazy properties next
+ if (hasLazy)
+ {
+ // A dictionary to store lazily invoked values.
+ var lazyMappings = new Dictionary>();
+ foreach (var propertyInfo in lazyProperties)
+ {
+ // Configure lazy properties
+ using (DittoDisposableTimer.DebugDuration($"Lazy Property ({content.Id} {propertyInfo.Name})"))
+ {
+ // Ensure it's a virtual property (Only relevant to property level lazy loads)
+ if (!propertyInfo.IsVirtualAndOverridable())
+ {
+ throw new InvalidOperationException($"Lazy property '{propertyInfo.Name}' of type '{type.AssemblyQualifiedName}' must be declared virtual in order to be lazy loadable.");
+ }
+
+ lazyMappings.Add(propertyInfo.Name, new Lazy(() => GetProcessedValue(content, culture, type, propertyInfo, instance, defaultProcessorType, contextAccessor, chainContext)));
+ }
+ }
+
+ ((IProxy)instance).Interceptor = new LazyInterceptor(lazyMappings);
+ }
+
+ // Process any non lazy properties
foreach (var propertyInfo in properties.Where(x => !x.ShouldAttemptLazyLoad()))
{
// Check for the ignore attribute.
@@ -387,7 +395,6 @@ public static class PublishedContentExtensions
}
// Set the value normally.
- // ReSharper disable once PossibleMultipleEnumeration
var value = GetProcessedValue(content, culture, type, propertyInfo, instance, defaultProcessorType, contextAccessor, chainContext);
// This over 4x faster as propertyInfo.SetValue(instance, value, null);
diff --git a/src/Our.Umbraco.Ditto/Proxy/LazyInterceptor.cs b/src/Our.Umbraco.Ditto/Proxy/LazyInterceptor.cs
index d9e325d..fa1171f 100644
--- a/src/Our.Umbraco.Ditto/Proxy/LazyInterceptor.cs
+++ b/src/Our.Umbraco.Ditto/Proxy/LazyInterceptor.cs
@@ -9,75 +9,57 @@ namespace Our.Umbraco.Ditto
///
internal class LazyInterceptor : IInterceptor
{
- ///
- /// The lazy dictionary.
- ///
private readonly Dictionary> lazyDictionary = new Dictionary>();
-
- ///
- /// The base target instance from which the proxy type is derived.
- ///
- private readonly object target;
+ private readonly Dictionary nonLazyDictionary = new Dictionary();
///
/// Initializes a new instance of the class.
///
- ///
- /// The base target instance from which the proxy type is derived.
- ///
///
/// The dictionary of values containing the property name to replace and the value to replace it with.
///
- public LazyInterceptor(object target, Dictionary> values)
+ public LazyInterceptor(Dictionary> values)
{
- this.target = target;
-
foreach (KeyValuePair> pair in values)
{
this.lazyDictionary.Add(pair.Key, pair.Value);
}
}
- ///
- /// Intercepts the in the proxy to return a replaced value.
- ///
- ///
- /// The containing information about the current
- /// invoked property.
- ///
- ///
- /// The object to set the to if it is a setter.
- ///
- ///
- /// The replacing the original implementation value.
- ///
+ ///
public object Intercept(MethodBase methodBase, object value)
{
- const string Getter = "get_";
- const string Setter = "set_";
- var name = methodBase.Name;
- var key = name.Substring(4);
- var parameters = value == null ? new object[] { } : new[] { value };
+ const string getter = "get_";
+ const string setter = "set_";
+ string name = methodBase.Name;
+ string key = name.Substring(4);
// Attempt to get the value from the lazy members.
- if (name.StartsWith(Getter))
+ if (name.StartsWith(getter))
{
if (this.lazyDictionary.ContainsKey(key))
{
return this.lazyDictionary[key].Value;
}
+
+ if (this.nonLazyDictionary.ContainsKey(key))
+ {
+ return this.nonLazyDictionary[key];
+ }
}
// Set the value, remove the old lazy value.
- if (name.StartsWith(Setter))
+ if (name.StartsWith(setter))
{
if (this.lazyDictionary.ContainsKey(key))
{
this.lazyDictionary.Remove(key);
}
+
+ this.nonLazyDictionary[key] = value;
}
- return methodBase.Invoke(this.target, parameters);
+ return null;
}
}
}
\ No newline at end of file
diff --git a/src/Our.Umbraco.Ditto/Proxy/PropertyEmitter.cs b/src/Our.Umbraco.Ditto/Proxy/PropertyEmitter.cs
index 1b4d52d..a2cb04c 100644
--- a/src/Our.Umbraco.Ditto/Proxy/PropertyEmitter.cs
+++ b/src/Our.Umbraco.Ditto/Proxy/PropertyEmitter.cs
@@ -13,14 +13,15 @@ internal static class PropertyEmitter
///
/// The get interceptor method.
///
- private static readonly MethodInfo GetInterceptor = typeof(IProxy).GetProperty("Interceptor").GetGetMethod();
+ // ReSharper disable once PossibleNullReferenceException
+ private static readonly MethodInfo GetInterceptor = typeof(IProxy).GetProperty(nameof(IProxy.Interceptor)).GetGetMethod();
///
/// The intercept handler method.
///
private static readonly MethodInfo InterceptorMethod = typeof(IInterceptor).GetMethod(
"Intercept",
- new[] { typeof(MethodBase), typeof(object) });
+ new[] { typeof(MethodBase), typeof(IProxy) });
///
/// The get method from handle method.
@@ -28,10 +29,10 @@ internal static class PropertyEmitter
private static readonly MethodInfo GetMethodFromHandle = typeof(MethodBase).GetMethod("GetMethodFromHandle", new[] { typeof(RuntimeMethodHandle) });
///
- /// The constructor.
+ /// The constructor.
///
- private static readonly ConstructorInfo NotImplementedConstructor =
- typeof(NotImplementedException).GetConstructor(new Type[0]);
+ private static readonly ConstructorInfo ArgumentNullExceptionConstructor =
+ typeof(ArgumentNullException).GetConstructor(new Type[0]);
///
/// Uses reflection to emit the given body for interception.
@@ -52,14 +53,14 @@ public static void Emit(TypeBuilder typeBuilder, MethodInfo method, FieldInfo in
ParameterInfo parameter = parameters.FirstOrDefault();
// Define attributes.
- const MethodAttributes MethodAttributes = MethodAttributes.Public |
+ const MethodAttributes methodAttributes = MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.Virtual;
// Define the method.
MethodBuilder methodBuilder = typeBuilder.DefineMethod(
method.Name,
- MethodAttributes,
+ methodAttributes,
CallingConventions.HasThis,
method.ReturnType,
parameters.Select(param => param.ParameterType).ToArray());
@@ -74,37 +75,46 @@ public static void Emit(TypeBuilder typeBuilder, MethodInfo method, FieldInfo in
// IInterceptor interceptor = ((IProxy)this).Interceptor;
// if (interceptor == null)
// {
- // throw new NotImplementedException();
+ // throw new ArgumentNullException();
// }
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Callvirt, GetInterceptor);
+ il.Emit(OpCodes.Ldarg_0); // this
+ il.Emit(OpCodes.Callvirt, GetInterceptor); // .Interceptor
Label skipThrow = il.DefineLabel();
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Bne_Un, skipThrow);
- il.Emit(OpCodes.Newobj, NotImplementedConstructor);
+ il.Emit(OpCodes.Newobj, ArgumentNullExceptionConstructor);
il.Emit(OpCodes.Throw);
il.MarkLabel(skipThrow);
- // This is equivalent to:
- // For get
+ // This is equivalent to:
// return return (PropertyType) interceptor.Intercept(methodof(BaseType.get_Property), null);
- // For set
- // interceptor.Intercept(methodof(BaseType.set_Property), value);
il.Emit(OpCodes.Ldtoken, method);
il.Emit(OpCodes.Call, GetMethodFromHandle);
- il.Emit(parameter == null ? OpCodes.Ldnull : OpCodes.Ldarg_1);
- il.Emit(OpCodes.Callvirt, InterceptorMethod);
- // Setter only.
- if (method.ReturnType == typeof(void))
+ if (parameter == null)
{
- il.Emit(OpCodes.Pop);
+ // Getter
+ // return interceptor.Intercept(MethodBase.GetMethodFromHandle(typeof(BaseType).GetMethod("get_PropertyName").MethodHandle), null);
+ il.Emit(OpCodes.Ldnull);
+ il.Emit(OpCodes.Callvirt, InterceptorMethod);
+
+ // Unbox the object back to the correct type.
+ il.Emit(OpCodes.Unbox_Any, method.ReturnType);
}
else
{
- // Unbox the object back to the corrct type.
- il.Emit(OpCodes.Unbox_Any, method.ReturnType);
+ // Setter
+ // interceptor.Intercept(MethodBase.GetMethodFromHandle(typeof(BaseType).GetMethod("set_PropertyName", new Type[] { typeof(PropertyType) }).MethodHandle), value);
+ il.Emit(OpCodes.Ldarg_1);
+
+ if (parameter.ParameterType.IsValueType)
+ {
+ il.Emit(OpCodes.Box, parameter.ParameterType);
+ }
+
+ il.Emit(OpCodes.Callvirt, InterceptorMethod);
+ il.Emit(OpCodes.Pop); // Clear the stack
}
il.Emit(OpCodes.Ret);
diff --git a/src/Our.Umbraco.Ditto/Proxy/ProxyFactory.cs b/src/Our.Umbraco.Ditto/Proxy/ProxyFactory.cs
index ff548bb..75918bf 100644
--- a/src/Our.Umbraco.Ditto/Proxy/ProxyFactory.cs
+++ b/src/Our.Umbraco.Ditto/Proxy/ProxyFactory.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
@@ -28,38 +29,36 @@ public class ProxyFactory
/// Creates an instance of the proxy class for the given .
///
/// The base to proxy.
- /// The to intercept properties with.
+ /// The collection of property names to map.
/// The to pass as a parameter.
///
/// The proxy instance.
///
- public object CreateProxy(Type baseType, IInterceptor interceptor, IPublishedContent content = null)
+ public IProxy CreateProxy(Type baseType, IEnumerable properties, IPublishedContent content = null)
{
- Type proxyType = this.CreateProxyType(baseType);
+ Type proxyType = this.CreateProxyType(baseType, properties);
object result = content == null ? proxyType.GetInstance() : proxyType.GetInstance(content);
- IProxy proxy = (IProxy)result;
- proxy.Interceptor = interceptor;
-
- return result;
+ return (IProxy)result;
}
///
/// Creates the proxy class or returns already created class from the cache.
///
/// The base to proxy.
+ /// The collection of property names to map.
///
/// The proxy .
///
- private Type CreateProxyType(Type baseType)
+ private Type CreateProxyType(Type baseType, IEnumerable properties)
{
try
{
// ConcurrentDictionary.GetOrAdd() is not atomic so we'll be doubly sure.
Locker.EnterWriteLock();
- return ProxyCache.GetOrAdd(baseType, c => this.CreateUncachedProxyType(baseType));
+ return ProxyCache.GetOrAdd(baseType, c => this.CreateUncachedProxyType(baseType, properties));
}
finally
{
@@ -70,38 +69,47 @@ private Type CreateProxyType(Type baseType)
///
/// Creates an un-cached proxy class.
///
- ///
- /// The base to proxy.
- ///
+ /// The base to proxy.
+ /// The collection of property names to map.
///
/// The proxy .
///
- private Type CreateUncachedProxyType(Type baseType)
+ private Type CreateUncachedProxyType(Type baseType, IEnumerable properties)
{
// Create a dynamic assembly and module to store the proxy.
AppDomain currentDomain = AppDomain.CurrentDomain;
- string typeName = string.Format("{0}Proxy", baseType.Name);
- string assemblyName = string.Format("{0}Assembly", typeName);
- string moduleName = string.Format("{0}Module", typeName);
+ string typeName = $"{baseType.Name}Proxy";
+ string assemblyName = $"{typeName}Assembly";
+ string moduleName = $"{typeName}Module";
// Define different behaviors for debug and release so that we can make debugging easier.
- AssemblyName name = new AssemblyName(assemblyName);
+ var name = new AssemblyName(assemblyName);
+ AssemblyBuilder assemblyBuilder;
#if DEBUG
- AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
- ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName, string.Format("{0}.mod", moduleName), true);
+ assemblyBuilder = currentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.RunAndSave);
+ ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName, $"{moduleName}.mod", true);
+
+ // Add a debuggable attribute to the assembly saying to disable optimizations
+ Type daType = typeof(DebuggableAttribute);
+ ConstructorInfo daCtor = daType.GetConstructor(new[] { typeof(DebuggableAttribute.DebuggingModes) });
+ var daBuilder = new CustomAttributeBuilder(
+ daCtor,
+ new object[] { DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.Default });
+ assemblyBuilder.SetCustomAttribute(daBuilder);
#else
- AssemblyBuilder assemblyBuilder = currentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
+ assemblyBuilder = currentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName);
#endif
+
// Define type attributes
- const TypeAttributes TypeAttributes = TypeAttributes.AutoClass |
+ const TypeAttributes typeAttributes = TypeAttributes.AutoClass |
TypeAttributes.Class |
TypeAttributes.Public |
TypeAttributes.BeforeFieldInit;
// Define the type.
- TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes, baseType);
+ TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, typeAttributes, baseType);
// Emit the default constructors for this proxy so that classes without parameterless constructors
// can be proxied.
@@ -114,17 +122,25 @@ private Type CreateUncachedProxyType(Type baseType)
// Emit the IProxy IInterceptor property.
FieldInfo interceptorField = InterceptorEmitter.Emit(typeBuilder);
- // Emit each property that is to be intercepted.
+ // Collect and filter our list of properties to intercept.
MethodInfo[] methods = baseType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
- IEnumerable proxyList = this.BuildPropertyList(methods);
+ IEnumerable proxyList = this.BuildPropertyList(methods)
+ .Where(m => properties.Contains(m.Name.Substring(4), StringComparer.OrdinalIgnoreCase));
+
+ // Emit each property that is to be intercepted.
foreach (MethodInfo methodInfo in proxyList)
{
PropertyEmitter.Emit(typeBuilder, methodInfo, interceptorField);
}
// Create and return.
- return typeBuilder.CreateType();
+ Type result = typeBuilder.CreateType();
+
+#if DEBUG
+ assemblyBuilder.Save(typeName + ".dll");
+#endif
+ return result;
}
///
@@ -138,7 +154,7 @@ private Type CreateUncachedProxyType(Type baseType)
///
private IEnumerable BuildPropertyList(MethodInfo[] methodInfos)
{
- List proxyList = new List();
+ var proxyList = new List();
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (MethodInfo method in methodInfos)
@@ -175,9 +191,7 @@ private IEnumerable BuildPropertyList(MethodInfo[] methodInfos)
///
/// Returns the parent if any, for the given .
///
- ///
- /// The .
- ///
+ /// The .
///
/// The , or null .
///
@@ -188,10 +202,10 @@ private PropertyInfo GetParentProperty(MethodInfo method)
{
if (method == null)
{
- throw new ArgumentNullException("method");
+ throw new ArgumentNullException(nameof(method));
}
- const BindingFlags PropertyFlags = BindingFlags.Instance | BindingFlags.Public;
+ const BindingFlags propertyFlags = BindingFlags.Public | BindingFlags.Instance;
bool takesArg = method.GetParameters().Length == 1;
bool hasReturn = method.ReturnType != typeof(void);
@@ -205,14 +219,14 @@ private PropertyInfo GetParentProperty(MethodInfo method)
{
if (method.DeclaringType != null)
{
- return method.DeclaringType.GetProperties(PropertyFlags)
+ return method.DeclaringType.GetProperties(propertyFlags)
.FirstOrDefault(p => this.AreMethodsEqualForDeclaringType(p.GetSetMethod(), method));
}
}
if (method.DeclaringType != null)
{
- return method.DeclaringType.GetProperties(PropertyFlags)
+ return method.DeclaringType.GetProperties(propertyFlags)
.FirstOrDefault(p => this.AreMethodsEqualForDeclaringType(p.GetGetMethod(), method));
}
@@ -220,15 +234,11 @@ private PropertyInfo GetParentProperty(MethodInfo method)
}
///
- /// Returns a value indicating whether two instances of are equal
+ /// Returns a value indicating whether two instances of are equal
/// for a declaring type.
///
- ///
- /// The first .
- ///
- ///
- /// The second .
- ///
+ /// The first .
+ /// The second .
///
/// True if the two instances of are equal; otherwise, false.
///
@@ -245,10 +255,7 @@ private bool AreMethodsEqualForDeclaringType(MethodInfo first, MethodInfo second
first.GetParameters().Select(p => p.ParameterType).ToArray());
MethodBody body = first.GetMethodBody();
- if (body != null)
- {
- firstBytes = body.GetILAsByteArray();
- }
+ firstBytes = body.GetILAsByteArray();
}
if (second != null && second.ReflectedType != null && second.DeclaringType != null)
@@ -260,10 +267,7 @@ private bool AreMethodsEqualForDeclaringType(MethodInfo first, MethodInfo second
second.GetParameters().Select(p => p.ParameterType).ToArray());
MethodBody body = second.GetMethodBody();
- if (body != null)
- {
- secondBytes = body.GetILAsByteArray();
- }
+ secondBytes = body.GetILAsByteArray();
}
return firstBytes.SequenceEqual(secondBytes);
diff --git a/tests/Our.Umbraco.Ditto.Tests/Our.Umbraco.Ditto.Tests.csproj b/tests/Our.Umbraco.Ditto.Tests/Our.Umbraco.Ditto.Tests.csproj
index 4ab2ef7..0138ef1 100644
--- a/tests/Our.Umbraco.Ditto.Tests/Our.Umbraco.Ditto.Tests.csproj
+++ b/tests/Our.Umbraco.Ditto.Tests/Our.Umbraco.Ditto.Tests.csproj
@@ -199,6 +199,7 @@
+
diff --git a/tests/Our.Umbraco.Ditto.Tests/ProxyFactoryTests.cs b/tests/Our.Umbraco.Ditto.Tests/ProxyFactoryTests.cs
new file mode 100644
index 0000000..0b04885
--- /dev/null
+++ b/tests/Our.Umbraco.Ditto.Tests/ProxyFactoryTests.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using NUnit.Framework;
+
+namespace Our.Umbraco.Ditto.Tests
+{
+ [TestFixture]
+ [Category("Proxy Generation")]
+ public class ProxyFactoryTests
+ {
+ [Test]
+ public void ProxyCanGetSetProperties()
+ {
+ var factory = new ProxyFactory();
+ IProxy proxy = factory.CreateProxy(typeof(TestClass), new List { "id", "name" });
+ proxy.Interceptor = new LazyInterceptor(new Dictionary>());
+
+ // This is the method we are replicating within the property emitter.
+ var tc = new TestClass();
+
+ var idMethod =
+ MethodBase.GetMethodFromHandle(typeof(TestClass).GetMethod("set_Id", new[] { typeof(int) }).MethodHandle);
+
+ var nameMethod =
+ MethodBase.GetMethodFromHandle(typeof(TestClass).GetMethod("set_Name", new[] { typeof(string) }).MethodHandle);
+
+ var dateMethod =
+ MethodBase.GetMethodFromHandle(typeof(TestClass).GetMethod("set_CreateDate", new[] { typeof(DateTime) }).MethodHandle);
+
+ idMethod.Invoke(tc, new object[] { 1 });
+ nameMethod.Invoke(tc, new object[] { "Foo" });
+ dateMethod.Invoke(tc, new object[] { new DateTime(2017, 1, 1) });
+
+ Assert.AreEqual(1, tc.Id);
+ Assert.AreEqual("Foo", tc.Name);
+ Assert.AreEqual(new DateTime(2017, 1, 1), tc.CreateDate);
+
+ // ReSharper disable once SuspiciousTypeConversion.Global
+ var testClass = (TestClass)proxy;
+
+ testClass.Id = 1;
+ testClass.Name = "Foo";
+ testClass.CreateDate = new DateTime(2017, 1, 1);
+
+ Assert.AreEqual(1, testClass.Id);
+ Assert.AreEqual("Foo", testClass.Name);
+ Assert.AreEqual(new DateTime(2017, 1, 1), testClass.CreateDate);
+ }
+ }
+
+ public class TestClass
+ {
+ public virtual int Id { get; set; }
+
+ public virtual string Name { get; set; }
+
+ public DateTime CreateDate { get; set; }
+ }
+}