1

我有两个List<FileInfo>列表,SourceFiles并且DestFiles. 我想构建一个 LINQ 查询,它将返回文件名在Source但不在的项目列表Dest,即左连接。

我的数据集SourceFiles是:

folder1\a.txt
folder1\b.txt
folder1\c.txt
folder1\d.txt

DestFiles是:

folder2\a.txt
folder2\b.txt
folder2\c.txt

所以查询应该返回 folder1\d.txt

按照MSDN 示例,我尝试使用 LINQ 语法:

var queryX = from s in SourceFiles
             join d in DestFiles
             on s.Name equals d.Name
             into SourceJoinDest
             from joinRow in SourceJoinDest.DefaultIfEmpty()
             select new
             {
                 joinRow.FullName
             };

并使用扩展方法:

var query = SourceFiles.GroupJoin(DestFiles,
                                    source => source.Name,
                                    dest => dest.Name,
                                    (source,dest) => new
                                    {
                                        path = source.FullName
                                    }).Select(x => x.path.DefaultIfEmpty())

但是这些都不起作用;LINQ 语法版本返回Object reference not sent to an instance of an object,扩展版本返回Enumeration yielded no results.

我意识到这些查询只返回一组FullName属性,而不是完整的FileInfo对象;我有接受每个FullName并返回 a 的代码,FileInfo并为查询中的每个项目执行此操作以重建列表。但是,如果有办法FileInfo直接从查询中返回 a,那就太好了。

4

3 回答 3

3

我认为这不是Join理想的工具。基本上你正在寻找一个Except. 内置Except没有通过 lambda 指定属性的重载。您将必须创建自己的IEqualityComparer. 但是,您可以这样做:

var excepts = SourceFiles.Where(c => !DestFiles.Any(p => p.Name == c.Name)).ToList();

或者,要仅选择完整路径,您可以Select在最后使用。

var excepts = SourceFiles.Where(c => !DestFiles.Any(p => p.Name == c.Name))
                         .Select(f => f.FullName).ToList();

我建议使用扩展方法来做快速ExceptIntersect.

public static IEnumerable<U> Except<R, S, T, U>(this IEnumerable<R> mainList, 
                                                IEnumerable<S> toBeSubtractedList,
                                                Func<R, T> mainListFunction, 
                                                Func<S, T> toBeSubtractedListFunction,
                                                Func<R, U> resultSelector)
{
    return EnumerateToCheck(mainList, toBeSubtractedList, mainListFunction, 
                            toBeSubtractedListFunction, resultSelector, false);
}

static IEnumerable<U> EnumerateToCheck<R, S, T, U>(IEnumerable<R> mainList, 
                                                   IEnumerable<S> secondaryList,
                                                   Func<R, T> mainListFunction, 
                                                   Func<S, T> secondaryListFunction,
                                                   Func<R, U> resultSelector,
                                                   bool ifFound)
{
    foreach (var r in mainList)
    {
        bool found = false;
        foreach (var s in secondaryList)
        {
            if (object.Equals(mainListFunction(r), secondaryListFunction(s)))
            {
                found = true;
                break;
            }
        }

        if (found == ifFound)
            yield return resultSelector(r);
    }

    //or may be just
    //return mainList.Where(r => secondaryList.Any(s => object.Equals(mainListFunction(r), secondaryListFunction(s))) == ifFound)
    //               .Select(r => resultSelector(r));
    //but I like the verbose way.. easier to debug..
}

public static IEnumerable<U> Intersect<R, S, T, U>(this IEnumerable<R> mainList, 
                                                   IEnumerable<S> toIntersectList,
                                                   Func<R, T> mainListFunction,
                                                   Func<S, T> toIntersectListFunction,
                                                   Func<R, U> resultSelector)
{
    return EnumerateToCheck(mainList, toIntersectList, mainListFunction, 
                            toIntersectListFunction, resultSelector, true);
}

现在在你的情况下,你可以这样做:

var excepts = SourceFiles.Except(DestFiles, p => p.Name, p => p.Name, p => p.FullName)
                         .ToList();
于 2012-12-26T20:53:19.713 回答
0

而不是使用 ajoin你可能能够处理这个.Except()

var enumerable = sourceFiles.Except(destFiles, new FileInfoComparer<FileInfo>((f1, f2)=>f1.Name == f2.Name, f=>f.Name.GetHashCode()));

.Except()接受一个IEqualityComparer<T>您可以自己编写或使用接受 lambda 的包装器的方法。

    class FileInfoComparer<T> : IEqualityComparer<T>
    {
        public FileInfoComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
        {
            _equals = equals;
            _getHashCode = getHashCode;
        }

        readonly Func<T, T, bool> _equals;
        public bool Equals(T x, T y)
        {
            return _equals(x, y);
        }

        readonly Func<T, int> _getHashCode;
        public int GetHashCode(T obj)
        {
            return _getHashCode(obj);
        }
    } 

使用一些示例数据运行它会产生一个FileInfo对象,其中包含"d.txt"

于 2012-12-26T20:53:30.290 回答
0

你几乎做到了。但是您只需要获取那些没有加入目标文件的源文件:

var query = from s in SourceFiles
            join d in DestFiles
                on s.Name equals d.Name into g
            where !g.Any() // empty group!
            select s;
于 2012-12-26T20:55:20.500 回答