Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing Duplicate List<T> Entries

I expect I'll be able to make a work around but I can't for the life of me understand why this code is not functioning correctly and allowing duplicate entries to be added to the List.

The if statement condition is never met, even when I drag identical files in from the same location. I don't understand why the "Contains" method isn't matching them up.

public class Form1:Form {
    private List<FileInfo> dragDropFiles = new List<FileInfo>();

    private void Form1_DragDrop(object sender, DragEventArgs e) {
        try {
            if (e.Data.GetDataPresent(DataFormats.FileDrop)) {
                string[] files =
                    (string[])e.Data.GetData(DataFormats.FileDrop);

                OutputDragDrop(files);
            }
        }
        catch { }
    }

    private void Form1_DragEnter(object sender, DragEventArgs e) {
        if (e.Data.GetDataPresent(DataFormats.FileDrop))
            e.Effect = DragDropEffects.Copy;
        else
            e.Effect = DragDropEffects.None;
    }

    private void OutputDragDrop(string[] files) {
        try {
            foreach (string file in files) {
                FileInfo fileInfo = new FileInfo(file);

                if (dragDropFiles.Contains(fileInfo)) {
                    dragDropFiles.Remove(fileInfo);
                }
                dragDropFiles.Add(fileInfo);
            }
            PopulateContextMenu();
        }
        catch { }
    }
}

I thought I had found another method in which to achieve this using "Distinct"

However, it appears checkedDragDropFiles & dragDropFiles have the same amount of entries, including duplicates, except when dragDropFiles is displayed in a ListBox it doesn't show them. Why does it do this?

I need to prevent any duplicated list entries, as I would be programmatically creating a menu based off of the list data.

private void OutputDragDrop(string[] files)
{
    try
    {
        foreach (string file in files)
        {
            FileInfo fileInfo = new FileInfo(file);

            //if (dragDropFiles.Contains(fileInfo))
            //{
            //    dragDropFiles.Remove(fileInfo);
            //}
            dragDropFiles.Add(fileInfo);
        }

        List<FileInfo> checkedDragDropFiles = dragDropFiles.Distinct().ToList();

        debugList.DataSource = checkedDragDropFiles;
        debugList2.DataSource = dragDropFiles;
        //PopulateContextMenu();
    }
    catch { }
}
like image 591
negligible Avatar asked Jan 11 '12 12:01

negligible


2 Answers

List<T> does indeed allow duplicates.

In the case of FileInfo, the Contains method will be checking whether the references are the same, but as you are fetching a completely new set of FileInfo, the references are different.

You need to use the overload of Contains that takes an IEqualityComparer - see here.

You can also use HashSet<T> instead - it is a data structure that does not allow duplicates (though with different references, you will still have this issue).

like image 72
Oded Avatar answered Sep 21 '22 10:09

Oded


Because the default Object.Equals implementation compares objects by reference, not by value. Each FileInfo instance you create is a different object, as far as .NET is concerned.

You can use LINQ to specify your custom comparison predicate in order to compare objects by different property:

if (dragDropFiles.Any(f => f.Name == file) == false)
{
    dragDropFiles.Add(fileInfo);
}

[Edit]

Since strings are compared by value, you might as well filter the list before you project it to FileInfo, like this:

private void OutputDragDrop(string[] files)
{
    dragDropFiles = files.Distinct().Select(f => new FileInfo(f)).ToList();
    debugList.DataSource = checkedDragDropFiles;
    debugList2.DataSource = dragDropFiles;
}
like image 32
Groo Avatar answered Sep 21 '22 10:09

Groo