我希望能够编写一个通用表达式,用户可以用它来描述他想要如何在一系列类型中进行转换.
表达式可能类似于:
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);