Files
Umbraco-CMS/src/Umbraco.Core/ReflectionUtilities.cs

897 lines
45 KiB
C#
Raw Normal View History

2017-09-25 12:58:54 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
2017-09-29 15:50:30 +02:00
using Umbraco.Core.Exceptions;
2017-09-25 12:58:54 +02:00
namespace Umbraco.Core
{
/// <summary>
/// Provides utilities to simplify reflection.
/// </summary>
2017-09-29 15:50:30 +02:00
/// <remarks>
/// <para>Readings:
/// * CIL instructions: https://en.wikipedia.org/wiki/List_of_CIL_instructions
/// * ECMA 335: https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf
/// * MSIL programming: http://www.blackbeltcoder.com/Articles/net/msil-programming-part-1
/// </para>
/// <para>Supports emitting constructors, instance and static methods, instance property getters and
/// setters. Does not support static properties yet.</para>
/// </remarks>
2017-09-25 12:58:54 +02:00
public static class ReflectionUtilities
{
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a property getter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyName">The name of the property.</param>
/// <param name="mustExist">A value indicating whether the property and its getter must exist.</param>
/// <returns>A property getter function. If <paramref name="mustExist"/> is <c>false</c>, returns null when the property or its getter does not exist.</returns>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="propertyName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when the property or its getter does not exist.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static Func<TDeclaring, TValue> EmitPropertyGetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
{
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentNullOrEmptyException(nameof(propertyName));
var property = typeof (TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property == null || property.GetMethod == null)
{
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find getter for {typeof(TDeclaring)}.{propertyName}.");
}
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
return EmitMethod<Func<TDeclaring, TValue>>(property.GetMethod);
}
/// <summary>
/// Emits a property setter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyName">The name of the property.</param>
/// <param name="mustExist">A value indicating whether the property and its setter must exist.</param>
/// <returns>A property setter function. If <paramref name="mustExist"/> is <c>false</c>, returns null when the property or its setter does not exist.</returns>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="propertyName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when the property or its setter does not exist.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static Action<TDeclaring, TValue> EmitPropertySetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
2017-09-25 12:58:54 +02:00
{
2017-09-29 15:50:30 +02:00
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentNullOrEmptyException(nameof(propertyName));
var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property == null || property.SetMethod == null)
{
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find setter for {typeof(TDeclaring)}.{propertyName}.");
}
return EmitMethod<Action<TDeclaring, TValue>>(property.SetMethod);
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a property getter and setter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyName">The name of the property.</param>
/// <param name="mustExist">A value indicating whether the property and its getter and setter must exist.</param>
/// <returns>A property getter and setter functions. If <paramref name="mustExist"/> is <c>false</c>, returns null when the property or its getter or setter does not exist.</returns>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="propertyName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when the property or its getter or setter does not exist.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static (Func<TDeclaring, TValue>, Action<TDeclaring, TValue>) EmitPropertyGetterAndSetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
2017-09-25 12:58:54 +02:00
{
2017-09-29 15:50:30 +02:00
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentNullOrEmptyException(nameof(propertyName));
var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (property == null || property.GetMethod == null || property.SetMethod == null)
{
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}.");
}
return (
EmitMethod<Func<TDeclaring, TValue>>(property.GetMethod),
EmitMethod<Action<TDeclaring, TValue>>(property.SetMethod));
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a property getter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyInfo">The property info.</param>
/// <returns>A property getter function.</returns>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="propertyInfo"/> is null.</exception>
/// <exception cref="ArgumentException">Occurs when the property has no getter.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static Func<TDeclaring, TValue> EmitPropertyGetter<TDeclaring, TValue>(PropertyInfo propertyInfo)
2017-09-25 12:58:54 +02:00
{
2017-09-29 15:50:30 +02:00
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
if (propertyInfo.GetMethod == null)
throw new ArgumentException("Property has no getter.", nameof(propertyInfo));
return EmitMethod<Func<TDeclaring, TValue>> (propertyInfo.GetMethod);
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a property setter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyInfo">The property info.</param>
/// <returns>A property setter function.</returns>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="propertyInfo"/> is null.</exception>
/// <exception cref="ArgumentException">Occurs when the property has no setter.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static Action<TDeclaring, TValue> EmitPropertySetter<TDeclaring, TValue>(PropertyInfo propertyInfo)
2017-09-25 12:58:54 +02:00
{
2017-09-29 15:50:30 +02:00
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
if (propertyInfo.SetMethod == null)
throw new ArgumentException("Property has no setter.", nameof(propertyInfo));
return EmitMethod<Action<TDeclaring, TValue>>(propertyInfo.SetMethod);
}
/// <summary>
/// Emits a property getter and setter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The property type.</typeparam>
/// <param name="propertyInfo">The property info.</param>
/// <returns>A property getter and setter functions.</returns>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="propertyInfo"/> is null.</exception>
/// <exception cref="ArgumentException">Occurs when the property has no getter or no setter.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TValue"/> does not match the type of the property.</exception>
public static (Func<TDeclaring, TValue>, Action<TDeclaring, TValue>) EmitPropertyGetterAndSetter<TDeclaring, TValue>(PropertyInfo propertyInfo)
{
if (propertyInfo == null)
throw new ArgumentNullException(nameof(propertyInfo));
if (propertyInfo.GetMethod == null || propertyInfo.SetMethod == null)
throw new ArgumentException("Property has no getter and/or no setter.", nameof(propertyInfo));
return (
EmitMethod<Func<TDeclaring, TValue>>(propertyInfo.GetMethod),
EmitMethod<Action<TDeclaring, TValue>>(propertyInfo.SetMethod));
}
/// <summary>
/// Emits a constructor.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the constructor.</typeparam>
/// <param name="mustExist">A value indicating whether the constructor must exist.</param>
/// <param name="declaring">The optional type of the class to construct.</param>
/// <returns>A constructor function. If <paramref name="mustExist"/> is <c>false</c>, returns null when the constructor does not exist.</returns>
/// <remarks>
/// <para>When <paramref name="declaring"/> is not specified, it is the type returned by <typeparamref name="TLambda"/>.</para>
/// <para>The constructor arguments are determined by <typeparamref name="TLambda"/> generic arguments.</para>
/// <para>The type returned by <typeparamref name="TLambda"/> does not need to be exactly <paramref name="declaring"/>,
/// when e.g. that type is not known at compile time, but it has to be a parent type (eg an interface, or <c>object</c>).</para>
/// </remarks>
/// <exception cref="InvalidOperationException">Occurs when the constructor does not exist and <paramref name="mustExist"/> is <c>true</c>.</exception>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TLambda"/> is not a Func or when <paramref name="declaring"/>
/// is specified and does not match the function's returned type.</exception>
public static TLambda EmitCtor<TLambda>(bool mustExist = true, Type declaring = null)
{
// validate lambda type
ValidateCtorLambda<TLambda>();
// get instance and arguments types
var genericArgs = typeof(TLambda).GetGenericArguments();
var args = new Type[genericArgs.Length - 1];
for (var i = 0; i < args.Length; i++)
args[i] = genericArgs[i];
var returned = genericArgs[args.Length];
if (declaring == null)
declaring = returned;
else if (!returned.IsAssignableFrom(declaring))
throw new ArgumentException($"Type {returned} is not assignable from type {declaring}.", nameof(declaring));
2017-09-25 12:58:54 +02:00
// get the constructor infos
2017-09-29 15:50:30 +02:00
var ctor = declaring.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
null, args, null);
2017-09-25 12:58:54 +02:00
if (ctor == null)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find constructor {declaring}.ctor({string.Join(", ", (IEnumerable<Type>) args)}).");
2017-09-27 21:16:09 +02:00
}
2017-09-25 12:58:54 +02:00
2017-09-29 15:50:30 +02:00
// emit
2017-09-29 15:51:33 +02:00
return EmitCtor<TLambda>(declaring, args, ctor);
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a constructor.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the constructor.</typeparam>
/// <param name="ctor">The constructor info.</param>
/// <returns>A constructor function.</returns>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TLambda"/> is not a Func or when its generic
/// arguments do not match those of <paramref name="ctor"/>.</exception>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="ctor"/> is null.</exception>
public static TLambda EmitCtor<TLambda>(ConstructorInfo ctor)
2017-09-26 14:57:50 +02:00
{
if (ctor == null)
2017-09-29 15:50:30 +02:00
throw new ArgumentNullException(nameof(ctor));
// get type and args
var declaring = ctor.DeclaringType;
var args = ctor.GetParameters().Select(x => x.ParameterType).ToArray();
// validate lambda type
ValidateCtorLambda<TLambda>();
// validate arguments
var genericArgs = typeof(TLambda).GetGenericArguments();
if (genericArgs.Length != args.Length + 1)
ThrowInvalidLambda<TLambda>("ctor", declaring, args);
for (var i = 0; i < args.Length; i++)
if (args[i] != genericArgs[i])
ThrowInvalidLambda<TLambda>("ctor", declaring, args);
if (!genericArgs[args.Length].IsAssignableFrom(declaring))
ThrowInvalidLambda<TLambda>("ctor", declaring, args);
2017-09-26 14:57:50 +02:00
2017-09-29 15:50:30 +02:00
// emit
2017-09-29 15:51:33 +02:00
return EmitCtor<TLambda>(declaring, args, ctor);
}
/// <summary>
/// Emits a constructor.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the constructor.</typeparam>
/// <param name="ctor">The constructor info.</param>
/// <returns>A constructor function.</returns>
/// <remarks>
/// <para>The constructor is emitted in an unsafe way, using the lambda arguments without verifying
/// them at all. This assumes that the calling code is taking care of all verifications, in order
/// to avoid cast errors.</para>
/// </remarks>
/// <exception cref="ArgumentException">Occurs when <typeparamref name="TLambda"/> is not a Func or when its generic
/// arguments do not match those of <paramref name="ctor"/>.</exception>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="ctor"/> is null.</exception>
public static TLambda EmitCtorUnsafe<TLambda>(ConstructorInfo ctor)
{
if (ctor == null)
throw new ArgumentNullException(nameof(ctor));
// get type and args
var declaring = ctor.DeclaringType;
var module = declaring?.Module;
if (module == null)
throw new ArgumentException("Failed to get ctor's declaring type module.", nameof(ctor));
// validate lambda type
ValidateCtorLambda<TLambda>();
// unsafe - use lambda's args and assume they are correct
//var args = ctor.GetParameters().Select(x => x.ParameterType).ToArray();
var genArgs = typeof(TLambda).GetGenericArguments();
var args = new Type[genArgs.Length - 1];
Array.Copy(genArgs, 0, args, 0, args.Length);
// emit
var dm = new DynamicMethod(string.Empty, declaring, args, module, true);
var ilgen = dm.GetILGenerator();
EmitLdargs(ilgen, args.Length);
ilgen.Emit(OpCodes.Newobj, ctor); // ok to just return, it's only objects
ilgen.Emit(OpCodes.Ret);
return (TLambda) (object) dm.CreateDelegate(typeof(TLambda));
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
private static void ValidateCtorLambda<TLambda>()
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
var typeLambda = typeof(TLambda);
var genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null;
if (genericDefinition == null || genericDefinition.FullName == null || !genericDefinition.FullName.StartsWith("System.Func`"))
throw new ArgumentException($"Lambda {typeLambda} is not a Func.", nameof(TLambda));
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:51:33 +02:00
private static TLambda EmitCtor<TLambda>(Type declaring, Type[] args, ConstructorInfo ctor)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:51:33 +02:00
var dm = new DynamicMethod(string.Empty, declaring, args, declaring.Module, true);
2017-09-29 15:50:30 +02:00
var ilgen = dm.GetILGenerator();
EmitLdargs(ilgen, args.Length);
ilgen.Emit(OpCodes.Newobj, ctor); // ok to just return, it's only objects
ilgen.Emit(OpCodes.Ret);
return (TLambda) (object) dm.CreateDelegate(typeof(TLambda));
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
private static void ValidateMethodLambda<TLambda>(out bool isFunction)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
isFunction = false;
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
var typeLambda = typeof(TLambda);
var genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null;
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
if (typeLambda.FullName == "System.Action")
return;
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
if (genericDefinition == null
|| genericDefinition.FullName == null
|| !genericDefinition.FullName.StartsWith("System.Func`") && !genericDefinition.FullName.StartsWith("System.Action`"))
throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda));
isFunction = genericDefinition.FullName.StartsWith("System.Func`");
2017-09-26 14:57:50 +02:00
}
2017-09-29 15:50:30 +02:00
/// <summary>
/// Emits a method.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the method.</typeparam>
/// <param name="method">The method info.</param>
/// <returns>The method.</returns>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="method"/> is null.</exception>
/// <exception cref="ArgumentException">Occurs when Occurs when <typeparamref name="TLambda"/> does not match the method signature.</exception>
public static TLambda EmitMethod<TLambda>(MethodInfo method)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if (method == null)
throw new ArgumentNullException(nameof(method));
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
// get type and args
var type = method.DeclaringType;
var returned = method.ReturnType;
var args = method.GetParameters().Select(x => x.ParameterType).ToArray();
// validate lambda type
ValidateMethodLambda<TLambda>(out var isFunction);
var genericArgs = typeof(TLambda).GetGenericArguments();
var isStatic = method.IsStatic;
var ax = 0;
var gx = 0;
// must match the expected number of args
var expectedCount = (isStatic ? 0 : 1) + args.Length + (isFunction ? 1 : 0);
if (expectedCount != genericArgs.Length)
ThrowInvalidLambda<TLambda>(method.Name, returned, args);
// if not static then the first generic arg must be the declaring type
if (!isStatic && genericArgs[gx++] != type)
ThrowInvalidLambda<TLambda>(method.Name, returned, args);
// all other generic args must match parameters
2018-01-26 17:55:20 +01:00
// except the last one, if it's a function, 'cos then it's the returned type
2017-09-29 15:50:30 +02:00
while (gx < genericArgs.Length - (isFunction ? 1 : 0))
if (genericArgs[gx++] != args[ax++])
ThrowInvalidLambda<TLambda>(method.Name, returned, args);
// if it's a function then the last one must match the returned type
2018-01-26 17:55:20 +01:00
if (isFunction)
{
if (genericArgs[gx] != returned)
{
if (genericArgs[gx].IsAssignableFrom(returned))
{
returned = genericArgs[gx]; // FIXME cast etc?!
}
else ThrowInvalidLambda<TLambda>(method.Name, returned, args);
}
}
//if (isFunction && !genericArgs[gx].IsAssignableFrom(returned))
// ThrowInvalidLambda<TLambda>(method.Name, returned, args);
2017-09-29 15:50:30 +02:00
// emit
return EmitMethod<TLambda>(returned, args, method);
}
/// <summary>
/// Emits an instance method.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the method.</typeparam>
/// <param name="methodName">The name of the method.</param>
/// <param name="mustExist">A value indicating whether the constructor must exist.</param>
/// <returns>The method. If <paramref name="mustExist"/> is <c>false</c>, returns null when the method does not exist.</returns>
/// <remarks>
/// <para>The method arguments are determined by <typeparamref name="TLambda"/> generic arguments.</para>
/// </remarks>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="methodName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when no proper method with name <paramref name="methodName"/> could be found.</exception>
/// <exception cref="ArgumentException">Occurs when Occurs when <typeparamref name="TLambda"/> does not match the method signature.</exception>
public static TLambda EmitMethod<TLambda>(string methodName, bool mustExist = true)
{
if (string.IsNullOrWhiteSpace(methodName))
throw new ArgumentNullOrEmptyException(nameof(methodName));
// validate lambda type
ValidateMethodLambda<TLambda>(out var isFunction);
// get instance and arguments types
var genericArgs = typeof(TLambda).GetGenericArguments();
var gx = 0;
var declaring = genericArgs[gx++];
var args = new Type[genericArgs.Length - 1 - (isFunction ? 1 : 0)];
for (var i = 0; i < args.Length; i++)
args[i] = genericArgs[gx++];
var returned = isFunction ? genericArgs[gx] : typeof (void);
// get the method infos
var method = declaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
null, args, null);
if (method == null || isFunction && method.ReturnType != returned)
{
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find method {declaring}.{methodName}({string.Join(", ", (IEnumerable<Type>) args)}).");
}
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
// emit
return EmitMethod<TLambda>(returned, args, method);
}
/// <summary>
/// Emits a static method.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TLambda">A lambda representing the method.</typeparam>
/// <param name="methodName">The name of the method.</param>
/// <param name="mustExist">A value indicating whether the constructor must exist.</param>
/// <returns>The method. If <paramref name="mustExist"/> is <c>false</c>, returns null when the method does not exist.</returns>
/// <remarks>
/// <para>The method arguments are determined by <typeparamref name="TLambda"/> generic arguments.</para>
/// </remarks>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="methodName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when no proper method with name <paramref name="methodName"/> could be found.</exception>
/// <exception cref="ArgumentException">Occurs when Occurs when <typeparamref name="TLambda"/> does not match the method signature.</exception>
public static TLambda EmitMethod<TDeclaring, TLambda>(string methodName, bool mustExist = true)
{
return EmitMethod<TLambda>(typeof (TDeclaring), methodName, mustExist);
}
/// <summary>
/// Emits a static method.
/// </summary>
/// <typeparam name="TLambda">A lambda representing the method.</typeparam>
/// <param name="declaring">The declaring type.</param>
/// <param name="methodName">The name of the method.</param>
/// <param name="mustExist">A value indicating whether the constructor must exist.</param>
/// <returns>The method. If <paramref name="mustExist"/> is <c>false</c>, returns null when the method does not exist.</returns>
/// <remarks>
/// <para>The method arguments are determined by <typeparamref name="TLambda"/> generic arguments.</para>
/// </remarks>
/// <exception cref="ArgumentNullOrEmptyException">Occurs when <paramref name="methodName"/> is null or empty.</exception>
/// <exception cref="InvalidOperationException">Occurs when no proper method with name <paramref name="methodName"/> could be found.</exception>
/// <exception cref="ArgumentException">Occurs when Occurs when <typeparamref name="TLambda"/> does not match the method signature.</exception>
public static TLambda EmitMethod<TLambda>(Type declaring, string methodName, bool mustExist = true)
{
if (string.IsNullOrWhiteSpace(methodName))
throw new ArgumentNullOrEmptyException(nameof(methodName));
// validate lambda type
ValidateMethodLambda<TLambda>(out var isFunction);
// get instance and arguments types
var genericArgs = typeof(TLambda).GetGenericArguments();
var args = new Type[genericArgs.Length - (isFunction ? 1 : 0)];
for (var i = 0; i < args.Length; i++)
args[i] = genericArgs[i];
var returned = isFunction ? genericArgs[args.Length] : typeof (void);
// get the method infos
var method = declaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static,
null, args, null);
if (method == null || isFunction && method.ReturnType != returned)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if (!mustExist) return default;
throw new InvalidOperationException($"Could not find static method {declaring}.{methodName}({string.Join(", ", (IEnumerable<Type>) args)}).");
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
// emit
return EmitMethod<TLambda>(returned, args, method);
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
private static TLambda EmitMethod<TLambda>(Type returned, Type[] args, MethodInfo method)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
var args2 = args;
if (!method.IsStatic)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
args2 = new Type[args.Length + 1];
args2[0] = method.DeclaringType;
Array.Copy(args, 0, args2, 1, args.Length);
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
var module = method.DeclaringType?.Module;
if (module == null)
throw new ArgumentException("Failed to get method's declaring type module.", nameof(method));
var dm = new DynamicMethod(string.Empty, returned, args2, module, true);
var ilgen = dm.GetILGenerator();
EmitLdargs(ilgen, args2.Length);
ilgen.Emit(method.IsStatic ? OpCodes.Call : OpCodes.Callvirt, method);
ilgen.Emit(OpCodes.Ret);
return (TLambda) (object) dm.CreateDelegate(typeof(TLambda));
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
private static void EmitLdargs(ILGenerator ilgen, int count)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if (count < 5)
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if (count > 0)
ilgen.Emit(OpCodes.Ldarg_0);
if (count > 1)
ilgen.Emit(OpCodes.Ldarg_1);
if (count > 2)
ilgen.Emit(OpCodes.Ldarg_2);
if (count > 3)
ilgen.Emit(OpCodes.Ldarg_3);
2017-09-27 21:16:09 +02:00
}
else
{
2017-09-29 15:50:30 +02:00
for (var i = 0; i < count; i++)
ilgen.Emit(OpCodes.Ldarg, i);
2017-09-27 21:16:09 +02:00
}
}
2017-09-29 15:50:30 +02:00
private static void ThrowInvalidLambda<TLambda>(string methodName, Type returned, Type[] args)
{
throw new ArgumentException($"Lambda {typeof(TLambda)} does not match {methodName}({string.Join(", ", (IEnumerable<Type>) args)}):{returned}.", nameof(TLambda));
}
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
// the code below should NOT be used
//
// keeping all this around as a reference for now - building expressions into dynamic assemblies,
// the resulting methods are fast (as fast as IL-generated methods) whereas the standard compiled
// expressions are slowish - alas, the compiled methods do not have access to eg private members
// and anything that would violate access control - we're not using it anymore - still used in a
// benchmark
internal static Func<TArg0, TInstance> GetCtor<TInstance, TArg0>()
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var type = typeof (TInstance);
var type0 = typeof (TArg0);
2017-09-25 12:58:54 +02:00
// get the constructor infos
var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
null, new[] { type0 }, null);
if (ctor == null)
throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0}).");
var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
// ReSharper disable once CoVariantArrayConversion
var exprNew = Expression.New(ctor, exprArgs);
var expr = Expression.Lambda<Func<TArg0, TInstance>>(exprNew, exprArgs);
2017-09-29 15:50:30 +02:00
return CompileToDelegate(expr);
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
internal static Func<TArg0, TArg1, TInstance> GetCtor<TInstance, TArg0, TArg1>()
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var type = typeof (TInstance);
var type0 = typeof (TArg0);
var type1 = typeof (TArg1);
2017-09-25 12:58:54 +02:00
// get the constructor infos
var ctor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
null, new[] { type0, type1 }, null);
if (ctor == null)
throw new InvalidOperationException($"Could not find constructor {type}.ctor({type0}, {type1}).");
var exprArgs = ctor.GetParameters().Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToArray();
// ReSharper disable once CoVariantArrayConversion
var exprNew = Expression.New(ctor, exprArgs);
var expr = Expression.Lambda<Func<TArg0, TArg1, TInstance>>(exprNew, exprArgs);
2017-09-29 15:50:30 +02:00
return CompileToDelegate(expr);
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
internal static TMethod GetMethod<TMethod>(MethodInfo method)
2017-09-25 12:58:54 +02:00
{
var type = method.DeclaringType;
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
return GetStaticMethod<TMethod>(method, method.Name, type, parameterTypes, returnType);
}
2017-09-29 15:50:30 +02:00
internal static TMethod GetMethod<TInstance, TMethod>(MethodInfo method)
2017-09-25 12:58:54 +02:00
{
var type = method.DeclaringType;
GetMethodParms<TInstance, TMethod>(out var parameterTypes, out var returnType);
return GetMethod<TMethod>(method, method.Name, type, parameterTypes, returnType);
}
2017-09-29 15:50:30 +02:00
internal static TMethod GetMethod<TInstance, TMethod>(string methodName)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var type = typeof (TInstance);
2017-09-25 12:58:54 +02:00
GetMethodParms<TInstance, TMethod>(out var parameterTypes, out var returnType);
var method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance,
null, parameterTypes, null);
return GetMethod<TMethod>(method, methodName, type, parameterTypes, returnType);
}
private static void GetMethodParms<TMethod>(out Type[] parameterTypes, out Type returnType)
{
2017-09-27 21:16:09 +02:00
var typeM = typeof (TMethod);
2017-09-25 12:58:54 +02:00
var typeList = new List<Type>();
2017-09-27 21:16:09 +02:00
returnType = typeof (void);
2017-09-25 12:58:54 +02:00
2017-09-27 21:16:09 +02:00
if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate))
2017-09-25 12:58:54 +02:00
throw new InvalidOperationException("Invalid TMethod, must be a Func or Action.");
var typeName = typeM.FullName;
2017-09-27 21:16:09 +02:00
if (typeName == null)
throw new InvalidOperationException($"Could not get {typeM} type name.");
2017-09-25 12:58:54 +02:00
if (typeName.StartsWith("System.Func`"))
{
var i = 0;
while (i < typeM.GenericTypeArguments.Length - 1)
typeList.Add(typeM.GenericTypeArguments[i++]);
returnType = typeM.GenericTypeArguments[i];
}
else if (typeName.StartsWith("System.Action`"))
{
var i = 0;
while (i < typeM.GenericTypeArguments.Length)
typeList.Add(typeM.GenericTypeArguments[i++]);
}
else if (typeName == "System.Action")
{
// no args
}
else
throw new InvalidOperationException(typeName);
parameterTypes = typeList.ToArray();
}
private static void GetMethodParms<TInstance, TMethod>(out Type[] parameterTypes, out Type returnType)
{
2017-09-27 21:16:09 +02:00
var type = typeof (TInstance);
2017-09-25 12:58:54 +02:00
2017-09-27 21:16:09 +02:00
var typeM = typeof (TMethod);
2017-09-25 12:58:54 +02:00
var typeList = new List<Type>();
2017-09-27 21:16:09 +02:00
returnType = typeof (void);
2017-09-25 12:58:54 +02:00
2017-09-27 21:16:09 +02:00
if (!typeof (MulticastDelegate).IsAssignableFrom(typeM) || typeM == typeof (MulticastDelegate))
2017-09-25 12:58:54 +02:00
throw new InvalidOperationException("Invalid TMethod, must be a Func or Action.");
var typeName = typeM.FullName;
2017-09-27 21:16:09 +02:00
if (typeName == null)
throw new InvalidOperationException($"Could not get {typeM} type name.");
2017-09-25 12:58:54 +02:00
if (!typeM.IsGenericType)
2017-09-27 21:16:09 +02:00
throw new InvalidOperationException($"Type {typeName} is not generic.");
2017-09-25 12:58:54 +02:00
if (typeM.GenericTypeArguments[0] != type)
2017-09-27 21:16:09 +02:00
throw new InvalidOperationException($"Invalid type {typeName}, the first generic argument must be {type.FullName}.");
2017-09-25 12:58:54 +02:00
if (typeName.StartsWith("System.Func`"))
{
var i = 1;
while (i < typeM.GenericTypeArguments.Length - 1)
typeList.Add(typeM.GenericTypeArguments[i++]);
returnType = typeM.GenericTypeArguments[i];
}
else if (typeName.StartsWith("System.Action`"))
{
var i = 1;
while (i < typeM.GenericTypeArguments.Length)
typeList.Add(typeM.GenericTypeArguments[i++]);
}
else
throw new InvalidOperationException(typeName);
parameterTypes = typeList.ToArray();
}
private static TMethod GetStaticMethod<TMethod>(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType)
{
if (method == null || method.ReturnType != returnType)
throw new InvalidOperationException($"Could not find static method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}");
var e = new List<ParameterExpression>();
foreach (var p in method.GetParameters())
e.Add(Expression.Parameter(p.ParameterType, p.Name));
var exprCallArgs = e.ToArray();
var exprLambdaArgs = exprCallArgs;
// ReSharper disable once CoVariantArrayConversion
var exprCall = Expression.Call(method, exprCallArgs);
var expr = Expression.Lambda<TMethod>(exprCall, exprLambdaArgs);
2017-09-29 15:50:30 +02:00
return CompileToDelegate(expr);
2017-09-25 12:58:54 +02:00
}
private static TMethod GetMethod<TMethod>(MethodInfo method, string methodName, Type type, Type[] parameterTypes, Type returnType)
{
if (method == null || method.ReturnType != returnType)
throw new InvalidOperationException($"Could not find method {type}.{methodName}({string.Join(",", parameterTypes.Select(x => x.ToString()))}) : {returnType}");
var e = new List<ParameterExpression>();
foreach (var p in method.GetParameters())
e.Add(Expression.Parameter(p.ParameterType, p.Name));
var exprCallArgs = e.ToArray();
var exprThis = Expression.Parameter(type, "this");
e.Insert(0, exprThis);
var exprLambdaArgs = e.ToArray();
// ReSharper disable once CoVariantArrayConversion
var exprCall = Expression.Call(exprThis, method, exprCallArgs);
var expr = Expression.Lambda<TMethod>(exprCall, exprLambdaArgs);
return expr.Compile();
}
2017-09-29 15:50:30 +02:00
internal const AssemblyBuilderAccess DefaultAssemblyBuilderAccess = AssemblyBuilderAccess.Run;
internal const AssemblyBuilderAccess NoAssembly = 0;
2017-09-27 21:16:09 +02:00
2017-09-29 15:50:30 +02:00
internal static TLambda Compile<TLambda>(Expression<TLambda> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-27 21:16:09 +02:00
{
return access == NoAssembly
? expr.Compile()
: CompileToDelegate(expr, access);
}
2017-09-25 12:58:54 +02:00
2017-09-29 15:50:30 +02:00
internal static Action CompileToDelegate(Expression<Action> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static); // CompileToMethod requires a static method
expr.CompileToMethod(builder);
return (Action) Delegate.CreateDelegate(typeof (Action), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Action<T1> CompileToDelegate<T1>(Expression<Action<T1>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
typeof (void), new[] { typeof (T1) });
expr.CompileToMethod(builder);
return (Action<T1>) Delegate.CreateDelegate(typeof (Action<T1>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Action<T1, T2> CompileToDelegate<T1, T2>(Expression<Action<T1, T2>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
2017-09-27 21:16:09 +02:00
typeof (void), new[] { typeof (T1), typeof (T2) });
2017-09-25 12:58:54 +02:00
expr.CompileToMethod(builder);
return (Action<T1, T2>) Delegate.CreateDelegate(typeof (Action<T1, T2>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Action<T1, T2, T3> CompileToDelegate<T1, T2, T3>(Expression<Action<T1, T2, T3>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
2017-09-27 21:16:09 +02:00
typeof (void), new[] { typeof (T1), typeof (T2), typeof (T3) });
2017-09-25 12:58:54 +02:00
expr.CompileToMethod(builder);
return (Action<T1, T2, T3>) Delegate.CreateDelegate(typeof (Action<T1, T2, T3>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Func<TResult> CompileToDelegate<TResult>(Expression<Func<TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static,
typeof (TResult), Array.Empty<Type>()); // CompileToMethod requires a static method
expr.CompileToMethod(builder);
return (Func<TResult>) Delegate.CreateDelegate(typeof (Func<TResult>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Func<T1, TResult> CompileToDelegate<T1, TResult>(Expression<Func<T1, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
typeof (TResult), new[] { typeof (T1) });
expr.CompileToMethod(builder);
2017-09-27 21:16:09 +02:00
return (Func<T1, TResult>) Delegate.CreateDelegate(typeof (Func<T1, TResult>), typeBuilder.CreateType().GetMethod("Method"));
2017-09-25 12:58:54 +02:00
}
2017-09-29 15:50:30 +02:00
internal static Func<T1, T2, TResult> CompileToDelegate<T1, T2, TResult>(Expression<Func<T1, T2, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
2017-09-27 21:16:09 +02:00
typeof (TResult), new[] { typeof (T1), typeof (T2) });
2017-09-25 12:58:54 +02:00
expr.CompileToMethod(builder);
return (Func<T1, T2, TResult>) Delegate.CreateDelegate(typeof (Func<T1, T2, TResult>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static Func<T1, T2, T3, TResult> CompileToDelegate<T1, T2, T3, TResult>(Expression<Func<T1, T2, T3, TResult>> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
typeof (TResult), new[] { typeof (T1), typeof (T2), typeof (T3) });
expr.CompileToMethod(builder);
return (Func<T1, T2, T3, TResult>) Delegate.CreateDelegate(typeof (Func<T1, T2, T3, TResult>), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static TMethod CompileToDelegate<TMethod>(Expression<TMethod> expr, AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
var builder = typeBuilder.DefineMethod("Method",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
returnType, parameterTypes);
expr.CompileToMethod(builder);
return (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), typeBuilder.CreateType().GetMethod("Method"));
}
2017-09-29 15:50:30 +02:00
internal static TMethod[] CompileToDelegates<TMethod>(params Expression<TMethod>[] exprs)
2017-09-27 21:16:09 +02:00
=> CompileToDelegates(AssemblyBuilderAccess.RunAndCollect, exprs);
2017-09-29 15:50:30 +02:00
internal static TMethod[] CompileToDelegates<TMethod>(AssemblyBuilderAccess access, params Expression<TMethod>[] exprs)
2017-09-25 12:58:54 +02:00
{
2017-09-27 21:16:09 +02:00
var typeBuilder = CreateTypeBuilder(access);
2017-09-25 12:58:54 +02:00
GetMethodParms<TMethod>(out var parameterTypes, out var returnType);
var i = 0;
foreach (var expr in exprs)
{
var builder = typeBuilder.DefineMethod($"Method_{i++}",
MethodAttributes.Public | MethodAttributes.Static, // CompileToMethod requires a static method
returnType, parameterTypes);
expr.CompileToMethod(builder);
}
var type = typeBuilder.CreateType();
var methods = new TMethod[exprs.Length];
for (i = 0; i < exprs.Length; i++)
methods[i] = (TMethod) (object) Delegate.CreateDelegate(typeof (TMethod), type.GetMethod($"Method_{i++}"));
return methods;
}
2017-09-27 21:16:09 +02:00
private static TypeBuilder CreateTypeBuilder(AssemblyBuilderAccess access = DefaultAssemblyBuilderAccess)
2017-09-25 12:58:54 +02:00
{
var assemblyName = new AssemblyName("Umbraco.Core.DynamicAssemblies." + Guid.NewGuid().ToString("N"));
2017-09-27 21:16:09 +02:00
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
2017-09-25 12:58:54 +02:00
return module.DefineType("Class", TypeAttributes.Public | TypeAttributes.Abstract);
}
}
}