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#
- Understanding SortedList Structure
- Method 1: ContainsValue() Method
- Method 2: Linear Search with Values Property
- Method 3: LINQ Any() Method
- Method 4: Custom Extension Methods
- Performance Comparison
- Best Practices and Common Use Cases
- Conclusion
- 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: TruePerformance 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.
Basic LINQ Search#
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#
| Method | Time Complexity | Best Use Case | Limitations |
|---|---|---|---|
ContainsValue() | O(n) | Simple searches, small collections | No custom comparison |
| Manual Iteration | O(n) | Custom logic, early termination | More code required |
LINQ Any() | O(n) | Readable code, complex conditions | Slight overhead |
| Custom Extensions | O(n) | Reusable patterns, team standards | Additional 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#
- Microsoft Docs: SortedList Class
- Microsoft Docs: SortedList<TKey, TValue> Class
- Microsoft Docs: LINQ Query Expressions
- .NET API Browser: EqualityComparer
- 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.