Checking if a SortedList Contains a Specific Value in C#

In C#, SortedList is a collection that maintains elements in sorted order based on their keys. While searching for keys is straightforward and efficient due to the sorted nature of the collection, searching for values requires different approaches. This blog post will explore various methods to check if a SortedList object contains a specific value, discussing their performance characteristics, use cases, and best practices.

The SortedList class in .NET is implemented as a hybrid of an array and a dictionary, providing O(log n) retrieval by key but requiring different strategies for value-based searches.

Table of Contents#

  1. Understanding SortedList Structure
  2. Method 1: ContainsValue() Method
  3. Method 2: Linear Search with Values Property
  4. Method 3: LINQ Any() Method
  5. Method 4: Custom Extension Methods
  6. Performance Comparison
  7. Best Practices and Common Use Cases
  8. Conclusion
  9. References

Understanding SortedList Structure#

Before diving into value searching techniques, it's important to understand how SortedList is structured:

// SortedList stores key-value pairs sorted by keys
SortedList<int, string> sortedList = new SortedList<int, string>
{
    { 3, "Apple" },
    { 1, "Banana" },
    { 2, "Cherry" }
};
 
// Internally, keys and values are stored in separate arrays
// Keys: [1, 2, 3] (sorted)
// Values: ["Banana", "Cherry", "Apple"] (aligned with keys)

The key characteristic to note is that while keys are always sorted, values maintain their position relative to their corresponding keys.

Method 1: ContainsValue() Method#

The most straightforward approach is using the built-in ContainsValue() method.

Syntax#

bool ContainsValue(TValue value)

Example Usage#

using System;
using System.Collections;
 
class Program
{
    static void Main()
    {
        // Create a SortedList
        SortedList<string, int> inventory = new SortedList<string, int>
        {
            { "Laptop", 15 },
            { "Mouse", 42 },
            { "Keyboard", 23 },
            { "Monitor", 8 }
        };
 
        // Check if value exists
        int searchQuantity = 23;
        bool exists = inventory.ContainsValue(searchQuantity);
        
        Console.WriteLine($"Value {searchQuantity} exists: {exists}");
        // Output: Value 23 exists: True
        
        // Check for non-existent value
        bool notExists = inventory.ContainsValue(100);
        Console.WriteLine($"Value 100 exists: {notExists}");
        // Output: Value 100 exists: False
    }
}

Non-Generic SortedList Example#

// For non-generic SortedList (older .NET versions)
SortedList legacyList = new SortedList();
legacyList.Add("A", 100);
legacyList.Add("B", 200);
 
bool containsValue = legacyList.ContainsValue(100);
Console.WriteLine(containsValue); // Output: True

Performance Characteristics#

  • Time Complexity: O(n) - linear search
  • Best For: Small collections or when readability is prioritized
  • Limitations: No early termination for reference types with custom equality

Method 2: Linear Search with Values Property#

You can manually iterate through the Values property for more control over the search process.

Example with Custom Logic#

using System;
using System.Collections.Generic;
 
class Program
{
    static void Main()
    {
        SortedList<int, Product> products = new SortedList<int, Product>
        {
            { 1, new Product("Phone", 699.99m) },
            { 2, new Product("Tablet", 399.99m) },
            { 3, new Product("Laptop", 999.99m) }
        };
 
        // Search for product with specific price
        decimal targetPrice = 399.99m;
        bool found = false;
        
        foreach (var product in products.Values)
        {
            if (product.Price == targetPrice)
            {
                found = true;
                break; // Early exit
            }
        }
 
        Console.WriteLine($"Product with price {targetPrice} found: {found}");
    }
}
 
class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }
}

Advanced Search with Multiple Criteria#

public static bool ContainsValueWithCondition<TKey, TValue>(
    SortedList<TKey, TValue> sortedList, 
    Func<TValue, bool> predicate)
{
    foreach (var value in sortedList.Values)
    {
        if (predicate(value))
            return true;
    }
    return false;
}
 
// Usage
var hasExpensiveProduct = ContainsValueWithCondition(products, p => p.Price > 500m);

Method 3: LINQ Any() Method#

LINQ provides a concise and readable way to search for values.

using System;
using System.Collections.Generic;
using System.Linq;
 
class Program
{
    static void Main()
    {
        SortedList<string, int> scores = new SortedList<string, int>
        {
            { "Alice", 85 },
            { "Bob", 92 },
            { "Charlie", 78 },
            { "Diana", 92 } // Duplicate value
        };
 
        // Check if any value equals 92
        bool hasHighScore = scores.Values.Any(score => score == 92);
        Console.WriteLine($"Has score 92: {hasHighScore}");
 
        // Count occurrences of a value
        int count = scores.Values.Count(score => score == 92);
        Console.WriteLine($"Number of scores 92: {count}");
 
        // Find all keys associated with a value
        var highScorers = scores.Where(kvp => kvp.Value == 92)
                               .Select(kvp => kvp.Key);
        foreach (var name in highScorers)
        {
            Console.WriteLine($"High scorer: {name}");
        }
    }
}

Complex Object Search with LINQ#

public class Employee
{
    public string Name { get; set; }
    public string Department { get; set; }
    public decimal Salary { get; set; }
}
 
// Search in complex objects
SortedList<int, Employee> employees = new SortedList<int, Employee>
{
    { 101, new Employee { Name = "John", Department = "IT", Salary = 60000 } },
    { 102, new Employee { Name = "Jane", Department = "HR", Salary = 55000 } },
    { 103, new Employee { Name = "Mike", Department = "IT", Salary = 65000 } }
};
 
// Search for employees in IT department
bool hasITEmployee = employees.Values.Any(e => e.Department == "IT");
 
// Search with multiple conditions
bool hasHighPaidHR = employees.Values.Any(e => 
    e.Department == "HR" && e.Salary > 50000);

Method 4: Custom Extension Methods#

Create reusable extension methods for common value search patterns.

Custom Extension Methods Implementation#

using System;
using System.Collections.Generic;
using System.Linq;
 
public static class SortedListExtensions
{
    // Generic value search
    public static bool ContainsValue<TKey, TValue>(
        this SortedList<TKey, TValue> sortedList, 
        TValue value, 
        IEqualityComparer<TValue> comparer = null)
    {
        comparer = comparer ?? EqualityComparer<TValue>.Default;
        
        foreach (var item in sortedList.Values)
        {
            if (comparer.Equals(item, value))
                return true;
        }
        return false;
    }
 
    // Search with custom predicate
    public static bool ContainsValueWhere<TKey, TValue>(
        this SortedList<TKey, TValue> sortedList,
        Func<TValue, bool> predicate)
    {
        return sortedList.Values.Any(predicate);
    }
 
    // Find key by value (returns first match)
    public static TKey FindKeyByValue<TKey, TValue>(
        this SortedList<TKey, TValue> sortedList,
        TValue value)
    {
        return sortedList.FirstOrDefault(kvp => 
            EqualityComparer<TValue>.Default.Equals(kvp.Value, value)).Key;
    }
 
    // Find all keys by value
    public static IEnumerable<TKey> FindAllKeysByValue<TKey, TValue>(
        this SortedList<TKey, TValue> sortedList,
        TValue value)
    {
        return sortedList.Where(kvp => 
            EqualityComparer<TValue>.Default.Equals(kvp.Value, value))
                       .Select(kvp => kvp.Key);
    }
}
 
// Usage example
class Program
{
    static void Main()
    {
        SortedList<int, string> data = new SortedList<int, string>
        {
            { 1, "Red" },
            { 2, "Green" },
            { 3, "Blue" },
            { 4, "Green" } // Duplicate value
        };
 
        // Using custom extension methods
        bool hasGreen = data.ContainsValue("Green");
        var greenKeys = data.FindAllKeysByValue("Green");
        
        Console.WriteLine($"Contains Green: {hasGreen}");
        Console.WriteLine("Keys with Green value: " + 
                         string.Join(", ", greenKeys));
    }
}

Performance Comparison#

Understanding the performance implications of each method is crucial for making informed decisions.

Performance Test Example#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
 
class PerformanceTest
{
    static void TestPerformance()
    {
        // Create large SortedList for testing
        var largeList = new SortedList<int, string>();
        for (int i = 0; i < 100000; i++)
        {
            largeList.Add(i, $"Value_{i}");
        }
 
        string targetValue = "Value_99999";
        
        // Test ContainsValue
        var stopwatch = Stopwatch.StartNew();
        bool result1 = largeList.ContainsValue(targetValue);
        stopwatch.Stop();
        Console.WriteLine($"ContainsValue: {stopwatch.ElapsedTicks} ticks");
 
        // Test LINQ Any
        stopwatch.Restart();
        bool result2 = largeList.Values.Any(v => v == targetValue);
        stopwatch.Stop();
        Console.WriteLine($"LINQ Any: {stopwatch.ElapsedTicks} ticks");
 
        // Test manual iteration
        stopwatch.Restart();
        bool result3 = false;
        foreach (var value in largeList.Values)
        {
            if (value == targetValue)
            {
                result3 = true;
                break;
            }
        }
        stopwatch.Stop();
        Console.WriteLine($"Manual iteration: {stopwatch.ElapsedTicks} ticks");
    }
}

Performance Characteristics Summary#

MethodTime ComplexityBest Use CaseLimitations
ContainsValue()O(n)Simple searches, small collectionsNo custom comparison
Manual IterationO(n)Custom logic, early terminationMore code required
LINQ Any()O(n)Readable code, complex conditionsSlight overhead
Custom ExtensionsO(n)Reusable patterns, team standardsAdditional development

Best Practices and Common Use Cases#

1. Choose the Right Method for Your Scenario#

// For simple value checks - use ContainsValue()
if (sortedList.ContainsValue(targetValue)) { }
 
// For complex conditions - use LINQ
if (sortedList.Values.Any(v => v.Property == desiredValue)) { }
 
// For performance-critical code - consider manual iteration
bool found = false;
foreach (var value in sortedList.Values)
{
    if (CustomComparison(value, targetValue))
    {
        found = true;
        break;
    }
}

2. Handle Null Values Appropriately#

SortedList<int, string> listWithNulls = new SortedList<int, string>
{
    { 1, "Valid" },
    { 2, null },
    { 3, "Another" }
};
 
// Safe null checking
bool hasNull = listWithNulls.Values.Any(v => v == null);
bool hasValid = listWithNulls.Values.Any(v => !string.IsNullOrEmpty(v));

3. Consider Value Semantics vs Reference Semantics#

public class Product : IEquatable<Product>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    
    // Implement IEquatable for better performance
    public bool Equals(Product other)
    {
        return Name == other?.Name && Price == other?.Price;
    }
    
    public override bool Equals(object obj) => Equals(obj as Product);
    public override int GetHashCode() => HashCode.Combine(Name, Price);
}
 
// Now ContainsValue will work correctly with Product objects
var products = new SortedList<int, Product>();
bool containsProduct = products.ContainsValue(specificProduct);

4. Optimize for Frequent Searches#

If you frequently need to search by values, consider maintaining a reverse lookup:

public class BidirectionalLookup<TKey, TValue> where TKey : notnull
{
    private readonly SortedList<TKey, TValue> forwardLookup;
    private readonly Dictionary<TValue, List<TKey>> reverseLookup;
    
    public BidirectionalLookup()
    {
        forwardLookup = new SortedList<TKey, TValue>();
        reverseLookup = new Dictionary<TValue, List<TKey>>();
    }
    
    public void Add(TKey key, TValue value)
    {
        forwardLookup.Add(key, value);
        
        if (!reverseLookup.ContainsKey(value))
            reverseLookup[value] = new List<TKey>();
        
        reverseLookup[value].Add(key);
    }
    
    public bool ContainsValue(TValue value) => reverseLookup.ContainsKey(value);
    
    public List<TKey> GetKeysByValue(TValue value) => 
        reverseLookup.TryGetValue(value, out var keys) ? keys : new List<TKey>();
}

Conclusion#

Checking if a SortedList contains a specific value in C# can be accomplished through various methods, each with its own strengths and use cases:

  • ContainsValue() is the most straightforward for simple checks
  • Manual iteration provides the most control for custom logic
  • LINQ methods offer excellent readability for complex conditions
  • Custom extensions enable reusable patterns across your codebase

Remember that value searching in SortedList is always an O(n) operation since values aren't indexed like keys. For performance-critical applications requiring frequent value lookups, consider alternative data structures like additional dictionaries or specialized bidirectional collections.

Choose the method that best fits your specific requirements regarding readability, performance, and maintainability.

References#

  1. Microsoft Docs: SortedList Class
  2. Microsoft Docs: SortedList<TKey, TValue> Class
  3. Microsoft Docs: LINQ Query Expressions
  4. .NET API Browser: EqualityComparer
  5. C# Programming Guide: Extension Methods

Note: All code examples were tested with .NET 5.0 and C# 9.0. Some methods may have different performance characteristics in older .NET versions.