连接/合并两个IEnumerable序列

48

我有两组数据行,它们各自是IEnumerable类型。我想将这两个列表连接成一个列表。我知道这是可以做到的。我不想使用循环,注意到两个列表上有Union方法和Join方法。有什么建议吗?


数据行集是相同类型的吗?还是不同类型的? - Oded
@Obed....它们是相同类型的。 - MikeTWebb
4个回答

99
假设您的对象类型相同,您可以使用 UnionConcat。请注意,就像 SQL 中的 UNION 关键字一样,Union 操作将确保消除重复项,而 Concat(与 UNION ALL 类似)只会将第二个列表添加到第一个列表的末尾。
IEnumerable<T> first = ...;
IEnumerable<T> second = ...;

IEnumerable<T> combined = first.Concat(second);

或者

IEnumerable<T> combined = first.Union(second);

如果它们是不同类型的,那么你需要将它们 Select 到一个共同的类型中。例如:
IEnumerable<TOne> first = ...;
IEnumerable<TTwo> second = ...;

IEnumerable<T> combined = first.Select(f => ConvertToT(f)).Concat(
                          second.Select(s => ConvertToT(s)));

ConvertToT(TOne f)ConvertToT(TTwo s)分别表示一种将TOne(和TTwo)的实例转换为T实例的操作。


@Adam....我说得太早了。我已经执行了Union操作,但是我得到了重复的记录。我需要初始化某种关键属性吗? - MikeTWebb
1
@MikeTWebb: Union有一个重载,允许您指定一个IEqualityComparer - Adam Robinson

2

我最近遇到了一个类似的情况,需要将多个序列连接起来。

我自然地在Google/StackOverflow上搜索现有的解决方案,但是没有找到不评估枚举的任何内容,例如将其转换为数组,然后使用Array.Copy()等,因此我编写了一个扩展和静态实用程序方法,称为ConcatMultiple

希望这可以帮助需要执行相同操作的任何人。

/// <summary>
/// Concatenates multiple sequences
/// </summary>
/// <typeparam name="TSource">The type of the elements of the input sequences.</typeparam>
/// <param name="first">The first sequence to concatenate.</param>
/// <param name="source">The other sequences to concatenate.</param>
/// <returns></returns>
public static IEnumerable<TSource> ConcatMultiple<TSource>(this IEnumerable<TSource> first, params IEnumerable<TSource>[] source)
{
    if (first == null)
        throw new ArgumentNullException("first");

    if (source.Any(x => (x == null)))
        throw new ArgumentNullException("source");

    return ConcatIterator<TSource>(source);
}

private static IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource> first, params IEnumerable<TSource>[] source)
{
    foreach (var iteratorVariable in first)
        yield return iteratorVariable;

    foreach (var enumerable in source)
    {
        foreach (var iteratorVariable in enumerable)
            yield return iteratorVariable;
    }
}

/// <summary>
/// Concatenates multiple sequences
/// </summary>
/// <typeparam name="TSource">The type of the elements of the input sequences.</typeparam>        
/// <param name="source">The sequences to concatenate.</param>
/// <returns></returns>
public static IEnumerable<TSource> ConcatMultiple<TSource>(params IEnumerable<TSource>[] source)
{
    if (source.Any(x => (x == null)))
        throw new ArgumentNullException("source");

    return ConcatIterator<TSource>(source);
}

private static IEnumerable<TSource> ConcatIterator<TSource>(params IEnumerable<TSource>[] source)
{
    foreach (var enumerable in source)
    {
        foreach (var iteratorVariable in enumerable)
            yield return iteratorVariable;
    }
}

1

Join方法类似于SQL的join,其中列表是基于条件进行交叉引用,它不是字符串连接或添加到列表。 Union方法确实可以实现您想要的效果,Concat方法也可以,但两者都是惰性评估,并且有参数非空的要求。它们返回一个ConcatIterator或UnionIterator,如果重复调用可能会导致问题。急切的评估会产生不同的行为,如果这是您想要的,那么可以使用下面的扩展方法。

public static IEnumerable<T> myEagerConcat<T>(this IEnumerable<T> first,
                                                   IEnumerable<T> second)
{
    return (first ?? Enumerable.Empty<T>()).Concat(
           (second ?? Enumerable.Empty<T>())).ToList();
}

1
延迟调用第二个及之后的可枚举对象
我通常使用Linq的 IEnumerable<T>.Concat(),但今天我需要确保第二次枚举不会在第一次枚举结束之前被枚举。(例如,我不想同时运行两个数据库查询)。因此,下面的函数可以延迟枚举。
    IEnumerable<T> DelayedConcat<T>(params Func<IEnumerable<T>>[] enumerableList)
    {
        foreach(var enumerable in enumerableList)
        {
            foreach (var item in enumerable())
            {
                yield return item;
            }
        }
    }

使用方法:

    return DelayedConcat(
                () => GetEnumerable1(),
                () => GetEnumerable2(),
 // and so on.. () => GetEnumerable3(),
                );

在这个例子中,GetEnumerable2函数的调用将被延迟,直到GetEnumerable1被完全枚举。

几乎等同于参考实现。 - TJHeuvel
@TJHeuvel 错了。那个接收两个枚举,但我的接收两个 Func<IEnumerable<T>> 并确保在第一个枚举被耗尽之前不会创建第二个枚举。 - Gerardo Grignoli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接