如何使用表达式调用以通用列表作为参数的方法调用? [英] How to use Expressions to invoke a method call with a generic list as the parameter?

查看:169
本文介绍了如何使用表达式调用以通用列表作为参数的方法调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在我们的项目中使用了非常出色的 ToStringBuilder 作为我们ToString的高性能,通用支持实现。它调试工作正常,直到我需要生成一个对象图形的字符串表示,以检查它是否在加载和关闭之间更改。以前我使用了一个MemoryStream将对象写到xml,但是这似乎重量级,所以我决定尝试使用ToStringBuilder,这是我打开一个showstopper ...

We're using the very Excellent ToStringBuilder in our project as a performant, generic backing for our ToString implementations. It worked fine for debugging until I needed to generate a string representation of an object graph to check if it had changed in between loading and closing. Previously I had used a MemoryStream to write the object out to xml, but this seemed heavyweight so I decided to try out using ToStringBuilder, which is where I hit a showstopper...



Our object graph uses generic typed lists heavily, so when the lists are printed out they look like the following:

PropertyName:{System.Collections.Generic.List`1[Namespace.Path.To.MyClassDto]}



<通过列表枚举和调用ToString对每个对象,这是罚款的默认行为(btw,ToStringBuilder支持object [],但我们不想改造我们的整个Dto层只是为了解决这个问题)。

Instead of enumerating through the list and invoking ToString on each object, which is fine as that's default behaviour (btw, ToStringBuilder supports object[], but we don't want to retrofit our entire Dto layer just to fix this problem).

我试图修补有问题的代码( ToStringBuilder.cs ,第177行)来识别何时类型是一个通用列表,然后调用string.Join(,,list),但我不能得到我的头如何linq反射API处理泛型。

I tried to patch the code in question (ToStringBuilder.cs, line 177) to recognise when the type is a generic list, and then invoke string.Join(", ", list), but I couldn't get my head around how the linq reflection API handles generics.

我尝试的第一件事是获取String.Join(IEnumerable <>)方法的句柄,如下所示:

The first thing I tried was to get a handle to the String.Join(IEnumerable<>) method like this:

var stringJoinMethod = typeof(string).GetMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });

但是GetMethod返回null,所以没有工作。我最终发现这个StackOverflow问题,显示如何通过签名(调用getmethods())获取一个通用方法,并过滤结果)。这给了我正确的方法句柄,所以我试着做这样的:

But GetMethod returned null so that didn't work. I eventually found this StackOverflow question that showed me how to get a generic method by signature (call getmethods() instead and filter the results). That got me the correct method handle, so I tried to do something like this:

private void AppendMember(MemberInfo memberInfo)
{
    AppendQuotesIfRequiredForType(memberInfo);

    Type type = GetMemberType(memberInfo);
    var memberAppendMethod = typeof(StringBuilder).GetMethod("Append", new[] { type });
    Expression getMemberValue = Expression.MakeMemberAccess(TargetArgExpression, memberInfo);

    if (type.IsValueType)
    {
        Type appendArgType = memberAppendMethod.GetParameters()[0].ParameterType;
        if (type != appendArgType)
        {
            getMemberValue = Expression.TypeAs(getMemberValue, typeof(object));
        }
        //my code begins here.
        _appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
    }
    else if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>)))
    {
        // now to emit some code to do the below, you wouldn't think it'd be this hard...
        // string.Join(", ", genericList);
        AppendStartOfMembers();

        //this returns null, because generics are not well supported by the reflection API, boo!
        var stringJoinMethod = typeof(string).GetGenericMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
        var CommaSpace = Expression.Constant(", ");

        // this doesn't work, throws an ArgumentException as below
        getMemberValue = Expression.Call(stringJoinMethod, CommaSpace, getMemberValue);


        _appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));

        AppendEndOfMembers();
    }
    else
    {
        //primitives like strings
        _appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));
    }

    //my code ends here.
    AppendQuotesIfRequiredForType(memberInfo);
}

此错误与以下异常有关:

This errors with the following exception:

System.ArgumentException: "Method System.String Join[T](System.String, System.Collections.Generic.IEnumerable`1[T]) is a generic method definition"
   at System.Linq.Expressions.Expression.ValidateMethodInfo(MethodInfo method)
   at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1)
   at MyNameSpace.Common.ToStringBuilder`1.AppendMember(MemberInfo memberInfo) in C:\myproject\MyNamespace.Common\ToStringBuilder.cs:line 206

我开始搜索该错误消息,发现人们在谈论使用Expression.Lamba

I started googling that error message and found people talking about using Expression.Lamba() to wrap calls to generic methods, at which point I realised I was way out of my depth.

因此,假设我有一个List mylist,我如何生成一个Expression如上所示这将等效于string.Join(,,mylist); ?

So, assuming I have a List mylist, how do I generate an Expression as above that will do the equivalent of string.Join(", ", mylist); ?

感谢!

推荐答案

你的通用列表(T类型),你可以做

To get the "generic type" of your generic list (the type of "T"), you can do

var genericListType= type.GetGenericArguments()[0];

所以在你的elseif中,你可以做到(可能更容易,

so in your elseif, you could do (it might be easier, I just stay as close as possible to your code)

else if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(List<>)))
    {
        // now to emit some code to do the below, you wouldn't think it'd be this hard...
        // string.Join(", ", genericList);
        AppendStartOfMembers();

        //this returns null, because generics are not well supported by the reflection API, boo!
        var stringJoinMethod = typeof(string).GetGenericMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
        var CommaSpace = Expression.Constant(", ");

        var genericListType= type.GetGenericArguments()[0];
        var genericStringJoinMethod = stringJoinMethod.MakeGenericMethod(new[]{genericListType});

        // this doesn't work, throws an ArgumentException as below
        getMemberValue = Expression.Call(genericStringJoinMethod , CommaSpace, getMemberValue);


        _appendExpressions.Add(Expression.Call(SbArgExpression, memberAppendMethod, getMemberValue));

        AppendEndOfMembers();
    }

这篇关于如何使用表达式调用以通用列表作为参数的方法调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆