【问题标题】:How to use Expressions to invoke a method call with a generic list as the parameter?如何使用表达式调用以泛型列表为参数的方法调用?
【发布时间】:2014-09-17 07:01:05
【问题描述】:

我们在我们的项目中使用非常优秀的ToStringBuilder 作为我们 ToString 实现的高性能通用支持。在我需要生成对象图的字符串表示以检查它在加载和关闭之间是否发生变化之前,它可以很好地进行调试。以前我使用 MemoryStream 将对象写到 xml,但这似乎很重,所以我决定尝试使用 ToStringBuilder,这是我遇到的阻碍...

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

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,我如何生成一个如上所述的表达式,它相当于 string.Join(", ", mylist); ?

谢谢!

【问题讨论】:

  • 认为您需要 var typedStringJoinMethod =stringJoinMethod.MakeGenericMethod(the generic type of your list) 并在该变量上使用 Expression.Call。

标签: linq c#-4.0 generics lambda


【解决方案1】:

要获取通用列表的“通用类型”(“T”的类型),您可以这样做

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

【讨论】:

  • 啊!谢谢,我现在在家,但我会在早上尝试你的代码,如果它有效,我会接受你的回答,谢谢!
猜你喜欢
  • 1970-01-01
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-06
  • 1970-01-01
相关资源
最近更新 更多