linq - métodos - ¿Cómo usar Expressions para invocar una llamada a método con una lista genérica como parámetro?
lambda sintaxis (1)
Para obtener el "tipo genérico" de su lista genérica (el tipo de "T"), puede hacer
var genericListType= type.GetGenericArguments()[0];
entonces en tu elseif, podrías hacerlo (puede ser más fácil, solo me quedo lo más cerca posible de tu código)
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();
}
Estamos utilizando el muy excelente ToStringBuilder en nuestro proyecto como un respaldo genérico y eficaz para nuestras implementaciones de ToString. Funcionó bien para la depuración hasta que necesité generar una representación de cadena de un gráfico de objetos para verificar si había cambiado entre la carga y el cierre. Anteriormente había usado un MemoryStream para escribir el objeto en xml, pero parecía pesado, así que decidí probar ToStringBuilder, que es donde toco un showtopper ...
Nuestro gráfico de objetos utiliza listas de tipos genéricos en gran medida, por lo que cuando las listas se imprimen se ven como las siguientes:
PropertyName:{System.Collections.Generic.List`1[Namespace.Path.To.MyClassDto]}
En lugar de enumerar a través de la lista e invocar a ToString en cada objeto, lo cual está bien ya que es el comportamiento predeterminado (por cierto, ToStringBuilder admite object [], pero no queremos actualizar toda nuestra capa Dto solo para solucionar este problema).
Traté de aplicar un parche al código en cuestión ( ToStringBuilder.cs , línea 177) para reconocer cuando el tipo es una lista genérica, y luego invocar a string.Join (",", list), pero no podía entender cómo la API de reflexión de linq maneja genéricos.
Lo primero que intenté fue manejar el método String.Join (IEnumerable <>) como este:
var stringJoinMethod = typeof(string).GetMethod("Join", new[] { typeof(string), typeof(IEnumerable<>) });
Pero GetMethod devolvió null, por lo que no funcionó. Eventualmente encontré esta pregunta de StackOverflow que me mostró cómo obtener un método genérico por firma (llame a getmethods () y filtre los resultados). Eso me dio el manejo correcto del método, así que traté de hacer algo como esto:
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);
}
Este error con la siguiente excepción:
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
Empecé a buscar en Google ese mensaje de error y encontré a personas que hablaban sobre usar Expression.Lamba () para envolver las llamadas a métodos genéricos, momento en el que me di cuenta de que estaba fuera de mi alcance.
Entonces, asumiendo que tengo una List mylist, ¿cómo puedo generar una Expresión como la anterior que hará el equivalente de string.Join (",", mylist); ?
¡Gracias!