using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; namespace Umbraco.Core { /// /// Provides utilities to simplify reflection. /// /// /// 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 /// /// Supports emitting constructors, instance and static methods, instance property getters and /// setters. Does not support static properties yet. /// public static partial class ReflectionUtilities { #region Fields /// /// Emits a field getter. /// /// The declaring type. /// The field type. /// The name of the field. /// /// A field getter function. /// /// fieldName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match field . type. /// Could not find field .. public static Func EmitFieldGetter(string fieldName) { var field = GetField(fieldName); return EmitFieldGetter(field); } /// /// Emits a field setter. /// /// The declaring type. /// The field type. /// The name of the field. /// /// A field setter action. /// /// fieldName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match field . type. /// Could not find field .. public static Action EmitFieldSetter(string fieldName) { var field = GetField(fieldName); return EmitFieldSetter(field); } /// /// Emits a field getter and setter. /// /// The declaring type. /// The field type. /// The name of the field. /// /// A field getter and setter functions. /// /// fieldName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match field . type. /// Could not find field .. public static (Func, Action) EmitFieldGetterAndSetter(string fieldName) { var field = GetField(fieldName); return (EmitFieldGetter(field), EmitFieldSetter(field)); } /// /// Gets the field. /// /// The type of the declaring. /// The type of the value. /// Name of the field. /// /// fieldName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match field . type. /// Could not find field .. private static FieldInfo GetField(string fieldName) { 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)); // 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 EmitFieldGetter(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) (object) dm.CreateDelegate(typeof(Func)); } private static Action EmitFieldSetter(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) (object) dm.CreateDelegate(typeof(Action)); } #endregion #region Properties /// /// Emits a property getter. /// /// The declaring type. /// The property type. /// The name of the property. /// A value indicating whether the property and its getter must exist. /// /// A property getter function. If is false, returns null when the property or its getter does not exist. /// /// propertyName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match property . type. /// Could not find property getter for .. public static Func EmitPropertyGetter(string propertyName, bool mustExist = true) { 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)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.GetMethod != null) return EmitMethod>(property.GetMethod); if (!mustExist) return default; throw new InvalidOperationException($"Could not find getter for {typeof(TDeclaring)}.{propertyName}."); } /// /// Emits a property setter. /// /// The declaring type. /// The property type. /// The name of the property. /// A value indicating whether the property and its setter must exist. /// /// A property setter function. If is false, returns null when the property or its setter does not exist. /// /// propertyName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match property . type. /// Could not find property setter for .. public static Action EmitPropertySetter(string propertyName, bool mustExist = true) { 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)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.SetMethod != null) return EmitMethod>(property.SetMethod); if (!mustExist) return default; throw new InvalidOperationException($"Could not find setter for {typeof(TDeclaring)}.{propertyName}."); } /// /// Emits a property getter and setter. /// /// The declaring type. /// The property type. /// The name of the property. /// A value indicating whether the property and its getter and setter must exist. /// /// A property getter and setter functions. If is false, returns null when the property or its getter or setter does not exist. /// /// propertyName /// Value can't be empty or consist only of white-space characters. - /// or /// Value type does not match property . type. /// Could not find property getter and setter for .. public static (Func, Action) EmitPropertyGetterAndSetter(string propertyName, bool mustExist = true) { 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)); var property = typeof(TDeclaring).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property?.GetMethod != null && property.SetMethod != null) return ( EmitMethod>(property.GetMethod), EmitMethod>(property.SetMethod)); if (!mustExist) return default; throw new InvalidOperationException($"Could not find getter and/or setter for {typeof(TDeclaring)}.{propertyName}."); } /// /// Emits a property getter. /// /// The declaring type. /// The property type. /// The property info. /// A property getter function. /// Occurs when is null. /// Occurs when the property has no getter. /// Occurs when does not match the type of the property. public static Func EmitPropertyGetter(PropertyInfo propertyInfo) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); if (propertyInfo.GetMethod == null) throw new ArgumentException("Property has no getter.", nameof(propertyInfo)); return EmitMethod>(propertyInfo.GetMethod); } /// /// Emits a property setter. /// /// The declaring type. /// The property type. /// The property info. /// A property setter function. /// Occurs when is null. /// Occurs when the property has no setter. /// Occurs when does not match the type of the property. public static Action EmitPropertySetter(PropertyInfo propertyInfo) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); if (propertyInfo.SetMethod == null) throw new ArgumentException("Property has no setter.", nameof(propertyInfo)); return EmitMethod>(propertyInfo.SetMethod); } /// /// Emits a property getter and setter. /// /// The declaring type. /// The property type. /// The property info. /// A property getter and setter functions. /// Occurs when is null. /// Occurs when the property has no getter or no setter. /// Occurs when does not match the type of the property. public static (Func, Action) EmitPropertyGetterAndSetter(PropertyInfo propertyInfo) { if (propertyInfo == null) throw new ArgumentNullException(nameof(propertyInfo)); if (propertyInfo.GetMethod == null || propertyInfo.SetMethod == null) throw new ArgumentException("Property has no getter and/or no setter.", nameof(propertyInfo)); return ( EmitMethod>(propertyInfo.GetMethod), EmitMethod>(propertyInfo.SetMethod)); } /// /// Emits a property setter. /// /// The declaring type. /// The property type. /// The property info. /// A property setter function. /// Occurs when is null. /// Occurs when the property has no setter. /// Occurs when does not match the type of the property. public static Action EmitPropertySetterUnsafe(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>(propertyInfo.SetMethod); } #endregion #region Constructors /// /// Emits a constructor. /// /// A lambda representing the constructor. /// A value indicating whether the constructor must exist. /// The optional type of the class to construct. /// A constructor function. If is false, returns null when the constructor does not exist. /// /// When is not specified, it is the type returned by . /// The constructor arguments are determined by generic arguments. /// The type returned by does not need to be exactly , /// when e.g. that type is not known at compile time, but it has to be a parent type (eg an interface, or object). /// /// Occurs when the constructor does not exist and is true. /// Occurs when is not a Func or when /// is specified and does not match the function's returned type. public static TLambda EmitConstructor(bool mustExist = true, Type declaring = null) { var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(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)); // get the constructor infos var ctor = declaring.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); if (ctor == null) { if (!mustExist) return default; throw new InvalidOperationException($"Could not find constructor {declaring}.ctor({string.Join(", ", (IEnumerable) lambdaParameters)})."); } // emit return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } /// /// Emits a constructor. /// /// A lambda representing the constructor. /// The constructor info. /// A constructor function. /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. public static TLambda EmitConstructor(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); return EmitConstructorSafe(lambdaParameters, lambdaReturned, ctor); } private static TLambda EmitConstructorSafe(Type[] lambdaParameters, Type returned, ConstructorInfo ctor) { // get type and args var ctorDeclaring = ctor.DeclaringType; var ctorParameters = ctor.GetParameters().Select(x => x.ParameterType).ToArray(); // validate arguments if (lambdaParameters.Length != ctorParameters.Length) ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); for (var i = 0; i < lambdaParameters.Length; i++) if (lambdaParameters[i] != ctorParameters[i]) // note: relax the constraint with IsAssignableFrom? ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); if (!returned.IsAssignableFrom(ctorDeclaring)) ThrowInvalidLambda("ctor", ctorDeclaring, ctorParameters); // emit return EmitConstructor(ctorDeclaring, ctorParameters, ctor); } /// /// Emits a constructor. /// /// A lambda representing the constructor. /// The constructor info. /// A constructor function. /// /// 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. /// /// Occurs when is not a Func or when its generic /// arguments do not match those of . /// Occurs when is null. public static TLambda EmitConstructorUnsafe(ConstructorInfo ctor) { if (ctor == null) throw new ArgumentNullException(nameof(ctor)); var (_, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, true); // emit - unsafe - use lambda's args and assume they are correct return EmitConstructor(lambdaReturned, lambdaParameters, ctor); } private static TLambda EmitConstructor(Type declaring, Type[] lambdaParameters, ConstructorInfo ctor) { // gets the method argument types var ctorParameters = GetParameters(ctor); // emit var (dm, 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(); return (TLambda) (object) dm.CreateDelegate(typeof(TLambda)); } #endregion #region Methods /// /// Emits a static method. /// /// The declaring type. /// A lambda representing the method. /// The name of the method. /// A value indicating whether the constructor must exist. /// /// The method. If is false, returns null when the method does not exist. /// /// methodName /// Value can't be empty or consist only of white-space characters. - /// or /// Occurs when does not match the method signature.. /// Occurs when no proper method with name could be found. /// /// The method arguments are determined by generic arguments. /// public static TLambda EmitMethod(string methodName, bool mustExist = true) { return EmitMethod(typeof(TDeclaring), methodName, mustExist); } /// /// Emits a static method. /// /// A lambda representing the method. /// The declaring type. /// The name of the method. /// A value indicating whether the constructor must exist. /// /// The method. If is false, returns null when the method does not exist. /// /// methodName /// Value can't be empty or consist only of white-space characters. - /// or /// Occurs when does not match the method signature.. /// Occurs when no proper method with name could be found. /// /// The method arguments are determined by generic arguments. /// public static TLambda EmitMethod(Type declaring, string methodName, bool mustExist = true) { 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)); var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(true, out var isFunction); // 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) lambdaParameters)})."); } // emit return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); } /// /// Emits a method. /// /// A lambda representing the method. /// The method info. /// The method. /// Occurs when is null. /// Occurs when Occurs when does not match the method signature. public static TLambda EmitMethod(MethodInfo method) { if (method == null) throw new ArgumentNullException(nameof(method)); // get type and args var methodDeclaring = method.DeclaringType; var methodReturned = method.ReturnType; var methodParameters = method.GetParameters().Select(x => x.ParameterType).ToArray(); var isStatic = method.IsStatic; var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(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))) ThrowInvalidLambda(method.Name, methodReturned, methodParameters); if (methodParameters.Length != lambdaParameters.Length) ThrowInvalidLambda(method.Name, methodReturned, methodParameters); for (var i = 0; i < methodParameters.Length; i++) if (!methodParameters[i].IsAssignableFrom(lambdaParameters[i])) ThrowInvalidLambda(method.Name, methodReturned, methodParameters); // if it's a function then the last lambda arg must match the method returned type if (isFunction && !lambdaReturned.IsAssignableFrom(methodReturned)) ThrowInvalidLambda(method.Name, methodReturned, methodParameters); // emit return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); } /// /// Emits a method. /// /// A lambda representing the method. /// The method info. /// The method. /// Occurs when is null. /// Occurs when Occurs when does not match the method signature. public static TLambda EmitMethodUnsafe(MethodInfo method) { if (method == null) throw new ArgumentNullException(nameof(method)); var isStatic = method.IsStatic; var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(isStatic, out _); // emit - unsafe - use lambda's args and assume they are correct return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); } /// /// Emits an instance method. /// /// A lambda representing the method. /// The name of the method. /// A value indicating whether the constructor must exist. /// /// The method. If is false, returns null when the method does not exist. /// /// methodName /// Value can't be empty or consist only of white-space characters. - /// or /// Occurs when does not match the method signature.. /// Occurs when no proper method with name could be found. /// /// The method arguments are determined by generic arguments. /// public static TLambda EmitMethod(string methodName, bool mustExist = true) { 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)); // validate lambda type var (lambdaDeclaring, lambdaParameters, lambdaReturned) = AnalyzeLambda(false, out var isFunction); // get the method infos var method = lambdaDeclaring.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, lambdaParameters, null); if (method == null || isFunction && method.ReturnType != lambdaReturned) { if (!mustExist) return default; throw new InvalidOperationException($"Could not find method {lambdaDeclaring}.{methodName}({string.Join(", ", (IEnumerable) lambdaParameters)})."); } // emit return EmitMethod(lambdaDeclaring, lambdaReturned, lambdaParameters, method); } // lambdaReturned = the lambda returned type (can be void) // lambdaArgTypes = the lambda argument types private static TLambda EmitMethod(Type lambdaDeclaring, Type lambdaReturned, Type[] lambdaParameters, MethodInfo method) { // non-static methods need the declaring type as first arg var parameters = lambdaParameters; if (!method.IsStatic) { parameters = new Type[lambdaParameters.Length + 1]; parameters[0] = lambdaDeclaring ?? method.DeclaringType; Array.Copy(lambdaParameters, 0, parameters, 1, lambdaParameters.Length); } // gets the method argument types var methodArgTypes = GetParameters(method, withDeclaring: !method.IsStatic); // emit IL var (dm, ilgen) = CreateIlGenerator(method.DeclaringType?.Module, parameters, lambdaReturned); EmitLdargs(ilgen, parameters, methodArgTypes); ilgen.CallMethod(method); EmitOutputAdapter(ilgen, lambdaReturned, method.ReturnType); ilgen.Return(); // create return (TLambda) (object) dm.CreateDelegate(typeof(TLambda)); } #endregion #region Utilities // 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(bool isStatic, bool isFunction) { var typeLambda = typeof(TLambda); var (declaring, parameters, returned) = AnalyzeLambda(isStatic, out var maybeFunction); if (isFunction) { if (!maybeFunction) throw new ArgumentException($"Lambda {typeLambda} is an Action, a Func was expected.", nameof(TLambda)); } 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(bool isStatic, out bool isFunction) { isFunction = false; var typeLambda = typeof(TLambda); 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)); return (null, Array.Empty(), typeof(void)); } var 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)); var genericArgs = typeLambda.GetGenericArguments(); if (genericArgs.Length == 0) throw new Exception("Panic: Func<> or Action<> has zero generic arguments."); var i = 0; var declaring = isStatic ? typeof(void) : genericArgs[i++]; 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)); var parameters = new Type[parameterCount]; for (var j = 0; j < parameterCount; j++) parameters[j] = genericArgs[i++]; var returned = isFunction ? genericArgs[i] : typeof(void); return (declaring, parameters, returned); } private static (DynamicMethod, ILGenerator) CreateIlGenerator(Module module, Type[] arguments, Type returned) { if (module == null) throw new ArgumentNullException(nameof(module)); var dm = new DynamicMethod(string.Empty, returned, arguments, module, true); return (dm, dm.GetILGenerator()); } private static Type[] GetParameters(ConstructorInfo ctor) { var parameters = ctor.GetParameters(); var types = new Type[parameters.Length]; var i = 0; foreach (var parameter in parameters) types[i++] = parameter.ParameterType; return types; } private static Type[] GetParameters(MethodInfo method, bool withDeclaring) { 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; } // emits args private static void EmitLdargs(ILGenerator ilgen, Type[] lambdaArgTypes, Type[] methodArgTypes) { var 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) ilgen.Emit(ldargOpCodes[i]); else ilgen.Emit(OpCodes.Ldarg, i); //var local = false; EmitInputAdapter(ilgen, lambdaArgTypes[i], methodArgTypes[i]/*, ref local*/); } } // 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) 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(); //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(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; // 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) { 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); } 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."); } } private static void ThrowInvalidLambda(string methodName, Type returned, Type[] args) { throw new ArgumentException($"Lambda {typeof(TLambda)} does not match {methodName}({string.Join(", ", (IEnumerable) args)}):{returned}.", nameof(TLambda)); } 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); } #endregion } }