2017-09-25 12:58:54 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Reflection ;
using System.Reflection.Emit ;
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>
2018-02-02 19:43:03 +01:00
public static partial class ReflectionUtilities
2017-09-25 12:58:54 +02:00
{
2018-05-29 18:30:37 +02:00
#region Fields
/// <summary>
/// Emits a field getter.
/// </summary>
/// <typeparam name="TDeclaring">The declaring type.</typeparam>
/// <typeparam name="TValue">The field type.</typeparam>
/// <param name="fieldName">The name of the field.</param>
2019-10-07 22:10:21 +02:00
/// <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>
2018-05-29 18:30:37 +02:00
public static Func < TDeclaring , TValue > EmitFieldGetter < TDeclaring , TValue > ( string fieldName )
{
var field = GetField < TDeclaring , TValue > ( fieldName ) ;
return EmitFieldGetter < TDeclaring , TValue > ( field ) ;
}
/// <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>
2019-10-07 22:10:21 +02:00
/// <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>
2018-05-29 18:30:37 +02:00
public static Action < TDeclaring , TValue > EmitFieldSetter < TDeclaring , TValue > ( string fieldName )
{
var field = GetField < TDeclaring , TValue > ( fieldName ) ;
return EmitFieldSetter < TDeclaring , TValue > ( field ) ;
}
/// <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>
2019-10-07 22:10:21 +02:00
/// <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>
2018-05-29 18:30:37 +02:00
public static ( Func < TDeclaring , TValue > , Action < TDeclaring , TValue > ) EmitFieldGetterAndSetter < TDeclaring , TValue > ( string fieldName )
{
var field = GetField < TDeclaring , TValue > ( fieldName ) ;
return ( EmitFieldGetter < TDeclaring , TValue > ( field ) , EmitFieldSetter < TDeclaring , TValue > ( field ) ) ;
}
2019-10-07 22:10:21 +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>
2018-05-29 18:30:37 +02:00
private static FieldInfo GetField < TDeclaring , TValue > ( string fieldName )
{
2019-10-07 22:10:21 +02:00
if ( fieldName = = null ) throw new ArgumentNullException ( nameof ( fieldName ) ) ;
if ( string . IsNullOrWhiteSpace ( fieldName ) ) throw new ArgumentException ( "Value can't be empty or consist only of white-space characters." , nameof ( fieldName ) ) ;
2018-05-29 18:30:37 +02:00
// get the field
var field = typeof ( TDeclaring ) . GetField ( fieldName , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
if ( field = = null ) throw new InvalidOperationException ( $"Could not find field {typeof(TDeclaring)}.{fieldName}." ) ;
// validate field type
if ( field . FieldType ! = typeof ( TValue ) ) // strict
throw new ArgumentException ( $"Value type {typeof(TValue)} does not match field {typeof(TDeclaring)}.{fieldName} type {field.FieldType}." ) ;
return field ;
}
private static Func < TDeclaring , TValue > EmitFieldGetter < TDeclaring , TValue > ( FieldInfo field )
{
// emit
var ( dm , 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 > ) ( object ) dm . CreateDelegate ( typeof ( Func < TDeclaring , TValue > ) ) ;
}
private static Action < TDeclaring , TValue > EmitFieldSetter < TDeclaring , TValue > ( FieldInfo field )
{
// emit
var ( dm , 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 > ) ( object ) dm . CreateDelegate ( typeof ( Action < TDeclaring , TValue > ) ) ;
}
#endregion
2018-02-02 19:43:03 +01:00
#region Properties
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>
2019-10-07 22:10:21 +02:00
/// <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>
2017-09-29 15:50:30 +02:00
public static Func < TDeclaring , TValue > EmitPropertyGetter < TDeclaring , TValue > ( string propertyName , bool mustExist = true )
{
2019-10-07 22:10:21 +02:00
if ( propertyName = = null ) throw new ArgumentNullException ( nameof ( propertyName ) ) ;
if ( string . IsNullOrWhiteSpace ( propertyName ) ) 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
2018-02-02 19:43:03 +01:00
var property = typeof ( TDeclaring ) . GetProperty ( propertyName , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
if ( property ? . GetMethod ! = null )
return EmitMethod < Func < TDeclaring , TValue > > ( property . GetMethod ) ;
2017-09-27 21:16:09 +02:00
2018-02-02 19:43:03 +01:00
if ( ! mustExist )
return default ;
throw new InvalidOperationException ( $"Could not find getter for {typeof(TDeclaring)}.{propertyName}." ) ;
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="propertyName">The name of the property.</param>
/// <param name="mustExist">A value indicating whether the property and its setter must exist.</param>
2019-10-07 22:10:21 +02:00
/// <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>
2017-09-29 15:50:30 +02:00
public static Action < TDeclaring , TValue > EmitPropertySetter < TDeclaring , TValue > ( string propertyName , bool mustExist = true )
2017-09-25 12:58:54 +02:00
{
2019-10-07 22:10:21 +02:00
if ( propertyName = = null ) throw new ArgumentNullException ( nameof ( propertyName ) ) ;
if ( string . IsNullOrWhiteSpace ( propertyName ) ) 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
var property = typeof ( TDeclaring ) . GetProperty ( propertyName , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
2018-02-02 19:43:03 +01:00
if ( property ? . SetMethod ! = null )
return EmitMethod < Action < TDeclaring , TValue > > ( property . SetMethod ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
if ( ! mustExist )
return default ;
throw new InvalidOperationException ( $"Could not find setter for {typeof(TDeclaring)}.{propertyName}." ) ;
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>
2019-10-07 22:10:21 +02:00
/// <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>
2017-09-29 15:50:30 +02:00
public static ( Func < TDeclaring , TValue > , Action < TDeclaring , TValue > ) EmitPropertyGetterAndSetter < TDeclaring , TValue > ( string propertyName , bool mustExist = true )
2017-09-25 12:58:54 +02:00
{
2019-10-07 22:10:21 +02:00
if ( propertyName = = null ) throw new ArgumentNullException ( nameof ( propertyName ) ) ;
if ( string . IsNullOrWhiteSpace ( propertyName ) ) 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
var property = typeof ( TDeclaring ) . GetProperty ( propertyName , BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) ;
2018-02-02 19:43:03 +01:00
if ( property ? . GetMethod ! = null & & property . SetMethod ! = null )
return (
EmitMethod < Func < TDeclaring , TValue > > ( property . GetMethod ) ,
EmitMethod < Action < TDeclaring , TValue > > ( property . SetMethod ) ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
if ( ! mustExist )
return default ;
throw new InvalidOperationException ( $"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}." ) ;
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
{
2018-02-02 19:43:03 +01:00
if ( propertyInfo = = null ) throw new ArgumentNullException ( nameof ( propertyInfo ) ) ;
2017-09-29 15:50:30 +02:00
if ( propertyInfo . GetMethod = = null )
throw new ArgumentException ( "Property has no getter." , nameof ( propertyInfo ) ) ;
2018-02-02 19:43:03 +01:00
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
{
2018-02-02 19:43:03 +01:00
if ( propertyInfo = = null ) throw new ArgumentNullException ( nameof ( propertyInfo ) ) ;
2017-09-29 15:50:30 +02:00
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 )
{
2018-02-02 19:43:03 +01:00
if ( propertyInfo = = null ) throw new ArgumentNullException ( nameof ( propertyInfo ) ) ;
2017-09-29 15:50:30 +02:00
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 ) ) ;
}
2018-02-02 19:43:03 +01: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 ) ) ;
if ( propertyInfo . SetMethod = = null )
throw new ArgumentException ( "Property has no setter." , nameof ( propertyInfo ) ) ;
return EmitMethodUnsafe < Action < TDeclaring , TValue > > ( propertyInfo . SetMethod ) ;
}
#endregion
#region Constructors
2017-09-29 15:50:30 +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>
2018-11-18 11:05:14 +01:00
public static TLambda EmitConstructor < TLambda > ( bool mustExist = true , Type declaring = null )
2017-09-29 15:50:30 +02:00
{
2018-05-29 18:30:37 +02:00
var ( _ , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( true , true ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
// 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-25 12:58:54 +02:00
// get the constructor infos
2018-02-02 19:43:03 +01:00
var ctor = declaring . GetConstructor ( BindingFlags . NonPublic | BindingFlags . Public | BindingFlags . Instance , null , lambdaParameters , 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 ;
2018-02-02 19:43:03 +01:00
throw new InvalidOperationException ( $"Could not find constructor {declaring}.ctor({string.Join(" , ", (IEnumerable<Type>) lambdaParameters)})." ) ;
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
2018-10-16 10:58:17 +02:00
return EmitConstructorSafe < TLambda > ( lambdaParameters , lambdaReturned , 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>
2018-10-16 10:58:17 +02:00
public static TLambda EmitConstructor < TLambda > ( ConstructorInfo ctor )
2017-09-26 14:57:50 +02:00
{
2018-02-02 19:43:03 +01:00
if ( ctor = = null ) throw new ArgumentNullException ( nameof ( ctor ) ) ;
2017-09-29 15:50:30 +02:00
2018-05-29 18:30:37 +02:00
var ( _ , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( true , true ) ;
2017-09-29 15:50:30 +02:00
2018-10-16 10:58:17 +02:00
return EmitConstructorSafe < TLambda > ( lambdaParameters , lambdaReturned , ctor ) ;
2018-02-02 19:43:03 +01:00
}
2018-10-16 10:58:17 +02:00
private static TLambda EmitConstructorSafe < TLambda > ( Type [ ] lambdaParameters , Type returned , ConstructorInfo ctor )
2018-02-02 19:43:03 +01:00
{
// get type and args
var ctorDeclaring = ctor . DeclaringType ;
var ctorParameters = ctor . GetParameters ( ) . Select ( x = > x . ParameterType ) . ToArray ( ) ;
2017-09-29 15:50:30 +02:00
// validate arguments
2018-02-02 19:43:03 +01:00
if ( lambdaParameters . Length ! = ctorParameters . Length )
ThrowInvalidLambda < TLambda > ( "ctor" , ctorDeclaring , ctorParameters ) ;
for ( var i = 0 ; i < lambdaParameters . Length ; i + + )
2018-02-09 18:22:59 +01:00
if ( lambdaParameters [ i ] ! = ctorParameters [ i ] ) // note: relax the constraint with IsAssignableFrom?
2018-02-02 19:43:03 +01:00
ThrowInvalidLambda < TLambda > ( "ctor" , ctorDeclaring , ctorParameters ) ;
if ( ! returned . IsAssignableFrom ( ctorDeclaring ) )
ThrowInvalidLambda < TLambda > ( "ctor" , ctorDeclaring , ctorParameters ) ;
2017-09-26 14:57:50 +02:00
2017-09-29 15:50:30 +02:00
// emit
2018-10-16 10:58:17 +02:00
return EmitConstructor < TLambda > ( ctorDeclaring , ctorParameters , ctor ) ;
2017-09-29 15:51:33 +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>
/// <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>
2018-10-16 10:58:17 +02:00
public static TLambda EmitConstructorUnsafe < TLambda > ( ConstructorInfo ctor )
2017-09-29 15:51:33 +02:00
{
2018-02-02 19:43:03 +01:00
if ( ctor = = null ) throw new ArgumentNullException ( nameof ( ctor ) ) ;
2017-09-29 15:51:33 +02:00
2018-05-29 18:30:37 +02:00
var ( _ , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( true , true ) ;
2017-09-29 15:51:33 +02:00
2018-02-02 19:43:03 +01:00
// emit - unsafe - use lambda's args and assume they are correct
2018-10-16 10:58:17 +02:00
return EmitConstructor < TLambda > ( lambdaReturned , lambdaParameters , ctor ) ;
2018-02-02 19:43:03 +01:00
}
2017-09-29 15:51:33 +02:00
2018-10-16 10:58:17 +02:00
private static TLambda EmitConstructor < TLambda > ( Type declaring , Type [ ] lambdaParameters , ConstructorInfo ctor )
2018-02-02 19:43:03 +01:00
{
// gets the method argument types
var ctorParameters = GetParameters ( ctor ) ;
2017-09-29 15:51:33 +02:00
// emit
2018-05-29 18:30:37 +02:00
var ( dm , ilgen ) = CreateIlGenerator ( ctor . DeclaringType ? . Module , lambdaParameters , declaring ) ;
2018-02-02 19:43:03 +01:00
EmitLdargs ( ilgen , lambdaParameters , ctorParameters ) ;
2017-09-29 15:51:33 +02:00
ilgen . Emit ( OpCodes . Newobj , ctor ) ; // ok to just return, it's only objects
2018-03-01 14:50:42 +01:00
ilgen . Return ( ) ;
2018-02-02 19:43:03 +01:00
2017-09-29 15:51:33 +02:00
return ( TLambda ) ( object ) dm . CreateDelegate ( typeof ( TLambda ) ) ;
2017-09-27 21:16:09 +02:00
}
2018-02-02 19:43:03 +01:00
#endregion
#region Methods
2017-09-27 21:16:09 +02:00
2018-02-02 19:43:03 +01: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>
2019-10-07 22:10:21 +02:00
/// <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>
2018-02-02 19:43:03 +01:00
/// <remarks>
2019-10-07 22:10:21 +02:00
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
2018-02-02 19:43:03 +01:00
/// </remarks>
public static TLambda EmitMethod < TDeclaring , TLambda > ( string methodName , bool mustExist = true )
2017-09-27 21:16:09 +02:00
{
2018-02-02 19:43:03 +01:00
return EmitMethod < TLambda > ( typeof ( TDeclaring ) , methodName , mustExist ) ;
2017-09-27 21:16:09 +02:00
}
2018-02-02 19:43:03 +01: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>
2019-10-07 22:10:21 +02:00
/// <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>
2018-02-02 19:43:03 +01:00
/// <remarks>
2019-10-07 22:10:21 +02:00
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
2018-02-02 19:43:03 +01:00
/// </remarks>
public static TLambda EmitMethod < TLambda > ( Type declaring , string methodName , bool mustExist = true )
2017-09-27 21:16:09 +02:00
{
2019-10-07 22:10:21 +02:00
if ( methodName = = null ) throw new ArgumentNullException ( nameof ( methodName ) ) ;
if ( string . IsNullOrWhiteSpace ( methodName ) ) throw new ArgumentException ( "Value can't be empty or consist only of white-space characters." , nameof ( methodName ) ) ;
2017-09-27 21:16:09 +02:00
2018-05-29 18:30:37 +02:00
var ( lambdaDeclaring , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( true , out var isFunction ) ;
2017-09-27 21:16:09 +02:00
2018-02-02 19:43:03 +01:00
// get the method infos
var 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 ;
throw new InvalidOperationException ( $"Could not find static method {declaring}.{methodName}({string.Join(" , ", (IEnumerable<Type>) lambdaParameters)})." ) ;
}
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
// emit
2018-03-01 14:50:42 +01:00
return EmitMethod < TLambda > ( lambdaDeclaring , lambdaReturned , lambdaParameters , method ) ;
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
{
2018-02-02 19:43:03 +01: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
2018-02-02 19:43:03 +01:00
var methodDeclaring = method . DeclaringType ;
var methodReturned = method . ReturnType ;
var methodParameters = method . GetParameters ( ) . Select ( x = > x . ParameterType ) . ToArray ( ) ;
2017-09-29 15:50:30 +02:00
var isStatic = method . IsStatic ;
2018-05-29 18:30:37 +02:00
var ( lambdaDeclaring , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( isStatic , out var isFunction ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
// if not static, then the first lambda arg must be the method declaring type
if ( ! isStatic & & ( methodDeclaring = = null | | ! methodDeclaring . IsAssignableFrom ( lambdaDeclaring ) ) )
ThrowInvalidLambda < TLambda > ( method . Name , methodReturned , methodParameters ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
if ( methodParameters . Length ! = lambdaParameters . Length )
ThrowInvalidLambda < TLambda > ( method . Name , methodReturned , methodParameters ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
for ( var i = 0 ; i < methodParameters . Length ; i + + )
if ( ! methodParameters [ i ] . IsAssignableFrom ( lambdaParameters [ i ] ) )
ThrowInvalidLambda < TLambda > ( method . Name , methodReturned , methodParameters ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
// if it's a function then the last lambda arg must match the method returned type
if ( isFunction & & ! lambdaReturned . IsAssignableFrom ( methodReturned ) )
ThrowInvalidLambda < TLambda > ( method . Name , methodReturned , methodParameters ) ;
2017-09-29 15:50:30 +02:00
// emit
2018-03-01 14:50:42 +01:00
return EmitMethod < TLambda > ( lambdaDeclaring , lambdaReturned , lambdaParameters , method ) ;
2017-09-29 15:50:30 +02:00
}
/// <summary>
2018-02-02 19:43:03 +01:00
/// Emits a method.
2017-09-29 15:50:30 +02:00
/// </summary>
/// <typeparam name="TLambda">A lambda representing the method.</typeparam>
2018-02-02 19:43:03 +01:00
/// <param name="method">The method info.</param>
/// <returns>The method.</returns>
/// <exception cref="ArgumentNullException">Occurs when <paramref name="method"/> is null.</exception>
2017-09-29 15:50:30 +02:00
/// <exception cref="ArgumentException">Occurs when Occurs when <typeparamref name="TLambda"/> does not match the method signature.</exception>
2018-02-02 19:43:03 +01:00
public static TLambda EmitMethodUnsafe < TLambda > ( MethodInfo method )
2017-09-29 15:50:30 +02:00
{
2018-02-02 19:43:03 +01:00
if ( method = = null ) throw new ArgumentNullException ( nameof ( method ) ) ;
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
var isStatic = method . IsStatic ;
2018-05-29 18:30:37 +02:00
var ( lambdaDeclaring , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( isStatic , out _ ) ;
2017-09-27 21:16:09 +02:00
2018-02-02 19:43:03 +01:00
// emit - unsafe - use lambda's args and assume they are correct
2018-03-01 14:50:42 +01:00
return EmitMethod < TLambda > ( lambdaDeclaring , lambdaReturned , lambdaParameters , method ) ;
2017-09-29 15:50:30 +02:00
}
/// <summary>
2018-02-02 19:43:03 +01:00
/// Emits an instance method.
2017-09-29 15:50:30 +02:00
/// </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>
2019-10-07 22:10:21 +02:00
/// <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>
2017-09-29 15:50:30 +02:00
/// <remarks>
2019-10-07 22:10:21 +02:00
/// The method arguments are determined by <typeparamref name="TLambda" /> generic arguments.
2017-09-29 15:50:30 +02:00
/// </remarks>
2018-02-02 19:43:03 +01:00
public static TLambda EmitMethod < TLambda > ( string methodName , bool mustExist = true )
2017-09-29 15:50:30 +02:00
{
2019-10-07 22:10:21 +02:00
if ( methodName = = null ) throw new ArgumentNullException ( nameof ( methodName ) ) ;
if ( string . IsNullOrWhiteSpace ( methodName ) ) throw new ArgumentException ( "Value can't be empty or consist only of white-space characters." , nameof ( methodName ) ) ;
2017-09-29 15:50:30 +02:00
// validate lambda type
2018-05-29 18:30:37 +02:00
var ( lambdaDeclaring , lambdaParameters , lambdaReturned ) = AnalyzeLambda < TLambda > ( false , out var isFunction ) ;
2017-09-29 15:50:30 +02:00
// get the method infos
2018-03-01 14:50:42 +01:00
var method = lambdaDeclaring . GetMethod ( methodName , BindingFlags . NonPublic | BindingFlags . Public | BindingFlags . Instance , null , lambdaParameters , null ) ;
2018-02-02 19:43:03 +01:00
if ( method = = null | | isFunction & & method . ReturnType ! = lambdaReturned )
2017-09-27 21:16:09 +02:00
{
2017-09-29 15:50:30 +02:00
if ( ! mustExist ) return default ;
2018-03-01 14:50:42 +01:00
throw new InvalidOperationException ( $"Could not find method {lambdaDeclaring}.{methodName}({string.Join(" , ", (IEnumerable<Type>) lambdaParameters)})." ) ;
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
// emit
2018-03-01 14:50:42 +01:00
return EmitMethod < TLambda > ( lambdaDeclaring , lambdaReturned , lambdaParameters , method ) ;
2017-09-27 21:16:09 +02:00
}
2018-02-02 19:43:03 +01:00
// lambdaReturned = the lambda returned type (can be void)
// lambdaArgTypes = the lambda argument types
2018-03-01 14:50:42 +01:00
private static TLambda EmitMethod < TLambda > ( Type lambdaDeclaring , Type lambdaReturned , Type [ ] lambdaParameters , MethodInfo method )
2017-09-27 21:16:09 +02:00
{
2018-02-02 19:43:03 +01:00
// non-static methods need the declaring type as first arg
var parameters = lambdaParameters ;
2017-09-29 15:50:30 +02:00
if ( ! method . IsStatic )
2017-09-27 21:16:09 +02:00
{
2018-02-02 19:43:03 +01:00
parameters = new Type [ lambdaParameters . Length + 1 ] ;
2018-03-01 14:50:42 +01:00
parameters [ 0 ] = lambdaDeclaring ? ? method . DeclaringType ;
2018-02-02 19:43:03 +01:00
Array . Copy ( lambdaParameters , 0 , parameters , 1 , lambdaParameters . Length ) ;
2017-09-27 21:16:09 +02:00
}
2017-09-29 15:50:30 +02:00
2018-02-02 19:43:03 +01:00
// gets the method argument types
var methodArgTypes = GetParameters ( method , withDeclaring : ! method . IsStatic ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
// emit IL
2018-05-29 18:30:37 +02:00
var ( dm , ilgen ) = CreateIlGenerator ( method . DeclaringType ? . Module , parameters , lambdaReturned ) ;
2018-02-02 19:43:03 +01:00
EmitLdargs ( ilgen , parameters , methodArgTypes ) ;
2018-03-01 14:50:42 +01:00
ilgen . CallMethod ( method ) ;
2018-02-02 19:43:03 +01:00
EmitOutputAdapter ( ilgen , lambdaReturned , method . ReturnType ) ;
2018-03-01 14:50:42 +01:00
ilgen . Return ( ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
// create
return ( TLambda ) ( object ) dm . CreateDelegate ( typeof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
#endregion
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
#region Utilities
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01: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 )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
var typeLambda = typeof ( TLambda ) ;
2017-09-25 12:58:54 +02:00
2018-05-29 18:30:37 +02:00
var ( declaring , parameters , returned ) = AnalyzeLambda < TLambda > ( isStatic , out var maybeFunction ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
if ( isFunction )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
if ( ! maybeFunction )
throw new ArgumentException ( $"Lambda {typeLambda} is an Action, a Func was expected." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
}
else
{
2018-02-02 19:43:03 +01:00
if ( maybeFunction )
throw new ArgumentException ( $"Lambda {typeLambda} is a Func, an Action was expected." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
return ( declaring , parameters , returned ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01: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 , out bool isFunction )
2017-09-27 21:16:09 +02:00
{
2018-02-02 19:43:03 +01:00
isFunction = false ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var typeLambda = typeof ( TLambda ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var isAction = typeLambda . FullName = = "System.Action" ;
if ( isAction )
{
if ( ! isStatic )
throw new ArgumentException ( $"Lambda {typeLambda} is an Action and can be used for static methods exclusively." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
return ( null , Array . Empty < Type > ( ) , typeof ( void ) ) ;
}
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var genericDefinition = typeLambda . IsGenericType ? typeLambda . GetGenericTypeDefinition ( ) : null ;
var name = genericDefinition ? . FullName ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
if ( name = = null )
throw new ArgumentException ( $"Lambda {typeLambda} is not a Func nor an Action." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var isActionOf = name . StartsWith ( "System.Action`" ) ;
isFunction = name . StartsWith ( "System.Func`" ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
if ( ! isActionOf & & ! isFunction )
throw new ArgumentException ( $"Lambda {typeLambda} is not a Func nor an Action." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var 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
2018-02-02 19:43:03 +01:00
var i = 0 ;
var declaring = isStatic ? typeof ( void ) : genericArgs [ i + + ] ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01: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 ) ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var parameters = new Type [ parameterCount ] ;
for ( var j = 0 ; j < parameterCount ; j + + )
parameters [ j ] = genericArgs [ i + + ] ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
var returned = isFunction ? genericArgs [ i ] : typeof ( void ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
return ( declaring , parameters , returned ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
private static ( DynamicMethod , ILGenerator ) CreateIlGenerator ( Module module , Type [ ] arguments , Type returned )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
if ( module = = null ) throw new ArgumentNullException ( nameof ( module ) ) ;
var dm = new DynamicMethod ( string . Empty , returned , arguments , module , true ) ;
return ( dm , dm . GetILGenerator ( ) ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
private static Type [ ] GetParameters ( ConstructorInfo ctor )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
var parameters = ctor . GetParameters ( ) ;
var types = new Type [ parameters . Length ] ;
var i = 0 ;
foreach ( var parameter in parameters )
types [ i + + ] = parameter . ParameterType ;
return types ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
private static Type [ ] GetParameters ( MethodInfo method , bool withDeclaring )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
var parameters = method . GetParameters ( ) ;
var types = new Type [ parameters . Length + ( withDeclaring ? 1 : 0 ) ] ;
var i = 0 ;
if ( withDeclaring )
types [ i + + ] = method . DeclaringType ;
foreach ( var parameter in parameters )
types [ i + + ] = parameter . ParameterType ;
return types ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
// emits args
private static void EmitLdargs ( ILGenerator ilgen , Type [ ] lambdaArgTypes , Type [ ] methodArgTypes )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
var ldargOpCodes = new [ ] { OpCodes . Ldarg_0 , OpCodes . Ldarg_1 , OpCodes . Ldarg_2 , OpCodes . Ldarg_3 } ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
if ( lambdaArgTypes . Length ! = methodArgTypes . Length )
throw new Exception ( "Panic: inconsistent number of args." ) ;
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
for ( var i = 0 ; i < lambdaArgTypes . Length ; i + + )
{
if ( lambdaArgTypes . Length < 5 )
ilgen . Emit ( ldargOpCodes [ i ] ) ;
else
ilgen . Emit ( OpCodes . Ldarg , i ) ;
2018-03-01 14:50:42 +01:00
2018-06-07 14:29:31 +02:00
//var local = false;
EmitInputAdapter ( ilgen , lambdaArgTypes [ i ] , methodArgTypes [ i ] /*, ref local*/ ) ;
2018-03-01 14:50:42 +01: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)
2018-06-07 14:29:31 +02:00
private static void EmitInputAdapter ( ILGenerator ilgen , Type inputType , Type methodParamType /*, ref bool local*/ )
2018-03-01 14:50:42 +01:00
{
if ( inputType = = methodParamType ) return ;
if ( methodParamType . IsValueType )
{
if ( inputType . IsValueType )
{
// both input and parameter are value types
// not supported, use proper input
// (otherwise, would require converting)
throw new NotSupportedException ( "ValueTypes conversion." ) ;
}
// 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
var unbox = ilgen . DefineLabel ( ) ;
2018-06-07 14:29:31 +02:00
//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
2018-03-01 14:50:42 +01:00
2018-06-07 14:29:31 +02:00
ilgen . Emit ( OpCodes . Dup ) ; // duplicate top of stack
2018-03-01 14:50:42 +01:00
2018-06-07 14:29:31 +02:00
// stack: value ; value
2018-03-01 14:50:42 +01:00
ilgen . Emit ( OpCodes . Isinst , methodParamType ) ; // test, pops value, and pushes either a null ref, or an instance of the type
2018-06-07 14:29:31 +02:00
// stack: inst|null ; value
2018-03-01 14:50:42 +01:00
ilgen . Emit ( OpCodes . Ldnull ) ; // push null
2018-06-07 14:29:31 +02:00
// stack: null ; inst|null ; value
2018-03-01 14:50:42 +01:00
ilgen . Emit ( OpCodes . Cgt_Un ) ; // compare what isInst returned to null - pops 2 values, and pushes 1 if greater else 0
2018-06-07 14:29:31 +02:00
// stack: 0|1 ; value
ilgen . Emit ( OpCodes . Brtrue_S , unbox ) ; // pops value, branches to unbox if true, ie nonzero
// stack: value
2018-03-01 14:50:42 +01:00
ilgen . Convert ( methodParamType ) ; // convert
2018-06-07 14:29:31 +02:00
// stack: value|converted
2018-03-01 14:50:42 +01:00
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 ) ;
2018-02-02 19:43:03 +01:00
}
2017-09-25 12:58:54 +02:00
}
2018-03-01 14:50:42 +01:00
//private static T ToT<T>(object o)
//{
// return o is T t ? t : (T) System.Convert.ChangeType(o, typeof(T));
//}
2018-03-07 09:34:23 +01:00
private static MethodInfo _convertMethod ;
2018-06-07 14:29:31 +02:00
private static MethodInfo _getTypeFromHandle ;
2018-03-07 09:34:23 +01:00
2018-03-01 14:50:42 +01:00
private static void Convert ( this ILGenerator ilgen , Type type )
{
2018-06-07 14:29:31 +02:00
if ( _getTypeFromHandle = = null )
_getTypeFromHandle = typeof ( Type ) . GetMethod ( "GetTypeFromHandle" , BindingFlags . Public | BindingFlags . Static , null , new [ ] { typeof ( RuntimeTypeHandle ) } , null ) ;
2018-03-07 09:34:23 +01:00
if ( _convertMethod = = null )
_convertMethod = typeof ( Convert ) . GetMethod ( "ChangeType" , BindingFlags . Public | BindingFlags . Static , null , new [ ] { typeof ( object ) , typeof ( Type ) } , null ) ;
2018-06-07 14:29:31 +02:00
ilgen . Emit ( OpCodes . Ldtoken , type ) ;
ilgen . CallMethod ( _getTypeFromHandle ) ;
2018-03-07 09:34:23 +01:00
ilgen . CallMethod ( _convertMethod ) ;
2018-03-01 14:50:42 +01:00
}
// 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)
2018-02-02 19:43:03 +01:00
private static void EmitOutputAdapter ( ILGenerator ilgen , Type outputType , Type methodReturnedType )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
if ( outputType = = methodReturnedType ) return ;
2017-09-25 12:58:54 +02:00
2018-03-01 14:50:42 +01: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
2018-02-02 19:43:03 +01:00
if ( methodReturnedType . IsValueType )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01: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." ) ;
}
2017-09-25 12:58:54 +02:00
2018-02-02 19:43:03 +01:00
// returned is value type, but output is reference type
// box the returned value
ilgen . Emit ( OpCodes . Box , methodReturnedType ) ;
}
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." ) ;
// 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
}
}
2018-02-02 19:43:03 +01:00
private static void ThrowInvalidLambda < TLambda > ( string methodName , Type returned , Type [ ] args )
2017-09-25 12:58:54 +02:00
{
2018-02-02 19:43:03 +01:00
throw new ArgumentException ( $"Lambda {typeof(TLambda)} does not match {methodName}({string.Join(" , ", (IEnumerable<Type>) args)}):{returned}." , nameof ( TLambda ) ) ;
2017-09-25 12:58:54 +02:00
}
2018-02-02 19:43:03 +01:00
2018-03-01 14:50:42 +01:00
private static void CallMethod ( this ILGenerator ilgen , MethodInfo method )
{
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 ) ;
}
2018-02-02 19:43:03 +01:00
#endregion
2017-09-25 12:58:54 +02:00
}
}