Reputation: 2449
I've written an extension method like this:
public static String Join<T>(this IEnumerable<T> enumerable)
{
if (typeof(T) is IEnumerable<T'> where T' is unknown at compile time)
{
return String.Join(",", enumerable.Select(e => e.Join()));
}
return String.Join(",", enumerable.Select(e => e.ToString()));
}
The problem is I don't know how to write code inside the if statement to make it works. Any suggestion? Thanks!
Upvotes: 1
Views: 422
Reputation: 127603
Because you are only calling .ToString()
you actually don't really care what T is, only if it implements IEnumerable
or not. Here is how to do it without reflection and just using IEnumerable
instead of IEnumerable<T>
, I do my own logic for String.Join
because it made it easier to code the recursive logic.
internal static class ExtensionMethods
{
public static String Join<T>(this IEnumerable<T> enumerable)
{
StringBuilder sb = new StringBuilder();
JoinInternal(enumerable, sb, true);
return sb.ToString();
}
private static bool JoinInternal(IEnumerable enumerable, StringBuilder sb, bool first)
{
foreach (var item in enumerable)
{
var castItem = item as IEnumerable;
if (castItem != null)
{
first = JoinInternal(castItem, sb, first);
}
else
{
if (!first)
{
sb.Append(",");
}
else
{
first = false;
}
sb.Append(item);
}
}
return first;
}
}
Here is a test program I wrote that shows it all works (it tests classes, structs, and IEnumerables 3 layers deep).
EDIT: Per your comment here is another version that flattens out the nested IEnumerables, you can do whatever you want to each element when you are done.
internal static class ExtensionMethods
{
public static IEnumerable<T> SelectManyRecusive<T>(this IEnumerable enumerable)
{
foreach (var item in enumerable)
{
var castEnumerable = item as IEnumerable;
if (castEnumerable != null
&& ((typeof(T) != typeof(string)) || !(castEnumerable is string))) //Don't split string to char if string is our target
{
foreach (var inner in SelectManyRecusive<T>(castEnumerable))
{
yield return inner;
}
}
else
{
if (item is T)
{
yield return (T)item;
}
}
}
}
}
There also was a bug I ran in to that I think may afffect my first part of my answer, a string
is technically a IEnumerable<char>
so a IEnumerable<string>
could be also seen as a IEnumerable<IEnumerable<char>>
and it may put too many ,
in. This second version has a check for that.
Test program showing how to use this method and String.Join
together.
Upvotes: 2
Reputation: 13138
You can use the non generic version of IEnumerable
that returns objects.
public static String Join(this IEnumerable enumerable)
{
var enumerable2 = enumerable as IEnumerable<IEnumerable>;
if (enumerable2 != null)
{
return String.Join(",", enumerable2.Select(e => e.Join()));
}
return String.Join(",", enumerable.Select(e => e.ToString()));
}
Edit : the method doesn't need to be generic. Beware that string is IEnumerable so you might want to add a special case :
public static String Join(this IEnumerable enumerable)
{
var stringEnumerable = enumerable as IEnumerable<string>;
if (stringEnumerable != null)
{
return String.Join(",", stringEnumerable);
}
var enumerable2 = enumerable as IEnumerable<IEnumerable>;
if (enumerable2 != null)
{
return String.Join(",", enumerable2.Select(e => e.Join()));
}
return String.Join(",", enumerable.Select(e => e.ToString()));
}
Upvotes: 1
Reputation: 411
The "check" part is not much of a problem but the subsequent call to Join
requires you to supply a Type Argument. Therefore, the only solution I found uses reflection to make that call.
Complete code as follows
(the function to retrieve the Type Argument to IEnumerable<>
is more generic than needed here because I just copy&pasted it from a project):
static public Type[] ListeTypeArgumentZuBaseOderInterface(
this Type Type,
Type BaseGenericTypeDefinition)
{
if (null == Type || null == BaseGenericTypeDefinition)
{
return null;
}
if (BaseGenericTypeDefinition.IsInterface)
{
var MengeInterface = Type.GetInterfaces();
if (null != MengeInterface)
{
foreach (var Interface in MengeInterface)
{
if (!Interface.IsGenericType)
{
continue;
}
var InterfaceGenericTypeDefinition = Interface.GetGenericTypeDefinition();
if (!InterfaceGenericTypeDefinition.Equals(BaseGenericTypeDefinition))
{
continue;
}
return Interface.GenericTypeArguments;
}
}
}
else
{
var BaseTypeAktuel = Type;
while (null != BaseTypeAktuel)
{
if (BaseTypeAktuel.IsGenericType)
{
var BaseTypeGenericTypeDefinition = BaseTypeAktuel.GetGenericTypeDefinition();
if (BaseTypeGenericTypeDefinition.Equals(BaseGenericTypeDefinition))
{
return BaseTypeAktuel.GenericTypeArguments;
}
}
BaseTypeAktuel = BaseTypeAktuel.BaseType;
}
}
return null;
}
static public Type IEnumerableTypeArgumentExtrakt(
this Type TypeImplementingEnumerable)
{
var GenericTypeArguments =
ListeTypeArgumentZuBaseOderInterface(TypeImplementingEnumerable, typeof(IEnumerable<>));
if (null == GenericTypeArguments)
{
// does not implement IEnumerable<>
return null;
}
return GenericTypeArguments.FirstOrDefault();
}
public static String Join<T>(this IEnumerable<T> enumerable)
{
// ¡the typeof() has to refer to the class containing this Method!:
var SelfType = typeof(Extension);
var IEnumerableTypeArgument = IEnumerableTypeArgumentExtrakt(typeof(T));
if (null != IEnumerableTypeArgument)
{
System.Reflection.MethodInfo method = SelfType.GetMethod("Join");
System.Reflection.MethodInfo generic = method.MakeGenericMethod(IEnumerableTypeArgument);
return String.Join(",", enumerable.Select(e => generic.Invoke(null, new object[] { e })));
}
return String.Join(",", enumerable.Select(e => e.ToString()));
}
reflection ienumerable makegenericmethod
Upvotes: 0