c# – 如何替换表达式树中的类型参数?
发布时间:2020-12-15 22:35:22 所属栏目:百科 来源:网络整理
导读:我希望能够编写一个通用表达式,用户可以用它来描述他想要如何在一系列类型中进行转换. 表达式可能类似于: ExpressionFuncPlaceHolder,object sample = x= (object)EqualityComparerPlaceHolder.GetHashCode(x) 我想把它转换成:: ExpressionFuncFoo,object s
我希望能够编写一个通用表达式,用户可以用它来描述他想要如何在一系列类型中进行转换.
表达式可能类似于: Expression<Func<PlaceHolder,object>> sample = x=> (object)EqualityComparer<PlaceHolder>.GetHashCode(x) 我想把它转换成:: Expression<Func<Foo,object>> sample = x=> (object)EqualityComparer<Foo>.GetHashCode(x) 我可以访问表达式,并用X替换PlaceHolder参数,但是我无法解析泛型类型调用. 表达式是用户提供的,您不能将通用方法分配给表达式. 最终结果总是返回一个对象,表达式将始终来自T => object.我将为默认规则将替换的任何对象编译一个新表达式. 这是我现有的代码,但它看起来很复杂. // ReSharper disable once InconsistentNaming // By design this is supposed to look like a generic parameter. public enum TEnum : long { } internal sealed class EnumReplacer : ExpressionVisitor { private Type ReplacePlaceHolder(Type type) { if (type.IsByRef) { return ReplacePlaceHolder(type.GetElementType()).MakeByRefType(); } if (type.IsArray) { // expressionTrees can only deal with 1d arrays. return ReplacePlaceHolder(type.GetElementType()).MakeArrayType(); } if (type.IsGenericType) { var typeDef = type.GetGenericTypeDefinition(); var args = Array.ConvertAll(type.GetGenericArguments(),t => ReplacePlaceHolder(t)); return typeDef.MakeGenericType(args); } if (type == typeof(TEnum)) { return _enumParam.Type; } return type; } private MethodBase ReplacePlaceHolder(MethodBase method) { var newCandidate = method; var currentParams = method.IsGenericMethod ? ((MethodInfo)method).GetGenericMethodDefinition().GetParameters() : method.GetParameters(); // ReSharper disable once PossibleNullReferenceException if (method.DeclaringType.IsGenericType) { var newType = ReplacePlaceHolder(method.DeclaringType); var methodCandidates = newType.GetMembers() .OfType<MethodBase>() .Where(x => x.Name == method.Name && x.IsStatic == method.IsStatic && x.IsGenericMethod == method.IsGenericMethod).ToArray(); // grab the first method that wins. Not 100% correct,but close enough. // yes an evil person could define a class like this:: // class C<T>{ // public object Foo<T>(T b){return null;} // public object Foo(PlaceHolderEnum b){return new object();} // } // my code would prefer the former,where as C#6 likes the later. newCandidate = methodCandidates.First(m => TestParameters(m,currentParams)); } if (method.IsGenericMethod) { var genericArgs = method.GetGenericArguments(); genericArgs = Array.ConvertAll(genericArgs,temp => ReplacePlaceHolder(temp)); newCandidate = ((MethodInfo)newCandidate).GetGenericMethodDefinition().MakeGenericMethod(genericArgs); } return newCandidate; } private Expression ReplacePlaceHolder(MethodBase method,Expression target,ReadOnlyCollection<Expression> arguments) { // no point in not doing this. var newArgs = Visit(arguments); if (target != null) { target = Visit(target); } var newCandidate = ReplacePlaceHolder(method); MethodInfo info = newCandidate as MethodInfo; if (info != null) { return Expression.Call(target,info,newArgs); } return Expression.New((ConstructorInfo)newCandidate,newArgs); } private bool TestParameters(MethodBase candidate,ParameterInfo[] currentParams) { var candidateParams = candidate.GetParameters(); if (candidateParams.Length != currentParams.Length) return false; for (int i = 0; i < currentParams.Length; i++) { // the names should match. if (currentParams[i].Name != candidateParams[i].Name) return false; var curType = currentParams[i].ParameterType; var candidateType = candidateParams[i].ParameterType; // Either they are the same generic type arg,or they are the same type after replacements. if (!((curType.IsGenericParameter && curType.GenericParameterPosition == candidateType.GenericParameterPosition) || ReplacePlaceHolder(curType) == candidateType)) { return false; } } return true; } private readonly ParameterExpression _enumParam; public EnumReplacer(ParameterExpression enumParam) { _enumParam = enumParam; } protected override Expression VisitParameter(ParameterExpression node) { if (node.Type == typeof(TEnum)) { return _enumParam; } if (node.Type == typeof(TypeCode)) { return Expression.Constant(Type.GetTypeCode(_enumParam.Type)); } return base.VisitParameter(node); } protected override Expression VisitUnary(UnaryExpression node) { if (node.NodeType == ExpressionType.Convert || node.NodeType == ExpressionType.ConvertChecked) { var t = ReplacePlaceHolder(node.Type); // this isn't perfect. The compiler loves inserting random casts. To be protective and offer the most range,TEnum should be a long. var method = node.Method == null ? null : ReplacePlaceHolder(node.Method); return node.NodeType == ExpressionType.ConvertChecked ? Expression.ConvertChecked(Visit(node.Operand),t,(MethodInfo) method) : Expression.Convert(Visit(node.Operand),(MethodInfo) method); } if (node.Operand.Type == typeof(TEnum)) { var operand = Visit(node.Operand); return node.Update(operand); } return base.VisitUnary(node); } private MemberInfo ReplacePlaceHolder(MemberInfo member) { if (member.MemberType == MemberTypes.Method || member.MemberType == MemberTypes.Constructor) { return ReplacePlaceHolder((MethodBase) member); } var newType = ReplacePlaceHolder(member.DeclaringType); var newMember = newType.GetMembers().First(x => x.Name == member.Name); return newMember; } protected override Expression VisitNewArray(NewArrayExpression node) { var children = Visit(node.Expressions); // Despite returning T[],it expects T. var type = ReplacePlaceHolder(node.Type.GetElementType()); return Expression.NewArrayInit(type,children); } protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node) { var newMember = ReplacePlaceHolder(node.Member); var bindings = node.Bindings.Select(x => VisitMemberBinding(x)); return Expression.MemberBind(newMember,bindings); } protected override MemberListBinding VisitMemberListBinding(MemberListBinding node) { var prop = ReplacePlaceHolder(node.Member); var inits = node.Initializers.Select(x => VisitElementInit(x)); return Expression.ListBind(prop,inits); } protected override Expression VisitMethodCall(MethodCallExpression node) { return ReplacePlaceHolder(node.Method,node.Object,node.Arguments); } protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) { var expr = Visit(node.Expression); var prop = ReplacePlaceHolder(node.Member); return Expression.Bind(prop,expr); } protected override ElementInit VisitElementInit(ElementInit node) { var method = ReplacePlaceHolder(node.AddMethod); var args = Visit(node.Arguments); return Expression.ElementInit((MethodInfo)method,args); } protected override Expression VisitNew(NewExpression node) { return ReplacePlaceHolder(node.Constructor,null,node.Arguments); } protected override Expression VisitConstant(ConstantExpression node) { // replace typeof expression if (node.Type == typeof(Type) && (Type)node.Value == typeof(TEnum)) { return Expression.Constant(_enumParam.Type); } // explicit usage of default(TEnum) or (TEnum)456 if (node.Type == typeof(TEnum)) { return Expression.Constant(Enum.ToObject(_enumParam.Type,node.Value)); } return base.VisitConstant(node); } } 用法就像这样:: class Program { public class Holder { public int Foo { get; set; } } public class Foo<T1,T2> : IEnumerable { public object GenericMethod<TM,TM2>(TM2 blarg) => blarg.ToString(); public IList<Foo<T1,T2>> T { get; set; } = new List<Foo<T1,T2>>(); public T1 Prop { get; set; } public void Add(int i) { } public Holder Holder { get; set; } = new Holder {}; public IEnumerator GetEnumerator() { throw new NotImplementedException(); } } public enum LongEnum:ulong { } static void Main(string[] args) { Expression<Func<TEnum,TypeCode,object>> evilTest = (x,t) => TypeCode.UInt64 == t ? (object)new Dictionary<TEnum,TypeCode>().TryGetValue(checked((x - 407)),out t) : new Foo<string,TEnum> { Holder = {Foo =6},T = new [] { new Foo<string,TEnum> { T = { new Foo<string,TEnum>{1,2,3,4,5,6,7,8,9,10,11,12} } },new Foo<string,TEnum> { Prop = $"What up hello? {args}" } }}.GenericMethod<string,TEnum>(x); Console.WriteLine(evilTest); var p = Expression.Parameter(typeof(LongEnum),"long"); var expressionBody = new EnumReplacer(p).Visit(evilTest.Body); var q = Expression.Lambda<Func<LongEnum,object>>(expressionBody,p); var func =q.Compile(); var res = func.Invoke((LongEnum)1234567890123Ul); 解决方法
修改现有的表达式树只是为了改变它中使用的类型,这似乎是一个愚蠢的差事.如果您尝试对该类型的对象进行操作,那么您将遇到更多问题.
但是你为什么要全都改变现有的树?您正在尝试参数化一个需要泛型的类型.只需创建一个返回所需表达式的泛型方法(类型为您的参数). Expression<Func<T,object>> CreateConverter<T>() => (T x) => EqualityComparer<T>.Default.GetHashCode(x); 无需创建假占位符类型,泛型类型参数是您的占位符. 如果您需要将其设置为可插入,请将该方法放在界面中,用户将提供执行转换的实现. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |