如何使用表达式调用以通用列表作为参数的方法调用?

时间:2014-09-17 07:01:05

标签: linq c#-4.0 generics lambda

我们在项目中使用非常优秀的ToStringBuilder作为ToString实现的高性能通用支持。它适用于调试,直到我需要生成对象图的字符串表示,以检查它是否在加载和关闭之间发生了变化。以前我曾使用MemoryStream将对象写入xml,但这看起来很重,所以我决定尝试使用ToStringBuilder,这是我点击showstopper ...

我们的对象图大量使用泛型类型列表,因此当列表打印出来时,它们如下所示:

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

而不是枚举列表并在每个对象上调用ToString,这很好,因为它的默认行为(顺便说一句,ToStringBuilder支持object []),但我们不想改进整个Dto层只是为了解决这个问题)。

我尝试修补有问题的代码(ToStringBuilder.cs,第177行)以识别何时类型是通用列表,然后调用string.Join(",",list) ,但我无法理解linq反射API如何处理泛型。

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

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

但GetMethod返回null,因此无法正常工作。我最终发现this StackOverflow question向我展示了如何通过签名获取通用方法(调用getmethods()代替并过滤结果)。这让我得到了正确的方法句柄,所以我试着做这样的事情:

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);
}

此错误有以下异常:

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()来包装对泛型方法的调用,此时我意识到我已经超出了我的深度。

因此,假设我有一个List mylist,如何生成一个与上面相同的Expression.Join(&#34;,&#34;,mylist); ?

谢谢!

1 个答案:

答案 0 :(得分:1)

获得&#34;泛型类型&#34;您的通用列表(&#34; T&#34;的类型),您可以

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

所以在你的elseif中,你可以做(​​可能更容易,我只是尽可能地保持你的代码)

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();
    }