Search code examples
c#listgenericsany

List<T>.Any(); How to get index of matched item?


I am comparing Listview items with Generic List items with the List<T>.Any() method like this:

foreach (ListViewItem itemRow in lstviewAddsheets.Items)
{
    if (InvalidSheets.Any(x => x != null && x.FilePath == itemRow.Tag.ToString()))
    {
        // Match found
    }
}

Please tell me, how to get the InvalidSheets list index which was matched with itemRow.Tag.ToString().


Solution

  • Since there seems some debate about how much faster it would be to use List.FindIndex() instead of Linq to find the index, I wrote a test program.

    This assumes that you only care about finding the index of the first matching item in a list. It doesn't handle multiple matching items.

    Also note that this test is worst-case in that the matching item is at the very end of the list.

    My results for an x86 release build (run on Windows 8 x64, quad core processor):

    Calling Via FindIndex() 100 times took 00:00:00.9326057
    Calling Via Linq 100 times took 00:00:04.0014677
    Calling Via FindIndex() 100 times took 00:00:00.8994282
    Calling Via Linq 100 times took 00:00:03.9179414
    Calling Via FindIndex() 100 times took 00:00:00.8971618
    Calling Via Linq 100 times took 00:00:03.9134804
    Calling Via FindIndex() 100 times took 00:00:00.8963758
    

    showing that List.FindIndex() is roughly four times faster than using Linq.

    Here's the test code:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    namespace Demo
    {
        class Test
        {
            public string FilePath;
        }
    
        class Program
        {
            private void run()
            {
                int count = 1000000;
    
                List<Test> list = new List<Test>(count);
    
                for (int i = 0; i < count; ++i)
                    list.Add(new Test{ FilePath = i.ToString()});
    
                string target = (count-1).ToString();
    
                for (int trial = 0; trial < 4; ++trial)
                {
                    Action viaFindIndex =
                    (
                        () =>
                        {
                            int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
                        }
                    );
    
                    Action viaLinq =
                    (
                        () =>
                        {
                            int index = list.Select((x, i) => new { Item = x, Index = i })
                            .First(x => (x != null) && (x.Item.FilePath == target))
                            .Index;
                        }
                    );
    
                    viaFindIndex.TimeThis("Via FindIndex()", 100);
                    viaLinq.TimeThis("Via Linq", 100);
                }
            }
    
            private static void Main()
            {
                new Program().run();
            }
        }
    
        static class DemoUtil
        {
            public static void TimeThis(this Action action, string title, int count = 1)
            {
                var sw = Stopwatch.StartNew();
    
                for (int i = 0; i < count; ++i)
                    action();
    
                Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed);
            }
        }
    }
    

    So given that List.FindIndex() is both much faster AND much easier to read than using the Linq, I can see no reason to use Linq to solve this particular problem.

    int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
    

    versus

    int index = list.Select((x, i) => new { Item = x, Index = i })
                .First(x => (x != null) && (x.Item.FilePath == target))
                .Index;
    

    The first version wins on all counts IMO.