Where Not In OR Except simulation of SQL in LINQ to Objects
Suppose I have two lists that holds the list of source file names and destination file names respectively.
The Sourcefilenamelist has files as 1.txt, 2.txt,3.txt, 4.txt
while the Destinaitonlist has 1.txt,2.txt.
I ned to write a linq query to find out which files are in SourceList that are absent in DestinationFile list.
e.g. here the out put will be 3.txt and 4.txt.
I have done this by a foreach
statement. but now I want to do the same by using LINQ(C#).
Edit:
My Code is
List<FileList> sourceFileNames = new List<FileList>();
sourceFileNames.Add(new FileList { FileNames = "1.txt" });
sourceFileNames.Add(new FileList { FileNames = "2.txt" });
sourceFileNames.Add(new FileList { FileNames = "3.txt" });
sourceFileNames.Add(new FileList { FileNames = "4.txt" });
List<FileList> destinationFileNames = new List<FileList>();
destinationFileNames.Add(new FileList { FileNames = "1.txt" });
destinationFileNames.Add(new FileList { FileNames = "2.txt" });
IEnumerable<Fi开发者_运维百科leList> except = sourceFileNames.Except(destinationFileNames);
And Filelist
is a simple class with only one property fileNames of type string.
class FileList
{
public string FileNames { get; set; }
}
That's what Except
is for:
var files = sourceFilenameList.Except(destinationList);
Note that this is a set operation, so if the source list has duplicate entries you'll only see unique results: new[] {a, a, b, b, c}.Except(new[] {b, c})
is just {a}
, not {a, a}
.
Like many LINQ operators, this returns an IEnumerable<T>
- if you want it back as a List
just call ToList
:
var files = sourceFilenameList.Except(destinationList).ToList();
EDIT: Okay, now you've shown what FileList
is, the problem is simply that you haven't implemented equality comparisons. You can do this either by overriding Equals
and GetHashCode
(and possibly IEquatable<FileList>
) or by implementing IEqualityComparer<T>
. However, you've still got a problem: FileNames
is a mutable type, and those don't typically work well in terms of hashing and equality. Two instances may be equal initially, and then one of them could change. I'd recommend reimplementing this as an immutable type. Something like this:
public sealed class FileList : IEquatable<FileList>
{
private readonly string fileNames;
public string FileNames { get { return fileNames; } }
public FileList(string fileNames)
{
// If you want to allow a null FileNames, you'll need to change
// the code in a few places
if (fileNames == null)
{
throw new ArgumentNullException("fileNames");
}
this.fileNames = fileNames;
}
public override int GetHashCode()
{
return fileNames.GetHashCode();
}
public override bool Equals(object other)
{
return Equals(other as FileList);
}
public bool Equals(FileList other)
{
return other != null && other.FileNames == FileNames;
}
}
Your sample code could then become:
List<FileList> sourceFileNames = new List<FileList>
{
new FileList("1.txt"),
new FileList("2.txt"),
new FileList("3.txt"),
new FileList("4.txt")
};
List<FileList> destinationFileNames = new List<FileList>
{
new FileList("1.txt"),
new FileList("2.txt")
};
IEnumerable<FileList> except = sourceFileNames.Except(destinationFileNames);
Sourcefilenamelist.Except(Destinaitonlist)
Not too difficult to do. In your FileList class create a child class that inherits from IEqualityComparer<>
public class FileListComparer : IEqualityComparer<FileList>
{
public bool Equals(FileList x, FileList y)
{
if (x == null || y == null)
{
return false;
}
return x.FileNames.Equals(y.FileNames, StringComparison.OrdinalIgnoreCase);
}
public int GetHashCode(FileList obj) { return base.GetHashCode(); }
}
And then when you call Except, use the Comparer
IEnumerable<FileList> except = sourceFileNames.Except(destinationFileNames, new FileList.FileListComparer() );
I upvoted masenkablast answer, i think the default equality comparer for class instances defaults to class instances' memory address comparison(not the value in class instance itself), so you have to provide your own value equality comparison.
But if you have simple data structure, try to use struct. I tried your code and changed class FileList
to struct FileList
, it works, it only displays 3 and 4
[EDIT] If you want to continue using class without implementing the IEqualityComparer, just implement IEquatable on your class, idea sourced from http://msdn.microsoft.com/en-us/library/bb300779.aspx
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ExceptList
{
class Program
{
static void Main(string[] args)
{
var sourceFileNames = new List<FileList>();
sourceFileNames.Add(new FileList { FileNames = "1.txt" });
sourceFileNames.Add(new FileList { FileNames = "2.txt" });
sourceFileNames.Add(new FileList { FileNames = "3.txt" });
sourceFileNames.Add(new FileList { FileNames = "4.txt" });
List<FileList> destinationFileNames = new List<FileList>();
destinationFileNames.Add(new FileList { FileNames = "1.txt" });
destinationFileNames.Add(new FileList { FileNames = "2.txt" });
var except = sourceFileNames.Except(destinationFileNames);
// list only 3 and 4
foreach (var f in except)
Console.WriteLine(f.FileNames);
Console.ReadLine();
}
}
class FileList : IEquatable<FileList>
{
public string FileNames { get; set; }
#region IEquatable<FileList> Members
public bool Equals(FileList other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
return FileNames.Equals(other.FileNames);
}
#endregion
public override int GetHashCode()
{
return FileNames.GetHashCode();
}
}
}
I think Jon Skeet's answer is the best answer, but your other option is looking directly into the property you want to compare (FileNames)
var destNames = destinationFileNames.Select(destName => destName.FileNames);
IEnumerable<FileList> except = sourceFileNames
.Where(sourceName => !destNames.Contains(sourceName.FileNames));
or (same thing in one expression)
IEnumerable<FileList> except = sourceFileNames
.Where(sourceName => !destinationFileNames
.Select(destNames => destNames.FileNames)
.Contains(sourceName.FileNames));
edit: thanks for the downvote; I tested the code and found a bug. It works now!
精彩评论