LINQ出现问题时,在List <fileinfo> </fileinfo>上保持连接

时间:2012-12-26 20:17:09

标签: c# linq

我有两个List<FileInfo>列表,SourceFilesDestFiles。我想构建一个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 example之后,我尝试使用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并返回FileInfo,并为查询中的每个项执行此操作以重建列表。但如果有办法直接从查询中返回FileInfo,那就太棒了。

3 个答案:

答案 0 :(得分: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();

答案 1 :(得分:0)

您可以使用.Except()

处理此问题,而不是使用join
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"

答案 2 :(得分: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;
相关问题