Share via

Enhance the way of linear search

Jonathan 170 Reputation points
2026-05-31T04:50:50.82+00:00

Hi,

To C# linear search below

User's image

any advice to apply the following way on the above?

Developer technologies | C#
Developer technologies | C#

An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.


Answer accepted by question author

Nancy Vo (WICLOUD CORPORATION) 5,700 Reputation points Microsoft External Staff Moderator
2026-06-04T10:58:42.7833333+00:00

Hi @Jonathan ,

I apologize for the delayed response. I have received your sharing regarding the issue.

The core issue is that your current code reads the entire file on every single IP check. This means if 1,000 visitors arrive at the same time, your program opens and reads the file 1,000 times.

I have already tested this on my side and checked the performance.

You can refer to my example code below:

BlockIPManager.cs:

public static class BlockedIPManager
{
    private static readonly object _lock = new object();
    private static HashSet<string> _blockedIPs = null;
    private static readonly string FileName = @"c:\cmp\list11.txt";

    public static void Initialize()
    {
        lock (_lock)
        {
            _blockedIPs = File.Exists(FileName)
                ? File.ReadLines(FileName)
                      .Select(line => line.Trim())
                      .Where(line => !string.IsNullOrWhiteSpace(line))
                      .ToHashSet()
                : new HashSet<string>();
        }
    }

    public static bool ShouldBlockIP(string ip)
    {
        if (string.IsNullOrWhiteSpace(ip)) return false;
        ip = ip.Trim();

        lock (_lock)
        {
            if (_blockedIPs == null) Initialize();

            if (_blockedIPs.Contains(ip))
                return true;

            _blockedIPs.Add(ip);
            File.AppendAllText(FileName, ip + Environment.NewLine);
            return false;
        }
    }

    public static void AddBlockIPs(IEnumerable<string> ipList)
    {
        if (ipList == null) return;

        lock (_lock)
        {
            if (_blockedIPs == null) Initialize();

            var newIPs = ipList
                .Select(ip => ip?.Trim())
                .Where(ip => !string.IsNullOrWhiteSpace(ip)
                          && !_blockedIPs.Contains(ip))
                .Distinct()
                .ToList();

            if (newIPs.Count == 0) return;

            foreach (var ip in newIPs)
                _blockedIPs.Add(ip);

            File.AppendAllLines(FileName, newIPs);
        }
    }
}

Program.cs

using System.Diagnostics;

BlockedIPManager.Initialize();

var sw = Stopwatch.StartNew();

Console.WriteLine("TEST 1: Single IP lookup — IP at TOP of file (10,000 times)");
sw.Restart();
for (int i = 0; i < 10000; i++)
    BlockedIPManager.ShouldBlockIP("113.45.67.89");
sw.Stop();
Console.WriteLine("Result: " + sw.ElapsedMilliseconds + " ms");
Console.WriteLine("Average per lookup: " + (sw.Elapsed.TotalMicroseconds / 10000).ToString("F4") + " us");

Console.WriteLine("");
Console.WriteLine("TEST 2: Single IP lookup — IP at BOTTOM of file (10,000 times)");
sw.Restart();
for (int i = 0; i < 10000; i++)
    BlockedIPManager.ShouldBlockIP("192.168.1.255");
sw.Stop();
Console.WriteLine("Result: " + sw.ElapsedMilliseconds + " ms");
Console.WriteLine("Average per lookup: " + (sw.Elapsed.TotalMicroseconds / 10000).ToString("F4") + " us");

Console.WriteLine("");
Console.WriteLine("TEST 3: Bulk add 1,000 new IPs");
var bulkIPs = Enumerable.Range(1, 1000)
    .Select(i => $"10.10.{i / 256}.{i % 256}")
    .ToArray();
sw.Restart();
BlockedIPManager.AddBlockIPs(bulkIPs);
sw.Stop();
Console.WriteLine("Result: " + sw.ElapsedMilliseconds + " ms");

Console.WriteLine("");
Console.WriteLine("TEST 4: Duplicate Protection");
string testIP = "77.77.77.77";
BlockedIPManager.ShouldBlockIP(testIP);
BlockedIPManager.ShouldBlockIP(testIP);
BlockedIPManager.ShouldBlockIP(testIP);
var count = File.ReadAllLines(@"c:\cmp\list11.txt")
    .Count(l => l.Trim() == testIP);
Console.WriteLine(testIP + " appears in file: " + count + " time(s) (should be 1)");

Console.WriteLine("");
Console.WriteLine("TEST 5: Functional Check");
bool result1 = BlockedIPManager.ShouldBlockIP("123.45.67.89");
Console.WriteLine("TEST 5a - Existing IP blocked: " + result1);

bool result2 = BlockedIPManager.ShouldBlockIP("8.8.8.8");
Console.WriteLine("TEST 5b - New IP added: " + !result2);

bool result3 = BlockedIPManager.ShouldBlockIP("8.8.8.8");
Console.WriteLine("TEST 5c - Same IP now blocked: " + result3);

Console.ReadLine();

User's image

I hope you find this information helpful. Thank you.

Was this answer helpful?

1 person found this answer helpful.

4 additional answers

Sort by: Most helpful
  1. Nancy Vo (WICLOUD CORPORATION) 5,700 Reputation points Microsoft External Staff Moderator
    2026-06-01T07:02:34.11+00:00

    Hello @Jonathan ,

    Thanks for your question.

    Instead of applying SearchValues + Parallel.ForEach to your existing List, I would suggest a simpler and more effective approach - replace List<string> with HashSet<string>.

    You can refer to my example code:

    var ipSet = new HashSet<string>(
        File.Exists(@"c:/cmp/list11.txt")
            ? File.ReadLines(@"c:/cmp/list11.txt").Select(l => l.Trim())
            : Enumerable.Empty<string>(),
        StringComparer.OrdinalIgnoreCase
    );
    
    public static bool AddIfNotExists(string ip, HashSet<string> ipSet)
    {
        ip = ip.Trim();
    
        if (ipSet.Add(ip))
        {
            File.AppendAllText(@"c:/cmp/list11.txt", ip + Environment.NewLine);
            return true;
        }
    
        return false;
    }
    

    I hope this addresses your question. If this response was helpful, please consider following the guidance to provide feedback.

    Was this answer helpful?


  2. Bruce (SqlWork.com) 84,086 Reputation points
    2026-05-31T16:06:47.36+00:00

    The file I/O is orders of magnitude slower than the index lookup, you will never notice the difference. If the lookup is in a loop, you want to extract the file access to outside the loop. That is build a new list, the write the list once. also you don’t show how lookups are done. Converting the list to a dictionary or hash requires iteration over the list, so if there is only one lookup, the conversion is more expensive. Also if the list is small, find index is not expensive, and may be quicker.

    Was this answer helpful?

    0 comments No comments

  3. Viorel 127K Reputation points
    2026-05-31T08:13:54.1666667+00:00

    The solution depends on other details. For example, if the list is loaded from file, then:

    string path = @"c:\cmp\list11.txt";
    
    ip = ip.Trim( );
    
    if( !File.Exists( path ) || !File.ReadLines( path ).Any( a => a == ip ) )
    {
        File.AppendAllText( path, ip + Environment.NewLine );
    
        return true;
    }
    

    Was this answer helpful?

    0 comments No comments

  4. AI answer

    2026-05-31T04:51:05.11+00:00

    List<T>.FindIndex performs a linear search (O(n)) on every lookup. For frequent lookups or large collections, replace the list-based search with a data structure that provides O(1) lookup.

    A typical enhancement is to use HashSet<string> or Dictionary<string, TValue> instead of repeatedly calling FindIndex on List<string>:

    // Build a HashSet once from the existing list
    var ipSet = new HashSet<string>(list0);  // case-sensitive; use custom comparer if needed
    
    // When checking/adding a new IP
    if (!ipSet.Contains(ip))
    {
        ipSet.Add(ip);
    }
    
    // When persisting back to file, enumerate the set
    File.Delete(@"c:/cmp/list11.txt");
    
    using (var sw = new StreamWriter(@"c:/cmp/list11.txt", append: false))
    {
        foreach (var item in ipSet)
        {
            sw.WriteLine(item.Trim());
        }
    }
    

    Key improvements compared to the original linear search with FindIndex:

    1. HashSet<string>.Contains is O(1) on average, instead of O(n) for List<T>.FindIndex.
    2. The output file is opened once and written in a single pass, instead of opening a new StreamWriter inside the loop.
    3. The collection (ipSet) naturally enforces uniqueness, so duplicates are avoided without extra checks.

    If the order of elements must be preserved, maintain both a List<string> for ordering and a HashSet<string> for fast membership checks:

    var ipList = new List<string>();
    var ipSet  = new HashSet<string>();
    
    void AddIp(string ip)
    {
        if (ipSet.Add(ip))
        {
            ipList.Add(ip); // only added when new
        }
    }
    

    This pattern keeps lookups efficient while preserving insertion order for file output.


    References:

    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.