我有两个List<FileInfo>
列表,SourceFiles
和DestFiles
。我想建立一个LINQ查询,它将返回一个项目的列表,其文件名在Source
中,但不在Dest
中,即左连接。
我的SourceFiles
的数据集是:
folder1a.txt
folder1b.txt
folder1c.txt
folder1d.txt
DestFiles
is:
folder2a.txt
folder2b.txt
folder2c.txt
所以查询应该返回folder1d.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
并返回一个FileInfo
,并对查询中的每个项目进行此操作以重建列表。但是,如果有一种方法可以直接从查询返回FileInfo
,那将是伟大的。
我不认为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();
我建议使用扩展方法来快速完成Except
和Intersect
。
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();
与其使用join
,不如使用.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);
}
}
用几个样例数据运行它会得到一个包含"d.txt"
FileInfo
对象。你差一点就成功了。但是您只需要获取那些没有加入目标文件的源文件:
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;