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]}
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).
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.
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<>) });
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
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.
So, assuming I have a List mylist, how do I generate an Expression as above that will do the equivalent of string.Join(", ", mylist); ?
thanks!