我可以使用字符串或IEnumerable <t>,其中T是字符串或IEnumerable <t>?</t> </t>

时间:2014-03-04 13:48:22

标签: c# generics

我有一个处理字符串集合的实用程序方法,比如说:

    public static string MyJoin(this IEnumerable<string> strings)
    {
        return string.Join(Environment.NewLine, strings);
    }

我希望能够以这种方式处理IEnumerable<IEnumerable<string>>IEnumerable<IEnumerable<IEnumerable<string>>>等等所有这些:

    public static string MyJoin(this IEnumerable<IEnumerable<string>> strings)
    {
        return strings.Select(x => x.MyJoin()).MyJoin();
    }

    public static string MyJoin(this IEnumerable<IEnumerable<IEnumerable<string>>> strings)
    {
        return strings.Select(x => x.MyJoin()).MyJoin();
    }

有没有什么方法可以优雅地表达这一点而不用硬编码我可能想要使用的所有可能级别?

  • 我尝试用泛型来做,但我无法弄清楚正确的约束(如果有的话)。
  • 如果可能的话,我也想避免声明一种新类型。
  • 另一方面,表现并不重要。

5 个答案:

答案 0 :(得分:2)

请注意IEnumerable<T>和字符串,因为字符串是IEnumerable<T>,其中T是char

使用多个重载的静态实现:

public static string MyJoin(this IEnumerable<IEnumerable<IEnumerable<string>>> items)
{
    return items.SelectMany(x => x).MyJoin();
}

public static string MyJoin(this IEnumerable<IEnumerable<string>> items)
{
    return items.SelectMany(x => x).MyJoin();
}

public static string MyJoin(this IEnumerable<string> strings)
{
    return string.Join(Environment.NewLine, strings);
}

您不能拥有单一的通用方法并动态“展开”泛型参数。

使用反射实现:

public static string MyJoin<T>(this IEnumerable<T> items)
{
    if (typeof(T) == typeof(string)
    {
        return items.Cast<string>().MyStringJoin();
    }
    var innerIEnumerableType = typeof(T).GetInterfaces().FirstOrDefault(x => x.IsGeneric() 
        && x.GetGenericType() == typeof(IEnumerable<>);
    if (innerIEnumerableType != null)
    {
        // create generic method to 
        var method = typeof(ThisType).GetMethod("MyJoin", /* some flags */)
            .MakeGenericMethod(innerIEnumerableType.GetGenericArguments().First())

        // recursive call to generic method
        return items.Select(x => (string)method.Invoke(null, x)).MyStringJoin();
    }
    throw new InvalidOperationException("Type is not a (nested) enumarable of strings")
}

public static string MyStringJoin(this IEnumerable<string> strings)
{
    return string.Join(Environment.NewLine, strings);
}

老实说,反思在这里没用。为什么使用泛型,如果它不是类型安全的呢?最后,您也可以使用非通用IEnumerable。实施起来要容易得多。看到Denis Itskovich的解决方案,他设法写下我实际上要做的事情。

答案 1 :(得分:2)

基于Stefan的解决方案,简化和编译固定版本

public static string MyJoin(this IEnumerable items)
{
    if (items is IEnumerable<string>)
    {
        return string.Join(Environment.NewLine, (IEnumerable<string>)items);
    }
    if (items is IEnumerable<IEnumerable>)
    {
        return items.Cast<IEnumerable>().Select(x => x.MyJoin())).MyJoin();
    }
    throw new InvalidOperationException("Type is not a (nested) enumarable of strings");
}

答案 2 :(得分:1)

而不是拥有未知类型的IEnumerable,您在这里真正想要做的就是创建一个基于树的结构。执行此操作的最有效方法是创建一个Node类,该类可以表示其子项的值。

public class Node
{
    public Node(string value)
    {
        Value = value;
        Children = Enumerable.Empty<Node>();
    }
    public Node(string value, IEnumerable<Node> children)
    {
        Value = value;
        Children = children;
    }
    public string Value { get; private set; }
    public IEnumerable<Node> Children { get; private set; }
}

这是一个很多更容易遍历的结构。这是一个通用的树遍历方法,我们可以应用于这种情况:

public static IEnumerable<T> Traverse<T>(T item, 
    Func<T, IEnumerable<T>> childSelector)
{
    var stack = new Stack<T>();
    stack.Push(item);
    while (stack.Any())
    {
        var next = stack.Pop();
        yield return next;
        foreach (var child in childSelector(next))
            stack.Push(child);
    }
}

var allValues = Traverse(rootNode, node => node.Children)
    .Select(node => node.Value);

答案 3 :(得分:0)

部分灵感来自Stefan的回答,我已经达成了一个可以在没有泛型的情况下编译的工作解决方案,并且(不幸的是)主要是运行时类型检查:

    public static string MyJoin(this IEnumerable<string> strings)
    {
        return string.MyJoin(Environment.NewLine, strings);
    }
    public static string MyJoin(this IEnumerable<object> items)
    {
        if (items.All(x => x is string))
            return items.Cast<string>().MyJoin();

        if (items.All(x => x is IEnumerable<object>))
            return items.Cast<IEnumerable<object>>().Select(x => x.MyJoin()).MyJoin();

        throw new InvalidOperationException("The argument was not a nested enumerable of strings.");
    }

答案 4 :(得分:-3)

使用SelectMany扩展方法。