1

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!

huangcd
  • 2,369
  • 3
  • 18
  • 26

3 Answers3

2

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.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Thanks, though it's not that 'generic', but for the ToString problem, it's indeed good and simple enough – huangcd Apr 18 '15 at 16:36
  • @huangcd What do you mean by "this is not generic"? – Yuval Itzchakov Apr 18 '15 at 16:45
  • 1
    @huangcd I added a update which gives you a [`SelectMany`](https://msdn.microsoft.com/en-us/library/system.linq.enumerable.selectmany%28v=vs.100%29.aspx) like method which will recursively go down the IEnumerable chain flattening it out to a single typed `IEnumerable`. It works like the [`OfType()`](https://msdn.microsoft.com/en-us/library/bb360913(v=vs.100).aspx) linq method, filtering out non matching items. – Scott Chamberlain Apr 18 '15 at 17:02
1

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()));
}
Guillaume
  • 12,824
  • 3
  • 40
  • 48
0

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

Michael Rätzel
  • 401
  • 1
  • 6
  • 17