score:5

Accepted answer

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() );

score:8

Sourcefilenamelist.Except(Destinaitonlist)

score:14

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);

score:2

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();
        }
    }

}

score:0

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!


Related Articles