c# - 如何使用表达式生成此属性实现而不是发出 IL?

标签 c# linq reflection.emit linq-expressions dynamic-assemblies

我试图在运行时生成类,这些类实现属性 getter ,其主体调用生成类的基类上的方法。这是一个简单接口(interface)的示例,以及我试图复制的手写实现和基类。

public interface IGenerated : IBase { decimal Property1 { get; } }

public class GeneratedByHand : ImplBase<IGenerated> { 
    public decimal Property1 { get { return Get(s => s.Property1); } }
}

public interface IBase { string _KeyPrefix { get; set; } }

public abstract class ImplBase<T> : IBase
    where T : IBase
{
    public virtual string _KeyPrefix { get; set; }

    protected virtual TResult Get<TResult>(Expression<Func<T, TResult>> property) { 
        return GetValue<TResult>(GetPropertyName(property)); 
    }

    private string GetPropertyName<TResult>(Expression<Func<T, TResult>> property) { 
        return ""; // reflection stuff to get name from property expression goes here
    }
    private TResult GetValue<TResult>(string keyPart) { 
        return default(TResult); // does something like: return ReallyGetValue<TResult>(_KeyPrefix + keyPart);
    }
}

我有一个生成器的工作实现,它发出 IL 来构建该方法,但如果我可以使用表达式来完成它,我认为这将更容易扩展和维护。我需要在属性定义上查找自定义属性,并使用它来调用属性实现中基类上的不同方法重载。

这是我为属性 get 实现构建表达式的地方。我真正不明白的是构建 Call 表达式,如果我正确设置它以执行与 this.Get()base.Get() 等效的操作>。现在,它在 CompileToMethod

处抛出 System.ArgumentException : Invalid argument value Parameter name: method
public void CreateExpressionForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
{
    var settingsParam = Expression.Parameter(interfaceType, "s");
    var propGetterExpr = Expression.Property(settingsParam, property);

    var propGetterExprFuncType = typeof(Func<,>).MakeGenericType(interfaceType, property.PropertyType);
    var propGetterLambda = Expression.Lambda(propGetterExprFuncType, propGetterExpr, settingsParam);

    var baseGetMethodInfo = 
        baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
        .Where(m => {
            var parameters = m.GetParameters();
            return m.Name == "Get" &&
                    parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
        })
        .First().MakeGenericMethod(property.PropertyType);

    var getExprType = typeof(Expression<>).MakeGenericType(propGetterExprFuncType);
    var getExprParam = Expression.Parameter(getExprType, "expression");

    var getCallExpr = Expression.Call(Expression.Parameter(baseType, "inst"), baseGetMethodInfo, propGetterLambda);

    var getFuncType = typeof(Func<,>).MakeGenericType(getExprType, property.PropertyType);
    var propLambda = Expression.Lambda(getFuncType, getCallExpr, getExprParam);
    propLambda.CompileToMethod(getBuilder);
}

我不太确定从这里该去哪里。我尝试了 Expression.Call 参数的其他一些变体,但其他所有方法都因参数类型错误而 Call 抛出异常。

这是我正在使用的所有示例代码的可构建版本,包括工作 IL 发射器:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using NUnit.Framework;



namespace ExpressionGenerationTest
{
    [TestFixture]
    public class UnitTests
    {
        [Test]
        public void CreateAndSaveAssembly()
        {
            var implGenerator = new ImplBuilder();
            var generatedType = implGenerator.CreateImplementation(typeof(IGenerated));
            implGenerator.SaveAssembly();
        }
    }


    public interface IBase { string _KeyPrefix { get; set; } }


    public abstract class ImplBase<T> : IBase
        where T : IBase
    {
        public virtual string _KeyPrefix { get; set; }

        protected virtual TResult Get<TResult>(Expression<Func<T, TResult>> property) { return GetValue<TResult>(GetPropertyName(property)); }

        private string GetPropertyName<TResult>(Expression<Func<T, TResult>> property) { return ""; } // reflection stuff to get name from property expression goes here
        private TResult GetValue<TResult>(string keyPart) { return default(TResult); } // does something like: return ReallyGetValue(_KeyPrefix + keyPart);
    }


    public interface IGenerated : IBase { decimal Property1 { get; } }

    public class GeneratedByHand : ImplBase<IGenerated> { public decimal Property1 { get { return Get(s => s.Property1); } } }



    public class ImplBuilder
    {
        private const string _assemblyNameBase = "ExpressionGenerationTest.Impl";

        public static ImplBuilder Default { get { return _default.Value; } }
        private static readonly Lazy<ImplBuilder> _default = new Lazy<ImplBuilder>(() => new ImplBuilder());

        private ConcurrentDictionary<Type, Type> _types = new ConcurrentDictionary<Type, Type>();
        private AssemblyBuilder _assemblyBuilder = null;
        private volatile ModuleBuilder _moduleBuilder = null;
        private object _lock = new object();

        private void EnsureInitialized()
        {
            if (_moduleBuilder == null) {
                lock (_lock) {
                    if (_moduleBuilder == null) {
                        _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(_assemblyNameBase), AssemblyBuilderAccess.RunAndSave);
                        _moduleBuilder = _assemblyBuilder.DefineDynamicModule(_assemblyBuilder.GetName().Name, _assemblyNameBase + ".dll");
                    }
                }
            }
        }

        public void SaveAssembly() { _assemblyBuilder.Save(_assemblyNameBase + ".dll"); }
        public TSettings CreateInstance<TSettings>() { return (TSettings)Activator.CreateInstance(_types.GetOrAdd(typeof(TSettings), CreateImplementation)); }
        public void CreateImplementations(IEnumerable<Type> types) { foreach (var t in types) _types.GetOrAdd(t, InternalCreateImplementation); }
        public Type CreateImplementation(Type interfaceType) { return _types.GetOrAdd(interfaceType, InternalCreateImplementation); }
        private Type InternalCreateImplementation(Type interfaceType)
        {
            EnsureInitialized();

            var baseType = typeof (ImplBase<>).MakeGenericType(interfaceType);
            var typeBuilder = _moduleBuilder.DefineType(
                (interfaceType.IsInterface && interfaceType.Name.StartsWith("I") 
                    ? interfaceType.Name.Substring(1) 
                    : interfaceType.Name) + "Impl",
                TypeAttributes.Public | TypeAttributes.Class |
                TypeAttributes.AutoClass | TypeAttributes.AnsiClass |
                TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
                baseType,
                new [] {interfaceType});

            foreach (var p in GetPublicProperties(interfaceType).Where(pi => pi.DeclaringType != typeof(IBase))) {
                var iGet = p.GetGetMethod();
                if (iGet != null) {
                    var getBuilder =
                        typeBuilder.DefineMethod(iGet.Name,
                            MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                            p.PropertyType, Type.EmptyTypes);

                    //EmitILForGetMethod(getBuilder, interfaceType, baseType, p, iGet);
                    CreateExpressionForGetMethod(getBuilder, interfaceType, baseType, p, iGet);
                    typeBuilder.DefineMethodOverride(getBuilder, iGet);
                }
            }

            var implementationType = typeBuilder.CreateType();
            return implementationType;
        }


        public void CreateExpressionForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
        {
            var settingsParam = Expression.Parameter(interfaceType, "s");
            var propGetterExpr = Expression.Property(settingsParam, property);

            var propGetterExprFuncType = typeof(Func<,>).MakeGenericType(interfaceType, property.PropertyType);
            var propGetterLambda = Expression.Lambda(propGetterExprFuncType, propGetterExpr, settingsParam);

            var baseGetMethodInfo = 
                baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                .Where(m => {
                    var parameters = m.GetParameters();
                    return m.Name == "Get" &&
                            parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
                })
                .First().MakeGenericMethod(property.PropertyType);

            var getExprType = typeof(Expression<>).MakeGenericType(propGetterExprFuncType);
            var getExprParam = Expression.Parameter(getExprType, "expression");

            var getCallExpr = Expression.Call(Expression.Parameter(baseType, "inst"), baseGetMethodInfo, propGetterLambda);

            var getFuncType = typeof(Func<,>).MakeGenericType(getExprType, property.PropertyType);
            var propLambda = Expression.Lambda(getFuncType, getCallExpr, getExprParam);
            propLambda.CompileToMethod(getBuilder);
        }


        public void EmitILForGetMethod(MethodBuilder getBuilder, Type interfaceType, Type baseType, PropertyInfo property, MethodInfo getMethod)
        {
            var getGen = getBuilder.GetILGenerator();
            var retVal = getGen.DeclareLocal(property.PropertyType);
            var expParam = getGen.DeclareLocal(typeof(ParameterExpression));
            var expParams = getGen.DeclareLocal(typeof(ParameterExpression[]));
            getGen.Emit(OpCodes.Ldarg_0);
            getGen.Emit(OpCodes.Ldtoken, interfaceType);
            getGen.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"));
            getGen.Emit(OpCodes.Ldstr, "s");
            getGen.Emit(OpCodes.Call, typeof(Expression).GetMethod("Parameter", new [] {typeof(Type), typeof(string)}));
            getGen.Emit(OpCodes.Stloc, expParam);
            getGen.Emit(OpCodes.Ldloc, expParam);
            getGen.Emit(OpCodes.Ldtoken, getMethod);
            getGen.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new [] {typeof(RuntimeMethodHandle)}, null));
            getGen.Emit(OpCodes.Castclass, typeof(MethodInfo));
            getGen.Emit(OpCodes.Call, typeof(Expression).GetMethod("Property", new[] {typeof(Expression), typeof(MethodInfo)}));
            getGen.Emit(OpCodes.Ldc_I4_1);
            getGen.Emit(OpCodes.Newarr, typeof(ParameterExpression));
            getGen.Emit(OpCodes.Stloc, expParams);
            getGen.Emit(OpCodes.Ldloc, expParams);
            getGen.Emit(OpCodes.Ldc_I4_0);
            getGen.Emit(OpCodes.Ldloc, expParam);
            getGen.Emit(OpCodes.Stelem_Ref);
            getGen.Emit(OpCodes.Ldloc, expParams);

            var lambdaMethodInfo = 
                typeof(Expression).GetMethods(BindingFlags.Public | BindingFlags.Static)
                .Where(x => { 
                    var parameters = x.GetParameters();
                    return x.Name == "Lambda" && 
                            x.IsGenericMethodDefinition &&
                            parameters.Count() == 2 &&
                            parameters[0].ParameterType == typeof(Expression) &&
                            parameters[1].ParameterType == typeof(ParameterExpression[]);
                }).FirstOrDefault();

            var lambdaFuncType = typeof(Func<,>);
            lambdaFuncType = lambdaFuncType.MakeGenericType(interfaceType, property.PropertyType);
            lambdaMethodInfo = lambdaMethodInfo.MakeGenericMethod(lambdaFuncType);

            getGen.Emit(OpCodes.Call, lambdaMethodInfo);


            var baseGetMethodInfo = 
                baseType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                .Where(m => {
                    var parameters = m.GetParameters();
                    return m.Name == "Get" &&
                            parameters != null && parameters.Count() == 1 && parameters[0].ParameterType != typeof(string);
                }).FirstOrDefault();

            baseGetMethodInfo = baseGetMethodInfo.MakeGenericMethod(property.PropertyType);

            getGen.Emit(OpCodes.Callvirt, baseGetMethodInfo);

            getGen.Emit(OpCodes.Stloc_0);
            var endOfMethod = getGen.DefineLabel();
            getGen.Emit(OpCodes.Br_S, endOfMethod);
            getGen.MarkLabel(endOfMethod);
            getGen.Emit(OpCodes.Ldloc_0);
            getGen.Emit(OpCodes.Ret);
        }


        // from http://stackoverflow.com/a/2444090/224087
        public static PropertyInfo[] GetPublicProperties(Type type)
        {
            if (!type.IsInterface)
                return type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);

            var propertyInfos = new List<PropertyInfo>();
            var considered = new List<Type>();
            var queue = new Queue<Type>();

            considered.Add(type);
            queue.Enqueue(type);
            while (queue.Count > 0) {
                var subType = queue.Dequeue();

                foreach (var subInterface in subType.GetInterfaces()) {
                    if (considered.Contains(subInterface)) 
                        continue;

                    considered.Add(subInterface);
                    queue.Enqueue(subInterface);
                }

                var typeProperties = subType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);
                var newPropertyInfos = typeProperties.Where(x => !propertyInfos.Contains(x));
                propertyInfos.InsertRange(0, newPropertyInfos);
            }

            return propertyInfos.ToArray();
        }
    }
}

最佳答案

What I don't really understand is building the Call expression, if I'm setting it up correctly to do the equivalent of this.Get() or base.Get().

如果您正在调用虚拟方法,则 this.Get() ,它访问最派生的覆盖(甚至可以在当前类的后代中定义),使用 callvirt说明。无论您反射(reflect)什么类型以获得 MethodInfo 都没有关系。 ,因为它们都共享相同的虚拟表槽。

发出base.Get() ,你必须

  • 使用 call 说明
  • 反射(reflect)基类类型

因为callvirt除了 v 表查找之外,还执行一些额外的操作,包括空指针检查,C# 编译器将其用于所有虚拟和非虚拟调用,除了那些涉及 base 的调用。关键字。

特别是,匿名委托(delegate)和 lambda 无法使用 base关键字,因为只有后代类型可以对虚拟方法进行非虚拟调用(至少在可验证的代码中),并且 lambda 实际上由闭包类型托管。

不幸的是,对于您的用例,无法使用 lambda 表示法或表达式树来表达基调用。 Expression.CompileToMethod只生成callvirt 。嗯,这并不完全正确。它生成call用于调用值类型的静态方法和实例方法。但引用类型的实例方法仅使用 callvirt 。您可以在 System.Linq.Expressions.Compiler.LambdaCompiler.UseVirtual 中看到这一点

感谢 @hvd 根据 the Microsoft Reference Source for UseVirtual 中的评论确认了这一点

关于c# - 如何使用表达式生成此属性实现而不是发出 IL?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23019297/

相关文章:

c# - 将 QT/C++ 转换为 C#

c# - LINQ 按总和分组未按预期工作

c# - 如何显示AudioMeterInformation?

c# - CSharpScript.EvaluateAsync 中的异常行号

c# - 将 foreach 转换为 Linq

c# - 分组输出集合中的项目

c# - 在运行时构建一次性方法

c# - 尝试调用方法时出错

c# - 尝试创建新类型时出现 InvalidProgramException

c# - 读取文件行的​​实现选择 - C#