2022-06-07 15:28:38 +02:00
|
|
|
|
using System.Reflection;
|
2017-09-25 12:58:54 +02:00
|
|
|
|
using System.Reflection.Emit;
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
namespace Umbraco.Cms.Core;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Provides utilities to simplify reflection.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <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>
|
|
|
|
|
|
public static class ReflectionUtilities
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#region Fields
|
|
|
|
|
|
|
2017-09-25 12:58:54 +02:00
|
|
|
|
/// <summary>
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// Emits a field getter.
|
2017-09-25 12:58:54 +02:00
|
|
|
|
/// </summary>
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
|
|
|
|
|
|
/// <typeparam name="TValue">The field type.</typeparam>
|
|
|
|
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// A field getter function.
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">fieldName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="fieldName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static Func<TDeclaring, TValue> EmitFieldGetter<TDeclaring, TValue>(string fieldName)
|
|
|
|
|
|
{
|
|
|
|
|
|
FieldInfo field = GetField<TDeclaring, TValue>(fieldName);
|
|
|
|
|
|
return EmitFieldGetter<TDeclaring, TValue>(field);
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Emits a field setter.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
|
|
|
|
|
|
/// <typeparam name="TValue">The field type.</typeparam>
|
|
|
|
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// A field setter action.
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">fieldName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="fieldName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static Action<TDeclaring, TValue> EmitFieldSetter<TDeclaring, TValue>(string fieldName)
|
|
|
|
|
|
{
|
|
|
|
|
|
FieldInfo field = GetField<TDeclaring, TValue>(fieldName);
|
|
|
|
|
|
return EmitFieldSetter<TDeclaring, TValue>(field);
|
|
|
|
|
|
}
|
2017-09-27 21:16:09 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Emits a field getter and setter.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
|
|
|
|
|
|
/// <typeparam name="TValue">The field type.</typeparam>
|
|
|
|
|
|
/// <param name="fieldName">The name of the field.</param>
|
|
|
|
|
|
/// <returns>
|
|
|
|
|
|
/// A field getter and setter functions.
|
|
|
|
|
|
/// </returns>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">fieldName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="fieldName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static (Func<TDeclaring, TValue>, Action<TDeclaring, TValue>) EmitFieldGetterAndSetter<TDeclaring, TValue>(
|
|
|
|
|
|
string fieldName)
|
|
|
|
|
|
{
|
|
|
|
|
|
FieldInfo field = GetField<TDeclaring, TValue>(fieldName);
|
|
|
|
|
|
return (EmitFieldGetter<TDeclaring, TValue>(field), EmitFieldSetter<TDeclaring, TValue>(field));
|
|
|
|
|
|
}
|
2018-02-02 19:43:03 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the field.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <typeparam name="TDeclaring">The type of the declaring.</typeparam>
|
|
|
|
|
|
/// <typeparam name="TValue">The type of the value.</typeparam>
|
|
|
|
|
|
/// <param name="fieldName">Name of the field.</param>
|
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">fieldName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="fieldName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find field <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="fieldName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
private static FieldInfo GetField<TDeclaring, TValue>(string fieldName)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (fieldName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(fieldName));
|
2017-09-29 15:50:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(fieldName))
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(fieldName));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// get the field
|
|
|
|
|
|
FieldInfo? field = typeof(TDeclaring).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
|
|
|
|
|
if (field == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException($"Could not find field {typeof(TDeclaring)}.{fieldName}.");
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// validate field type
|
|
|
|
|
|
if (field.FieldType != typeof(TValue))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException(
|
|
|
|
|
|
$"Value type {typeof(TValue)} does not match field {typeof(TDeclaring)}.{fieldName} type {field.FieldType}.");
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return field;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Func<TDeclaring, TValue> EmitFieldGetter<TDeclaring, TValue>(FieldInfo field)
|
|
|
|
|
|
{
|
|
|
|
|
|
// emit
|
|
|
|
|
|
(DynamicMethod dm, ILGenerator ilgen) =
|
|
|
|
|
|
CreateIlGenerator(field.DeclaringType?.Module, new[] { typeof(TDeclaring) }, typeof(TValue));
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldarg_0);
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldfld, field);
|
|
|
|
|
|
ilgen.Return();
|
|
|
|
|
|
|
|
|
|
|
|
return (Func<TDeclaring, TValue>)dm.CreateDelegate(typeof(Func<TDeclaring, TValue>));
|
|
|
|
|
|
}
|
2018-02-02 19:43:03 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
private static Action<TDeclaring, TValue> EmitFieldSetter<TDeclaring, TValue>(FieldInfo field)
|
|
|
|
|
|
{
|
|
|
|
|
|
// emit
|
|
|
|
|
|
(DynamicMethod dm, ILGenerator ilgen) = CreateIlGenerator(field.DeclaringType?.Module, new[] { typeof(TDeclaring), typeof(TValue) }, typeof(void));
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldarg_0);
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldarg_1);
|
|
|
|
|
|
ilgen.Emit(OpCodes.Stfld, field);
|
|
|
|
|
|
ilgen.Return();
|
|
|
|
|
|
|
|
|
|
|
|
return (Action<TDeclaring, TValue>)dm.CreateDelegate(typeof(Action<TDeclaring, TValue>));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region Properties
|
|
|
|
|
|
|
|
|
|
|
|
/// <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="ArgumentNullException">propertyName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="propertyName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match property <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="propertyName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find property getter for <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="propertyName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static Func<TDeclaring, TValue>? EmitPropertyGetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyName));
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(propertyName))
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (property?.GetMethod != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return EmitMethod<Func<TDeclaring, TValue>>(property.GetMethod);
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!mustExist)
|
|
|
|
|
|
{
|
|
|
|
|
|
return default;
|
|
|
|
|
|
}
|
2018-02-02 19:43:03 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException($"Could not find getter for {typeof(TDeclaring)}.{propertyName}.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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="ArgumentNullException">propertyName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="propertyName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match property <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="propertyName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find property setter for <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="propertyName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static Action<TDeclaring, TValue>? EmitPropertySetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyName));
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(propertyName))
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (property?.SetMethod != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return EmitMethod<Action<TDeclaring, TValue>>(property.SetMethod);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!mustExist)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return default;
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException($"Could not find setter for {typeof(TDeclaring)}.{propertyName}.");
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +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="ArgumentNullException">propertyName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="propertyName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Value type <typeparamref name="TValue" /> does not match property <typeparamref name="TDeclaring" />.
|
|
|
|
|
|
/// <paramref name="propertyName" /> type.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Could not find property getter and setter for
|
|
|
|
|
|
/// <typeparamref name="TDeclaring" />.<paramref name="propertyName" />.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
public static (Func<TDeclaring, TValue>, Action<TDeclaring, TValue>)
|
|
|
|
|
|
EmitPropertyGetterAndSetter<TDeclaring, TValue>(string propertyName, bool mustExist = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyName));
|
2017-09-29 15:50:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(propertyName))
|
2017-09-29 15:50:30 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(propertyName));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
PropertyInfo? property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (property?.GetMethod != null && property.SetMethod != null)
|
|
|
|
|
|
{
|
2017-09-29 15:50:30 +02:00
|
|
|
|
return (
|
2022-06-07 15:28:38 +02:00
|
|
|
|
EmitMethod<Func<TDeclaring, TValue>>(property.GetMethod),
|
|
|
|
|
|
EmitMethod<Action<TDeclaring, TValue>>(property.SetMethod));
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!mustExist)
|
2017-09-26 14:57:50 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return default;
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}.");
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +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)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyInfo == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyInfo));
|
2018-02-02 19:43:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (propertyInfo.GetMethod == null)
|
2018-02-02 19:43:03 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException("Property has no getter.", nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return EmitMethod<Func<TDeclaring, TValue>>(propertyInfo.GetMethod);
|
|
|
|
|
|
}
|
2017-09-26 14:57:50 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +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)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyInfo == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (propertyInfo.SetMethod == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Property has no setter.", nameof(propertyInfo));
|
2017-09-26 14:57:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
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)
|
2017-09-27 21:16:09 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentNullException(nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-27 21:16:09 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (propertyInfo.GetMethod == null || propertyInfo.SetMethod == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Property has no getter and/or no setter.", nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return (
|
|
|
|
|
|
EmitMethod<Func<TDeclaring, TValue>>(propertyInfo.GetMethod),
|
|
|
|
|
|
EmitMethod<Action<TDeclaring, TValue>>(propertyInfo.SetMethod));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +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> EmitPropertySetterUnsafe<TDeclaring, TValue>(PropertyInfo propertyInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (propertyInfo == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (propertyInfo.SetMethod == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Property has no setter.", nameof(propertyInfo));
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return EmitMethodUnsafe<Action<TDeclaring, TValue>>(propertyInfo.SetMethod);
|
|
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#endregion
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#region Constructors
|
2017-09-27 21:16:09 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <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? EmitConstructor<TLambda>(bool mustExist = true, Type? declaring = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
(_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda<TLambda>(true, true);
|
|
|
|
|
|
|
|
|
|
|
|
// determine returned / declaring type
|
|
|
|
|
|
if (declaring == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
declaring = lambdaReturned;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (!lambdaReturned.IsAssignableFrom(declaring))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException($"Type {lambdaReturned} is not assignable from type {declaring}.", nameof(declaring));
|
2017-09-27 21:16:09 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// get the constructor infos
|
|
|
|
|
|
ConstructorInfo? ctor = declaring.GetConstructor(
|
|
|
|
|
|
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null);
|
|
|
|
|
|
if (ctor == null)
|
2017-09-27 21:16:09 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!mustExist)
|
2017-09-27 21:16:09 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return default;
|
2017-09-27 21:16:09 +02:00
|
|
|
|
}
|
2017-09-29 15:50:30 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"Could not find constructor {declaring}.ctor({string.Join(", ", (IEnumerable<Type>)lambdaParameters)}).");
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
return EmitConstructorSafe<TLambda>(lambdaParameters, lambdaReturned, ctor);
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +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 EmitConstructor<TLambda>(ConstructorInfo ctor)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ctor == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(ctor));
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
(_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda<TLambda>(true, true);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return EmitConstructorSafe<TLambda>(lambdaParameters, lambdaReturned, ctor);
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
private static TLambda EmitConstructorSafe<TLambda>(Type[] lambdaParameters, Type returned, ConstructorInfo ctor)
|
|
|
|
|
|
{
|
|
|
|
|
|
// get type and args
|
|
|
|
|
|
Type? ctorDeclaring = ctor.DeclaringType;
|
|
|
|
|
|
Type[] ctorParameters = ctor.GetParameters().Select(x => x.ParameterType).ToArray();
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// validate arguments
|
|
|
|
|
|
if (lambdaParameters.Length != ctorParameters.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
ThrowInvalidLambda<TLambda>("ctor", ctorDeclaring, ctorParameters);
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
for (var i = 0; i < lambdaParameters.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
// note: relax the constraint with IsAssignableFrom?
|
|
|
|
|
|
if (lambdaParameters[i] != ctorParameters[i])
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ThrowInvalidLambda<TLambda>("ctor", ctorDeclaring, ctorParameters);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!returned.IsAssignableFrom(ctorDeclaring))
|
|
|
|
|
|
{
|
|
|
|
|
|
ThrowInvalidLambda<TLambda>("ctor", ctorDeclaring, ctorParameters);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
return EmitConstructor<TLambda>(ctorDeclaring, ctorParameters, 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 EmitConstructorUnsafe<TLambda>(ConstructorInfo ctor)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ctor == null)
|
2017-09-27 21:16:09 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentNullException(nameof(ctor));
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
(_, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda<TLambda>(true, true);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit - unsafe - use lambda's args and assume they are correct
|
|
|
|
|
|
return EmitConstructor<TLambda>(lambdaReturned, lambdaParameters, ctor);
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
private static TLambda EmitConstructor<TLambda>(Type? declaring, Type[] lambdaParameters, ConstructorInfo ctor)
|
|
|
|
|
|
{
|
|
|
|
|
|
// gets the method argument types
|
|
|
|
|
|
Type[] ctorParameters = GetParameters(ctor);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
(DynamicMethod dm, ILGenerator ilgen) =
|
|
|
|
|
|
CreateIlGenerator(ctor.DeclaringType?.Module, lambdaParameters, declaring);
|
|
|
|
|
|
EmitLdargs(ilgen, lambdaParameters, ctorParameters);
|
|
|
|
|
|
ilgen.Emit(OpCodes.Newobj, ctor); // ok to just return, it's only objects
|
|
|
|
|
|
ilgen.Return();
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return (TLambda)(object)dm.CreateDelegate(typeof(TLambda));
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#endregion
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#region Methods
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <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>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">methodName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="methodName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Occurs when <typeparamref name="TLambda" /> does not match the method signature..
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Occurs when no proper method with name <paramref name="methodName" /> could
|
|
|
|
|
|
/// be found.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
public static TLambda? EmitMethod<TDeclaring, TLambda>(string methodName, bool mustExist = true) =>
|
|
|
|
|
|
EmitMethod<TLambda>(typeof(TDeclaring), methodName, mustExist);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <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>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">methodName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="methodName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Occurs when <typeparamref name="TLambda" /> does not match the method signature..
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Occurs when no proper method with name <paramref name="methodName" /> could
|
|
|
|
|
|
/// be found.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
public static TLambda? EmitMethod<TLambda>(Type declaring, string methodName, bool mustExist = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (methodName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(methodName));
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(methodName))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName));
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
(Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) =
|
|
|
|
|
|
AnalyzeLambda<TLambda>(true, out var isFunction);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// get the method infos
|
|
|
|
|
|
MethodInfo? method = declaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, lambdaParameters, null);
|
|
|
|
|
|
if (method == null || (isFunction && !lambdaReturned.IsAssignableFrom(method.ReturnType)))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!mustExist)
|
|
|
|
|
|
{
|
|
|
|
|
|
return default;
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"Could not find static method {declaring}.{methodName}({string.Join(", ", (IEnumerable<Type>)lambdaParameters)}).");
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
return EmitMethod<TLambda>(lambdaDeclaring, lambdaReturned, lambdaParameters, method);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <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)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (method == null)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentNullException(nameof(method));
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// get type and args
|
|
|
|
|
|
Type? methodDeclaring = method.DeclaringType;
|
|
|
|
|
|
Type methodReturned = method.ReturnType;
|
|
|
|
|
|
Type[] methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
var isStatic = method.IsStatic;
|
|
|
|
|
|
(Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) =
|
|
|
|
|
|
AnalyzeLambda<TLambda>(isStatic, out var isFunction);
|
|
|
|
|
|
|
|
|
|
|
|
// if not static, then the first lambda arg must be the method declaring type
|
|
|
|
|
|
if (!isStatic && (methodDeclaring == null || !methodDeclaring.IsAssignableFrom(lambdaDeclaring)))
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ThrowInvalidLambda<TLambda>(method.Name, methodReturned, methodParameters);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (methodParameters.Length != lambdaParameters.Length)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ThrowInvalidLambda<TLambda>(method.Name, methodReturned, methodParameters);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
for (var i = 0; i < methodParameters.Length; i++)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (!methodParameters[i].IsAssignableFrom(lambdaParameters[i]))
|
2018-02-02 19:43:03 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ThrowInvalidLambda<TLambda>(method.Name, methodReturned, methodParameters);
|
2018-03-01 14:50:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// if it's a function then the last lambda arg must match the method returned type
|
|
|
|
|
|
if (isFunction && !lambdaReturned.IsAssignableFrom(methodReturned))
|
2018-03-01 14:50:42 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ThrowInvalidLambda<TLambda>(method.Name, methodReturned, methodParameters);
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
return EmitMethod<TLambda>(lambdaDeclaring, lambdaReturned, lambdaParameters, method);
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +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 EmitMethodUnsafe<TLambda>(MethodInfo method)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (method == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(method));
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
var isStatic = method.IsStatic;
|
|
|
|
|
|
(Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) = AnalyzeLambda<TLambda>(isStatic, out _);
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit - unsafe - use lambda's args and assume they are correct
|
|
|
|
|
|
return EmitMethod<TLambda>(lambdaDeclaring, lambdaReturned, lambdaParameters, method);
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
/// <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>
|
|
|
|
|
|
/// <exception cref="ArgumentNullException">methodName</exception>
|
|
|
|
|
|
/// <exception cref="ArgumentException">
|
|
|
|
|
|
/// Value can't be empty or consist only of white-space characters. - <paramref name="methodName" />
|
|
|
|
|
|
/// or
|
|
|
|
|
|
/// Occurs when <typeparamref name="TLambda" /> does not match the method signature..
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">
|
|
|
|
|
|
/// Occurs when no proper method with name <paramref name="methodName" /> could
|
|
|
|
|
|
/// be found.
|
|
|
|
|
|
/// </exception>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
public static TLambda? EmitMethod<TLambda>(string methodName, bool mustExist = true)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (methodName == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(methodName));
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (string.IsNullOrWhiteSpace(methodName))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(methodName));
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// validate lambda type
|
|
|
|
|
|
(Type? lambdaDeclaring, Type[] lambdaParameters, Type lambdaReturned) =
|
|
|
|
|
|
AnalyzeLambda<TLambda>(false, out var isFunction);
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// get the method infos
|
|
|
|
|
|
MethodInfo? method = lambdaDeclaring?.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null);
|
|
|
|
|
|
if (method == null || (isFunction && method.ReturnType != lambdaReturned))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!mustExist)
|
|
|
|
|
|
{
|
|
|
|
|
|
return default;
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new InvalidOperationException(
|
|
|
|
|
|
$"Could not find method {lambdaDeclaring}.{methodName}({string.Join(", ", (IEnumerable<Type>)lambdaParameters)}).");
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit
|
|
|
|
|
|
return EmitMethod<TLambda>(lambdaDeclaring, lambdaReturned, lambdaParameters, method);
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// lambdaReturned = the lambda returned type (can be void)
|
|
|
|
|
|
// lambdaArgTypes = the lambda argument types
|
|
|
|
|
|
private static TLambda EmitMethod<TLambda>(Type? lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, MethodInfo method)
|
|
|
|
|
|
{
|
|
|
|
|
|
// non-static methods need the declaring type as first arg
|
|
|
|
|
|
Type[] parameters = lambdaParameters;
|
|
|
|
|
|
if (!method.IsStatic)
|
|
|
|
|
|
{
|
|
|
|
|
|
parameters = new Type[lambdaParameters.Length + 1];
|
|
|
|
|
|
parameters[0] = lambdaDeclaring ?? method.DeclaringType!;
|
|
|
|
|
|
Array.Copy(lambdaParameters, 0, parameters, 1, lambdaParameters.Length);
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// gets the method argument types
|
|
|
|
|
|
Type[] methodArgTypes = GetParameters(method, !method.IsStatic);
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emit IL
|
|
|
|
|
|
(DynamicMethod dm, ILGenerator ilgen) =
|
|
|
|
|
|
CreateIlGenerator(method.DeclaringType?.Module, parameters, lambdaReturned);
|
|
|
|
|
|
EmitLdargs(ilgen, parameters, methodArgTypes);
|
|
|
|
|
|
ilgen.CallMethod(method);
|
|
|
|
|
|
EmitOutputAdapter(ilgen, lambdaReturned, method.ReturnType);
|
|
|
|
|
|
ilgen.Return();
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// create
|
|
|
|
|
|
return (TLambda)(object)dm.CreateDelegate(typeof(TLambda));
|
|
|
|
|
|
}
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#endregion
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
#region Utilities
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// when !isStatic, the first generic argument of the lambda is the declaring type
|
|
|
|
|
|
// hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument
|
|
|
|
|
|
// when isFunction, the last generic argument of the lambda is the returned type
|
|
|
|
|
|
// everything in between is parameters
|
|
|
|
|
|
private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda<TLambda>(bool isStatic, bool isFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
Type typeLambda = typeof(TLambda);
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
(Type? declaring, Type[] parameters, Type returned) = AnalyzeLambda<TLambda>(isStatic, out var maybeFunction);
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (isFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!maybeFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException($"Lambda {typeLambda} is an Action, a Func was expected.", nameof(TLambda));
|
2018-03-01 14:50:42 +01:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
if (maybeFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException($"Lambda {typeLambda} is a Func, an Action was expected.", nameof(TLambda));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (declaring, parameters, returned);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// when !isStatic, the first generic argument of the lambda is the declaring type
|
|
|
|
|
|
// hence, when !isStatic, the lambda cannot be a simple Action, as it requires at least one generic argument
|
|
|
|
|
|
// when isFunction, the last generic argument of the lambda is the returned type
|
|
|
|
|
|
// everything in between is parameters
|
|
|
|
|
|
private static (Type? Declaring, Type[] Parameters, Type Returned) AnalyzeLambda<TLambda>(bool isStatic, out bool isFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
isFunction = false;
|
|
|
|
|
|
|
|
|
|
|
|
Type typeLambda = typeof(TLambda);
|
|
|
|
|
|
|
|
|
|
|
|
var isAction = typeLambda.FullName == "System.Action";
|
|
|
|
|
|
if (isAction)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!isStatic)
|
2018-03-01 14:50:42 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
throw new ArgumentException(
|
|
|
|
|
|
$"Lambda {typeLambda} is an Action and can be used for static methods exclusively.",
|
|
|
|
|
|
nameof(TLambda));
|
2018-02-02 19:43:03 +01:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
|
|
|
|
|
|
return (null, Array.Empty<Type>(), typeof(void));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Type? genericDefinition = typeLambda.IsGenericType ? typeLambda.GetGenericTypeDefinition() : null;
|
|
|
|
|
|
var name = genericDefinition?.FullName;
|
|
|
|
|
|
|
|
|
|
|
|
if (name == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var isActionOf = name.StartsWith("System.Action`");
|
|
|
|
|
|
isFunction = name.StartsWith("System.Func`");
|
|
|
|
|
|
|
|
|
|
|
|
if (!isActionOf && !isFunction)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException($"Lambda {typeLambda} is not a Func nor an Action.", nameof(TLambda));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Type[] genericArgs = typeLambda.GetGenericArguments();
|
|
|
|
|
|
if (genericArgs.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("Panic: Func<> or Action<> has zero generic arguments.");
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
var i = 0;
|
|
|
|
|
|
Type declaring = isStatic ? typeof(void) : genericArgs[i++];
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
var parameterCount = genericArgs.Length - (isStatic ? 0 : 1) - (isFunction ? 1 : 0);
|
|
|
|
|
|
if (parameterCount < 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentException(
|
|
|
|
|
|
$"Lambda {typeLambda} is a Func and requires at least two arguments (declaring type and returned type).",
|
|
|
|
|
|
nameof(TLambda));
|
|
|
|
|
|
}
|
2018-03-07 09:34:23 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
var parameters = new Type[parameterCount];
|
|
|
|
|
|
for (var j = 0; j < parameterCount; j++)
|
2018-03-01 14:50:42 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
parameters[j] = genericArgs[i++];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Type returned = isFunction ? genericArgs[i] : typeof(void);
|
2018-06-07 14:29:31 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return (declaring, parameters, returned);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static (DynamicMethod, ILGenerator) CreateIlGenerator(Module? module, Type[] arguments, Type? returned)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (module == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new ArgumentNullException(nameof(module));
|
|
|
|
|
|
}
|
2018-03-07 09:34:23 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
var dm = new DynamicMethod(string.Empty, returned, arguments, module, true);
|
|
|
|
|
|
return (dm, dm.GetILGenerator());
|
|
|
|
|
|
}
|
2018-03-07 09:34:23 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
private static Type[] GetParameters(ConstructorInfo ctor)
|
|
|
|
|
|
{
|
|
|
|
|
|
ParameterInfo[] parameters = ctor.GetParameters();
|
|
|
|
|
|
var types = new Type[parameters.Length];
|
|
|
|
|
|
var i = 0;
|
|
|
|
|
|
foreach (ParameterInfo parameter in parameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
types[i++] = parameter.ParameterType;
|
2018-03-01 14:50:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return types;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static Type[] GetParameters(MethodInfo method, bool withDeclaring)
|
|
|
|
|
|
{
|
|
|
|
|
|
ParameterInfo[] parameters = method.GetParameters();
|
|
|
|
|
|
var types = new Type[parameters.Length + (withDeclaring ? 1 : 0)];
|
|
|
|
|
|
var i = 0;
|
|
|
|
|
|
if (withDeclaring)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
types[i++] = method.DeclaringType!;
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
foreach (ParameterInfo parameter in parameters)
|
|
|
|
|
|
{
|
|
|
|
|
|
types[i++] = parameter.ParameterType;
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return types;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// emits args
|
|
|
|
|
|
private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] methodArgTypes)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpCode[] ldargOpCodes = new[] { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 };
|
|
|
|
|
|
|
|
|
|
|
|
if (lambdaArgTypes.Length != methodArgTypes.Length)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("Panic: inconsistent number of args.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < lambdaArgTypes.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (lambdaArgTypes.Length < 5)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ilgen.Emit(ldargOpCodes[i]);
|
2018-02-02 19:43:03 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
ilgen.Emit(OpCodes.Ldarg, i);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
|
|
|
|
|
|
// var local = false;
|
|
|
|
|
|
EmitInputAdapter(ilgen, lambdaArgTypes[i], methodArgTypes[i] /*, ref local*/);
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// emits adapter opcodes after OpCodes.Ldarg
|
|
|
|
|
|
// inputType is the lambda input type
|
|
|
|
|
|
// methodParamType is the actual type expected by the actual method
|
|
|
|
|
|
// adding code to do inputType -> methodParamType
|
|
|
|
|
|
// valueType -> valueType : not supported ('cos, why?)
|
|
|
|
|
|
// valueType -> !valueType : not supported ('cos, why?)
|
|
|
|
|
|
// !valueType -> valueType : unbox and convert
|
|
|
|
|
|
// !valueType -> !valueType : cast (could throw)
|
|
|
|
|
|
private static void EmitInputAdapter(ILGenerator ilgen, Type inputType, Type methodParamType /*, ref bool local*/)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (inputType == methodParamType)
|
2017-09-25 12:58:54 +02:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
return;
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
2018-02-02 19:43:03 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (methodParamType.IsValueType)
|
2018-03-01 14:50:42 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (inputType.IsValueType)
|
2022-02-09 13:24:35 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// both input and parameter are value types
|
|
|
|
|
|
// not supported, use proper input
|
|
|
|
|
|
// (otherwise, would require converting)
|
|
|
|
|
|
throw new NotSupportedException("ValueTypes conversion.");
|
2022-02-09 13:24:35 +01:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
|
|
|
|
|
|
// parameter is value type, but input is reference type
|
|
|
|
|
|
// unbox the input to the parameter value type
|
|
|
|
|
|
// this is more or less equivalent to the ToT method below
|
|
|
|
|
|
Label unbox = ilgen.DefineLabel();
|
|
|
|
|
|
|
|
|
|
|
|
// if (!local)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// ilgen.DeclareLocal(typeof(object)); // declare local var for st/ld loc_0
|
|
|
|
|
|
// local = true;
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// stack: value
|
|
|
|
|
|
|
|
|
|
|
|
// following code can be replaced with .Dump (and then we don't need the local variable anymore)
|
|
|
|
|
|
// ilgen.Emit(OpCodes.Stloc_0); // pop value into loc.0
|
|
|
|
|
|
//// stack:
|
|
|
|
|
|
// ilgen.Emit(OpCodes.Ldloc_0); // push loc.0
|
|
|
|
|
|
// ilgen.Emit(OpCodes.Ldloc_0); // push loc.0
|
|
|
|
|
|
ilgen.Emit(OpCodes.Dup); // duplicate top of stack
|
|
|
|
|
|
|
|
|
|
|
|
// stack: value ; value
|
|
|
|
|
|
ilgen.Emit(OpCodes.Isinst, methodParamType); // test, pops value, and pushes either a null ref, or an instance of the type
|
|
|
|
|
|
|
|
|
|
|
|
// stack: inst|null ; value
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldnull); // push null
|
|
|
|
|
|
|
|
|
|
|
|
// stack: null ; inst|null ; value
|
|
|
|
|
|
ilgen.Emit(OpCodes.Cgt_Un); // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0
|
|
|
|
|
|
|
|
|
|
|
|
// stack: 0|1 ; value
|
|
|
|
|
|
ilgen.Emit(OpCodes.Brtrue_S, unbox); // pops value, branches to unbox if true, ie nonzero
|
|
|
|
|
|
|
|
|
|
|
|
// stack: value
|
|
|
|
|
|
ilgen.Convert(methodParamType); // convert
|
|
|
|
|
|
|
|
|
|
|
|
// stack: value|converted
|
|
|
|
|
|
ilgen.MarkLabel(unbox);
|
|
|
|
|
|
ilgen.Emit(OpCodes.Unbox_Any, methodParamType);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// parameter is reference type, but input is value type
|
|
|
|
|
|
// not supported, input should always be less constrained
|
|
|
|
|
|
// (otherwise, would require boxing and converting)
|
|
|
|
|
|
if (inputType.IsValueType)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException("ValueType boxing.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// both input and parameter are reference types
|
|
|
|
|
|
// cast the input to the parameter type
|
|
|
|
|
|
ilgen.Emit(OpCodes.Castclass, methodParamType);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// private static T ToT<T>(object o)
|
|
|
|
|
|
// {
|
|
|
|
|
|
// return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T));
|
|
|
|
|
|
// }
|
|
|
|
|
|
private static MethodInfo? _convertMethod;
|
|
|
|
|
|
private static MethodInfo? _getTypeFromHandle;
|
|
|
|
|
|
|
|
|
|
|
|
private static void Convert(this ILGenerator ilgen, Type type)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_getTypeFromHandle == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(RuntimeTypeHandle) }, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (_convertMethod == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_convertMethod = typeof(Convert).GetMethod("ChangeType", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(object), typeof(Type) }, null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ilgen.Emit(OpCodes.Ldtoken, type);
|
|
|
|
|
|
ilgen.CallMethod(_getTypeFromHandle);
|
|
|
|
|
|
ilgen.CallMethod(_convertMethod);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// emits adapter code before OpCodes.Ret
|
|
|
|
|
|
// outputType is the lambda output type
|
|
|
|
|
|
// methodReturnedType is the actual type returned by the actual method
|
|
|
|
|
|
// adding code to do methodReturnedType -> outputType
|
|
|
|
|
|
// valueType -> valueType : not supported ('cos, why?)
|
|
|
|
|
|
// valueType -> !valueType : box
|
|
|
|
|
|
// !valueType -> valueType : not supported ('cos, why?)
|
|
|
|
|
|
// !valueType -> !valueType : implicit cast (could throw)
|
|
|
|
|
|
private static void EmitOutputAdapter(ILGenerator ilgen, Type outputType, Type methodReturnedType)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (outputType == methodReturnedType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
2018-03-01 14:50:42 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// note: the only important thing to support here, is returning a specific type
|
|
|
|
|
|
// as an object, when emitting the method as a Func<..., object> - anything else
|
|
|
|
|
|
// is pointless really - so we box value types, and ensure that non value types
|
|
|
|
|
|
// can be assigned
|
|
|
|
|
|
if (methodReturnedType.IsValueType)
|
2018-03-01 14:50:42 +01:00
|
|
|
|
{
|
2022-06-07 15:28:38 +02:00
|
|
|
|
if (outputType.IsValueType)
|
|
|
|
|
|
{
|
|
|
|
|
|
// both returned and output are value types
|
|
|
|
|
|
// not supported, use proper output
|
|
|
|
|
|
// (otherwise, would require converting)
|
|
|
|
|
|
throw new NotSupportedException("ValueTypes conversion.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// returned is value type, but output is reference type
|
|
|
|
|
|
// box the returned value
|
|
|
|
|
|
ilgen.Emit(OpCodes.Box, methodReturnedType);
|
2018-03-01 14:50:42 +01:00
|
|
|
|
}
|
2022-06-07 15:28:38 +02:00
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// returned is reference type, but output is value type
|
|
|
|
|
|
// not supported, output should always be less constrained
|
|
|
|
|
|
// (otherwise, would require boxing and converting)
|
|
|
|
|
|
if (outputType.IsValueType)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException("ValueType boxing.");
|
|
|
|
|
|
}
|
2018-03-01 14:50:42 +01:00
|
|
|
|
|
2022-06-07 15:28:38 +02:00
|
|
|
|
// both output and returned are reference types
|
|
|
|
|
|
// as long as returned can be assigned to output, good
|
|
|
|
|
|
if (!outputType.IsAssignableFrom(methodReturnedType))
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException("Invalid cast.");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|
2022-06-07 15:28:38 +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));
|
|
|
|
|
|
|
|
|
|
|
|
private static void CallMethod(this ILGenerator ilgen, MethodInfo? method)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (method is not null)
|
|
|
|
|
|
{
|
|
|
|
|
|
var virt = !method.IsStatic && (method.IsVirtual || !method.IsFinal);
|
|
|
|
|
|
ilgen.Emit(virt ? OpCodes.Callvirt : OpCodes.Call, method);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void Return(this ILGenerator ilgen) => ilgen.Emit(OpCodes.Ret);
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
2017-09-25 12:58:54 +02:00
|
|
|
|
}
|