我有两个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 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
,那就太棒了。
答案 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();
我建议使用扩展方法快速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();
答案 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;