C# Containsメソッド完全ガイド:7つの実践的な使い方とパフォーマンス最適化のコツ

はじめに

C#のContainsメソッドは、文字列検索やコレクション内の要素確認など、日常的なプログラミングで頻繁に使用される重要なメソッドです。
単純に見えるこのメソッドですが、適切に使用することで、より効率的で保守性の高いコードを実現できます。

本記事で学べること

Containsメソッドの基本的な使い方と動作原理

データ型別の効率的な実装方法とベストプラクティス

パフォーマンスを考慮した最適化手法

実務で遭遇する具体的なユースケースとその解決方法

一般的な実装ミスとその対処法

メモリ効率とスレッドセーフティを考慮した実装テクニック

Containsメソッドの基礎知識

Containsメソッドの動作原理と基本構文

Containsメソッドは、C#で配列やコレクション、文字列内の要素や部分文字列の存在を確認するための基本的なメソッドです。
このメソッドは、検索対象が見つかった場合はtrueを、見つからなかった場合はfalseを返します。

using System;
using System.Collections.Generic;
using System.Linq;

namespace ContainsMethodBasics
{
    public class ContainsBasicExamples
    {
        // 文字列での基本的な使用例
        public void DemonstrateStringContains()
        {
            // 基本的な検索
            string text = "Hello, World!";
            bool containsWorld = text.Contains("World");  // true

            // 大文字小文字を区別しない検索
            bool containsHELLO = text.Contains(
                "HELLO", 
                StringComparison.OrdinalIgnoreCase  // 常に明示的に比較方法を指定
            );  // true

            // 複数の検索条件
            bool containsMultiple = text.Contains("Hello") && 
                                  text.Contains("World");  // true
        }

        // コレクションでの基本的な使用例
        public void DemonstrateCollectionContains()
        {
            // Listでの使用
            var numbers = new List<int> { 1, 2, 3, 4, 5 };
            bool containsThree = numbers.Contains(3);  // true

            // 配列での使用(LINQを使用)
            int[] array = { 1, 2, 3, 4, 5 };
            bool containsFour = array.Contains(4);  // true

            // HashSetでの使用(最も効率的)
            var set = new HashSet<int> { 1, 2, 3, 4, 5 };
            bool containsFive = set.Contains(5);  // true
        }

        // オブジェクトコレクションでの使用例
        public void DemonstrateObjectContains()
        {
            var users = new List<User>
            {
                new User { Id = 1, Name = "John" },
                new User { Id = 2, Name = "Jane" }
            };

            var searchUser = new User { Id = 1, Name = "John" };

            // 注意: カスタムクラスではEqualsのオーバーライドが必要
            bool containsUser = users.Contains(searchUser);  // オーバーライド次第
        }
    }

    // カスタムクラスの例
    public class User : IEquatable<User>
    {
        public int Id { get; set; }
        public string Name { get; set; }

        // IEquatable<T>の実装
        public bool Equals(User other)
        {
            if (other == null) return false;
            return Id == other.Id && Name == other.Name;
        }

        // Object.Equalsのオーバーライド
        public override bool Equals(object obj)
        {
            return Equals(obj as User);
        }

        // GetHashCodeのオーバーライド(必須)
        public override int GetHashCode()
        {
            return HashCode.Combine(Id, Name);
        }
    }
}

ContainsメソッドとIndexOfメソッドの違い

各メソッドの特徴と使い分けを明確に示します。

各メソッドの特徴

public class ContainsVsIndexOf
{
    public void DemonstrateMethodDifferences()
    {
        string text = "Hello, World! Hello, Universe!";

        // Containsメソッドの使用例
        bool hasWorld = text.Contains("World");  // 単純な存在確認

        // IndexOfメソッドの使用例
        int firstHello = text.IndexOf("Hello");  // 位置の特定
        if (firstHello != -1)
        {
            int secondHello = text.IndexOf("Hello", firstHello + 1);  // 二回目の出現位置
        }

        // 使い分けの例
        public bool ContainsKeyword(string text, string keyword)
        {
            // 単純な存在確認の場合はContainsを使用
            return text.Contains(keyword, StringComparison.OrdinalIgnoreCase);
        }

        public string ExtractTextBetweenKeywords(
            string text, 
            string startKeyword, 
            string endKeyword)
        {
            // 位置情報が必要な場合はIndexOfを使用
            int start = text.IndexOf(startKeyword);
            if (start == -1) return string.Empty;

            start += startKeyword.Length;
            int end = text.IndexOf(endKeyword, start);
            if (end == -1) return string.Empty;

            return text.Substring(start, end - start);
        }
    }

    // メソッドの特徴比較
    public void CompareMethodCharacteristics(string text, string searchTerm)
    {
        // 1. Containsメソッド
        bool containsResult = text.Contains(searchTerm);
        // - 戻り値が bool型
        // - 単純な存在確認に最適
        // - より直感的なAPI

        // 2. IndexOfメソッド
        int indexOfResult = text.IndexOf(searchTerm);
        // - 戻り値が int型(位置情報)
        // - より詳細な情報が必要な場合に使用
        // - 追加の文字列操作が必要な場合に有用
    }
}

メソッドの使い分けの基準

特徴ContainsIndexOf
主な用途存在確認のみ位置特定と追加処理
戻り値boolint(位置)
使用シーン単純な検索テキスト加工
コード量少ないやや多い
エラー処理シンプルより複雑
このセクションのポイント

Containsメソッドは文字列とコレクションの両方で使用可能

文字列検索では必ずStringComparisonを明示的に指定する

カスタムクラスでは適切にIEquatable<T>を実装する

単純な存在確認にはContainsを、位置情報が必要な場合はIndexOfを使用する

データ型別Containsメソッドの活用法

文字列操作でのContainsメソッド活用術

文字列操作におけるContainsメソッドは、最も頻繁に使用される実装の一つです。

using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;

public class StringContainsUsage
{
    // 文字列検索の基本パターン
    public class SearchManager
    {
        private readonly StringComparison _comparison;

        public SearchManager(StringComparison comparison = StringComparison.OrdinalIgnoreCase)
        {
            _comparison = comparison;
        }

        // キーワード検索の実装
        public bool ContainsKeywords(string text, IEnumerable<string> keywords)
        {
            if (string.IsNullOrEmpty(text) || keywords == null)
                return false;

            // すべてのキーワードが含まれているかチェック
            return keywords.All(keyword => 
                !string.IsNullOrEmpty(keyword) && 
                text.Contains(keyword, _comparison));
        }

        // URLの検証
        public bool IsValidUrl(string url)
        {
            if (string.IsNullOrEmpty(url))
                return false;

            // 必須プロトコルの確認
            bool hasProtocol = url.Contains("http://", _comparison) || 
                             url.Contains("https://", _comparison);

            // 基本的なドメイン構造の確認
            bool hasDomain = url.Contains(".", _comparison) && 
                           !url.Contains(" ", _comparison);

            return hasProtocol && hasDomain;
        }

        // 禁止語句のチェック(高速・省メモリ実装)
        public bool ContainsProhibitedWords(
            string text, 
            IReadOnlySet<string> prohibitedWords)
        {
            if (string.IsNullOrEmpty(text))
                return false;

            // HashSetを使用して高速に検索
            return prohibitedWords.Any(word => 
                text.Contains(word, _comparison));
        }
    }
}

配列とリストでのContainsメソッドの使い方

配列やリストでのContainsメソッドは、要素の存在確認に便利です。

public class CollectionContainsUsage
{
    // コレクション操作の基本パターン
    public class CollectionManager<T>
    {
        private readonly IEqualityComparer<T> _comparer;

        public CollectionManager(IEqualityComparer<T> comparer = null)
        {
            _comparer = comparer ?? EqualityComparer<T>.Default;
        }

        // 重複を除外しながら要素を追加
        public void AddUniqueItems(
            ICollection<T> target, 
            IEnumerable<T> newItems)
        {
            if (target == null || newItems == null)
                throw new ArgumentNullException();

            // 追加前に重複チェック
            var uniqueItems = new HashSet<T>(target, _comparer);
            foreach (var item in newItems)
            {
                if (uniqueItems.Add(item))
                {
                    target.Add(item);
                }
            }
        }

        // 配列での効率的な検索
        public bool ContainsAny(T[] array, IEnumerable<T> searchItems)
        {
            if (array == null || searchItems == null)
                return false;

            // 検索要素をHashSetに変換して高速化
            var searchSet = new HashSet<T>(searchItems, _comparer);
            return array.Any(item => searchSet.Contains(item));
        }

        // リストの差分検出
        public IEnumerable<T> FindMissingItems(
            IList<T> source, 
            IEnumerable<T> required)
        {
            var sourceSet = new HashSet<T>(source, _comparer);
            return required.Where(item => !sourceSet.Contains(item));
        }
    }
}

カスタムクラスでContainsメソッドを実装する方法

カスタムクラスでContainsメソッドを効果的に使用するには、適切なインターフェースの実装が必要です。

// カスタムクラスの定義
public class Product : IEquatable<Product>
{
    public int Id { get; set; }
    public string Code { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    // 効率的な比較のためのIEquatable<T>実装
    public bool Equals(Product other)
    {
        if (other == null) return false;

        // 業務要件に応じて比較ロジックを実装
        return Id == other.Id && 
               Code == other.Code;  // IDとコードで一意性を判断
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Product);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Id, Code);
    }
}

// カスタムコレクションの実装
public class ProductInventory
{
    private readonly Dictionary<string, List<Product>> _categoryIndex;
    private readonly HashSet<Product> _products;

    public ProductInventory()
    {
        _categoryIndex = new Dictionary<string, List<Product>>(
            StringComparer.OrdinalIgnoreCase
        );
        _products = new HashSet<Product>();
    }

    // インデックスを活用した効率的な検索
    public bool Contains(Product product)
    {
        if (product == null)
            return false;

        return _products.Contains(product);
    }

    // カテゴリベースの検索
    public bool ContainsByCategory(string category, Product product)
    {
        if (string.IsNullOrEmpty(category) || product == null)
            return false;

        return _categoryIndex.TryGetValue(category, out var products) && 
               products.Contains(product);
    }

    // 複数条件での検索
    public bool ContainsByCondition(Func<Product, bool> condition)
    {
        return _products.Any(condition);
    }

    // 製品の追加(インデックスの自動更新)
    public void AddProduct(string category, Product product)
    {
        if (string.IsNullOrEmpty(category) || product == null)
            throw new ArgumentNullException();

        if (_products.Add(product))  // 重複チェックと追加を一度に実行
        {
            if (!_categoryIndex.TryGetValue(category, out var products))
            {
                products = new List<Product>();
                _categoryIndex = products;
            }
            products.Add(product);
        }
    }
}
このセクションのポイント

文字列検索では常にStringComparisonを指定し、null検査を実施

コレクションでは適切なデータ構造(HashSet、Dictionary)を選択

カスタムクラスではビジネスロジックに基づいた比較を実装

インデックスを活用して検索パフォーマンスを最適化

メモリ効率を考慮した実装を心がける

パフォーマンスを意識したContainsの使い方

大規模データでのContainsメソッドのパフォーマンス評価

Containsメソッドのパフォーマンスは、データ構造とデータサイズによって大きく異なります。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

public class ContainsPerformanceTest
{
    private readonly int _dataSize;
    private readonly int _iterations;
    private readonly Random _random = new();

    public ContainsPerformanceTest(int dataSize = 1_000_000, int iterations = 10_000)
    {
        _dataSize = dataSize;
        _iterations = iterations;
    }

    // 各データ構造のパフォーマンス比較
    public void CompareDataStructures()
    {
        // テストデータの準備
        Console.WriteLine($"データ件数: {_dataSize:N0}");
        Console.WriteLine($"検索回数: {_iterations:N0}\n");

        var numbers = Enumerable.Range(0, _dataSize).ToList();
        var searchValues = Enumerable.Range(0, _iterations)
            .Select(_ => _random.Next(0, _dataSize))
            .ToArray();

        var results = new Dictionary<string, (TimeSpan Time, long Memory)>
        {
            ["List<T>"] = MeasurePerformance(() => 
                TestListPerformance(numbers, searchValues)),
            ["Array"] = MeasurePerformance(() => 
                TestArrayPerformance(numbers.ToArray(), searchValues)),
            ["HashSet"] = MeasurePerformance(() => 
                TestHashSetPerformance(new HashSet<int>(numbers), searchValues)),
            ["SortedSet"] = MeasurePerformance(() => 
                TestSortedSetPerformance(new SortedSet<int>(numbers), searchValues))
        };

        // 結果の表示
        var baseline = results.Values.Min(r => r.Time.TotalMilliseconds);
        foreach (var result in results.OrderBy(r => r.Value.Time))
        {
            Console.WriteLine(
                $"{result.Key,-12}: " +
                $"時間 = {result.Value.Time.TotalMilliseconds,8:N1}ms " +
                $"({result.Value.Time.TotalMilliseconds / baseline,5:N1}x), " +
                $"メモリ = {result.Value.Memory / 1024,8:N0}KB"
            );
        }
    }

    // 各データ構造のテストメソッド
    private void TestListPerformance(List<int> list, int[] searchValues)
    {
        foreach (var value in searchValues)
        {
            _ = list.Contains(value);
        }
    }

    private void TestArrayPerformance(int[] array, int[] searchValues)
    {
        foreach (var value in searchValues)
        {
            _ = array.Contains(value);
        }
    }

    private void TestHashSetPerformance(HashSet<int> set, int[] searchValues)
    {
        foreach (var value in searchValues)
        {
            _ = set.Contains(value);
        }
    }

    private void TestSortedSetPerformance(SortedSet<int> set, int[] searchValues)
    {
        foreach (var value in searchValues)
        {
            _ = set.Contains(value);
        }
    }

    // パフォーマンス計測
    private (TimeSpan Time, long Memory) MeasurePerformance(Action action)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        var startMemory = GC.GetTotalMemory(true);

        var sw = Stopwatch.StartNew();
        action();
        sw.Stop();

        var endMemory = GC.GetTotalMemory(false);
        return (sw.Elapsed, endMemory - startMemory);
    }
}

HashSet活用による検索パフォーマンスの向上

HashSetを活用することで、検索パフォーマンスを大幅に向上させることができます。

public class OptimizedSearchImplementation
{
    // メモリ効率を考慮した検索の実装
    public class MemoryEfficientSearcher<T>
    {
        private readonly IEnumerable<T> _source;
        private readonly IEqualityComparer<T> _comparer;
        private HashSet<T> _searchIndex;
        private int _searchCount;
        private readonly object _lock = new();

        // 閾値を超えたらHashSetを使用
        private const int HashSetThreshold = 5;

        public MemoryEfficientSearcher(
            IEnumerable<T> source, 
            IEqualityComparer<T> comparer = null)
        {
            _source = source ?? throw new ArgumentNullException(nameof(source));
            _comparer = comparer ?? EqualityComparer<T>.Default;
        }

        public bool Contains(T item)
        {
            // 検索回数が少ない場合は直接検索
            if (_searchIndex == null)
            {
                _searchCount++;
                var contains = _source.Contains(item);

                // 検索回数が閾値を超えたらインデックスを構築
                if (_searchCount >= HashSetThreshold)
                {
                    lock (_lock)
                    {
                        if (_searchIndex == null)
                        {
                            _searchIndex = new HashSet<T>(_source, _comparer);
                        }
                    }
                }

                return contains;
            }

            return _searchIndex.Contains(item);
        }

        // メモリの解放
        public void ReleaseMemory()
        {
            lock (_lock)
            {
                _searchIndex = null;
                _searchCount = 0;
            }
        }
    }

    // 大規模データの効率的な処理
    public class BatchProcessor<T>
    {
        private readonly int _batchSize;
        private readonly IEqualityComparer<T> _comparer;

        public BatchProcessor(
            int batchSize = 10000, 
            IEqualityComparer<T> comparer = null)
        {
            _batchSize = batchSize;
            _comparer = comparer ?? EqualityComparer<T>.Default;
        }

        // バッチ処理による重複チェック
        public async Task<HashSet<T>> FindDuplicatesAsync(
            IEnumerable<T> items, 
            CancellationToken cancellationToken = default)
        {
            var uniqueItems = new HashSet<T>(_comparer);
            var duplicates = new HashSet<T>(_comparer);
            var batch = new List<T>(_batchSize);

            foreach (var item in items)
            {
                cancellationToken.ThrowIfCancellationRequested();
                batch.Add(item);

                if (batch.Count >= _batchSize)
                {
                    await ProcessBatchAsync(batch, uniqueItems, duplicates);
                    batch.Clear();
                }
            }

            if (batch.Count > 0)
            {
                await ProcessBatchAsync(batch, uniqueItems, duplicates);
            }

            return duplicates;
        }

        private Task ProcessBatchAsync(
            List<T> batch, 
            HashSet<T> uniqueItems, 
            HashSet<T> duplicates)
        {
            return Task.Run(() =>
            {
                foreach (var item in batch)
                {
                    if (!uniqueItems.Add(item))
                    {
                        lock (duplicates)
                        {
                            duplicates.Add(item);
                        }
                    }
                }
            });
        }
    }
}
このセクションのポイント

データ構造による検索パフォーマンスの違いを定量的に評価

検索頻度に応じて適切なデータ構造を選択

メモリ使用量とパフォーマンスのトレードオフを考慮

大規模データの場合はバッチ処理を活用

非同期処理とキャンセレーション対応で柔軟性を確保

パフォーマンス特性の比較

データ構造検索時間計算量メモリ使用量初期化コスト
ListO(n)
ArrayO(n)
HashSetO(1)
SortedSetO(log n)

Containsメソッドの実践的なユースケース

重複チェックの効率的な実装方法

重複チェックは実務でよく遭遇するシナリオです。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;

public class DuplicateCheckImplementation
{
    // ユーザー登録での重複チェック
    public class UserRegistrationValidator
    {
        private readonly HashSet<string> _existingEmails;
        private readonly HashSet<string> _existingUsernames;
        private readonly HashSet<string> _reservedWords;

        public UserRegistrationValidator(
            IEnumerable<string> existingEmails,
            IEnumerable<string> existingUsernames,
            IEnumerable<string> reservedWords)
        {
            // 大文字小文字を区別しない比較を使用
            var comparer = StringComparer.OrdinalIgnoreCase;
            _existingEmails = new HashSet<string>(existingEmails, comparer);
            _existingUsernames = new HashSet<string>(existingUsernames, comparer);
            _reservedWords = new HashSet<string>(reservedWords, comparer);
        }

        public (bool IsValid, List<string> Errors) ValidateNewUser(
            string email, 
            string username)
        {
            var errors = new List<string>();

            // メールアドレスの検証
            if (string.IsNullOrEmpty(email))
            {
                errors.Add("メールアドレスは必須です。");
            }
            else if (_existingEmails.Contains(email))
            {
                errors.Add("このメールアドレスは既に使用されています。");
            }

            // ユーザー名の検証
            if (string.IsNullOrEmpty(username))
            {
                errors.Add("ユーザー名は必須です。");
            }
            else
            {
                if (_existingUsernames.Contains(username))
                {
                    errors.Add("このユーザー名は既に使用されています。");
                }
                if (_reservedWords.Contains(username))
                {
                    errors.Add("このユーザー名は使用できません。");
                }
            }

            return (errors.Count == 0, errors);
        }
    }

    // ファイルシステムでの重複チェック
    public class FileSystemValidator
    {
        private readonly IEqualityComparer<string> _comparer;
        private readonly HashSet<string> _existingPaths;
        private readonly object _lock = new();

        public FileSystemValidator(StringComparison comparison = StringComparison.OrdinalIgnoreCase)
        {
            _comparer = StringComparer.FromComparison(comparison);
            _existingPaths = new HashSet<string>(_comparer);
        }

        public bool IsPathUnique(string path)
        {
            if (string.IsNullOrEmpty(path))
                throw new ArgumentNullException(nameof(path));

            lock (_lock)
            {
                return !_existingPaths.Contains(path);
            }
        }

        public string GenerateUniquePath(string basePath)
        {
            if (string.IsNullOrEmpty(basePath))
                throw new ArgumentNullException(nameof(basePath));

            lock (_lock)
            {
                if (!_existingPaths.Contains(basePath))
                {
                    _existingPaths.Add(basePath);
                    return basePath;
                }

                string directory = Path.GetDirectoryName(basePath);
                string fileName = Path.GetFileNameWithoutExtension(basePath);
                string extension = Path.GetExtension(basePath);
                int counter = 1;

                while (true)
                {
                    string newPath = Path.Combine(
                        directory, 
                        $"{fileName}({counter}){extension}"
                    );

                    if (!_existingPaths.Contains(newPath))
                    {
                        _existingPaths.Add(newPath);
                        return newPath;
                    }

                    counter++;
                }
            }
        }
    }
}

部分文字列検索の最適な実装パターン

部分文字列検索では、大文字小文字の区別や特殊文字の処理など、さまざまな考慮が必要です。

public class SubstringSearchImplementation
{
    // 検索エンジンの実装
    public class SearchEngine
    {
        private readonly Dictionary<string, SearchIndex> _indices;
        private readonly StringComparison _comparison;

        public SearchEngine(StringComparison comparison = StringComparison.OrdinalIgnoreCase)
        {
            _indices = new Dictionary<string, SearchIndex>();
            _comparison = comparison;
        }

        // インデックスの作成
        public void CreateIndex(string category, IEnumerable<string> documents)
        {
            if (string.IsNullOrEmpty(category) || documents == null)
                throw new ArgumentNullException();

            var index = new SearchIndex(_comparison);
            foreach (var doc in documents)
            {
                index.AddDocument(doc);
            }

            _indices = index;
        }

        // カテゴリ内での検索
        public IEnumerable<string> Search(string category, string searchTerm)
        {
            if (!_indices.TryGetValue(category, out var index))
                return Enumerable.Empty<string>();

            return index.Search(searchTerm);
        }

        // 全カテゴリでの検索
        public Dictionary<string, IEnumerable<string>> SearchAll(string searchTerm)
        {
            return _indices.ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.Search(searchTerm)
            );
        }

        // インデックスクラス
        private class SearchIndex
        {
            private readonly HashSet<string> _documents;
            private readonly Dictionary<string, HashSet<string>> _wordIndex;
            private readonly StringComparison _comparison;

            public SearchIndex(StringComparison comparison)
            {
                _documents = new HashSet<string>();
                _wordIndex = new Dictionary<string, HashSet<string>>();
                _comparison = comparison;
            }

            public void AddDocument(string document)
            {
                if (string.IsNullOrEmpty(document))
                    return;

                _documents.Add(document);
                var words = document.Split(new[] { ' ', '.', ',', '!', '?' }, 
                    StringSplitOptions.RemoveEmptyEntries);

                foreach (var word in words)
                {
                    var normalizedWord = word.Trim().ToLower();
                    if (!_wordIndex.TryGetValue(normalizedWord, out var docs))
                    {
                        docs = new HashSet<string>();
                        _wordIndex[normalizedWord] = docs;
                    }
                    docs.Add(document);
                }
            }

            public IEnumerable<string> Search(string searchTerm)
            {
                if (string.IsNullOrEmpty(searchTerm))
                    return Enumerable.Empty<string>();

                // 完全一致検索
                if (_wordIndex.TryGetValue(searchTerm.ToLower(), out var exactMatches))
                    return exactMatches;

                // 部分一致検索
                return _documents.Where(doc => 
                    doc.Contains(searchTerm, _comparison));
            }
        }
    }
}

大文字小文字を区別しない検索の実装テクニック

大文字小文字を区別しない検索は、ユーザー入力を扱う際によく必要となります。

public class CaseInsensitiveSearchImplementation
{
    // 多言語対応の検索エンジン
    public class MultilingualSearchEngine
    {
        private readonly bool _ignoreCase;
        private readonly bool _ignoreAccents;
        private readonly HashSet<string> _searchIndex;

        public MultilingualSearchEngine(
            bool ignoreCase = true,
            bool ignoreAccents = true)
        {
            _ignoreCase = ignoreCase;
            _ignoreAccents = ignoreAccents;
            _searchIndex = new HashSet<string>(
                StringComparer.FromComparison(
                    ignoreCase ? StringComparison.OrdinalIgnoreCase 
                              : StringComparison.Ordinal
                )
            );
        }

        public void AddText(string text)
        {
            if (string.IsNullOrEmpty(text))
                return;

            var normalizedText = NormalizeText(text);
            _searchIndex.Add(normalizedText);
        }

        public bool Contains(string searchTerm)
        {
            if (string.IsNullOrEmpty(searchTerm))
                return false;

            var normalizedTerm = NormalizeText(searchTerm);
            return _searchIndex.Contains(normalizedTerm);
        }

        private string NormalizeText(string text)
        {
            if (string.IsNullOrEmpty(text))
                return text;

            if (_ignoreCase)
                text = text.ToLowerInvariant();

            if (_ignoreAccents)
            {
                text = text.Normalize(NormalizationForm.FormD);
                var chars = text.Where(c => 
                    CharUnicodeInfo.GetUnicodeCategory(c) != 
                    UnicodeCategory.NonSpacingMark
                ).ToArray();
                text = new string(chars).Normalize(NormalizationForm.FormC);
            }

            return text;
        }
    }
}
このセクションのポイント

ユーザー登録では大文字小文字を区別しない比較を使用

ファイルシステム操作ではスレッドセーフな実装を心がける

検索エンジンではインデックスを活用して検索を最適化

多言語対応では文字列の正規化を適切に実装

メモリ使用量と検索パフォーマンスのバランスを考慮

Containsメソッドのベストプラクティスとアンチパターン

メモリ使用量を最適化するためのテクニック

Containsメソッドを使用する際のメモリ最適化は、特に大規模なデータセットを扱う場合に重要です。

using System;
using System.Collections.Generic;
using System.Linq;

public class MemoryOptimizationExamples
{
    // メモリ効率の良い実装パターン
    public class MemoryEfficientImplementation<T>
    {
        private readonly IEnumerable<T> _source;
        private readonly IEqualityComparer<T> _comparer;
        private WeakReference<HashSet<T>> _cachedSet;
        private readonly object _lock = new();

        public MemoryEfficientImplementation(
            IEnumerable<T> source,
            IEqualityComparer<T> comparer = null)
        {
            _source = source ?? throw new ArgumentNullException(nameof(source));
            _comparer = comparer ?? EqualityComparer<T>.Default;
        }

        // メモリ効率を考慮したContains実装
        public bool Contains(T item)
        {
            // キャッシュの取得を試みる
            if (_cachedSet != null && 
                _cachedSet.TryGetTarget(out var hashSet))
            {
                return hashSet.Contains(item);
            }

            // キャッシュがない場合は直接検索
            lock (_lock)
            {
                // 二重チェック
                if (_cachedSet != null && 
                    _cachedSet.TryGetTarget(out hashSet))
                {
                    return hashSet.Contains(item);
                }

                // 新しいHashSetを作成
                hashSet = new HashSet<T>(_source, _comparer);
                _cachedSet = new WeakReference<HashSet<T>>(hashSet);
                return hashSet.Contains(item);
            }
        }

        // メモリ解放のためのキャッシュクリア
        public void ClearCache()
        {
            lock (_lock)
            {
                _cachedSet = null;
            }
        }
    }

    // アンチパターン:メモリリークの可能性
    public class MemoryLeakExample
    {
        // 問題のある実装
        private readonly Dictionary<string, HashSet<string>> _cache 
            = new(StringComparer.OrdinalIgnoreCase);

        // 問題点:キャッシュが無制限に増加する
        public bool ContainsBad(string category, string item)
        {
            if (!_cache.TryGetValue(category, out var items))
            {
                items = LoadItems(category);  // データ読み込み
                _cache = items;     // 無制限にキャッシュ
            }
            return items.Contains(item);
        }

        // 改善された実装
        private readonly LimitedCache<string, HashSet<string>> _limitedCache 
            = new(maxItems: 100);

        public bool ContainsGood(string category, string item)
        {
            if (!_limitedCache.TryGetValue(category, out var items))
            {
                items = LoadItems(category);
                _limitedCache.Add(category, items);  // キャッシュサイズを制限
            }
            return items.Contains(item);
        }

        private HashSet<string> LoadItems(string category)
        {
            // データ読み込みの実装
            return new HashSet<string>();
        }
    }
}

避けるべき一般的な実装ミス

Containsメソッドを使用する際によく見られる実装ミスと、その対処法を示します。

public class CommonMistakesExample
{
    // アンチパターン1:不適切な文字列比較
    public class StringComparisonMistakes
    {
        // 問題のある実装
        public bool ContainsBad(string text, string searchTerm)
        {
            // 問題点1:新しい文字列オブジェクトを作成
            // 問題点2:カルチャーに依存する可能性
            return text.ToLower().Contains(searchTerm.ToLower());
        }

        // 改善された実装
        public bool ContainsGood(string text, string searchTerm)
        {
            if (text == null || searchTerm == null)
                return false;

            return text.Contains(
                searchTerm, 
                StringComparison.OrdinalIgnoreCase
            );
        }
    }

    // アンチパターン2:非効率な検索
    public class InefficientSearch
    {
        // 問題のある実装
        public bool ContainsBad<T>(IEnumerable<T> items, T searchItem)
        {
            // 問題点:毎回リストを線形検索
            return items.ToList().Contains(searchItem);
        }

        // 改善された実装
        public bool ContainsGood<T>(IEnumerable<T> items, T searchItem)
        {
            // 検索頻度に応じてHashSetを使用
            if (items is ICollection<T> collection)
            {
                if (collection.Count > 100)  // 要素数が多い場合
                {
                    return new HashSet<T>(items).Contains(searchItem);
                }
            }
            return items.Contains(searchItem);
        }
    }

    // アンチパターン3:スレッドセーフティの欠如
    public class ThreadSafetyIssues
    {
        // 問題のある実装
        private HashSet<string> _items = new();

        public bool ContainsBad(string item)
        {
            // 問題点:スレッドセーフティなし
            return _items.Contains(item);
        }

        // 改善された実装
        private readonly HashSet<string> _safeItems = new();
        private readonly object _lock = new();

        public bool ContainsGood(string item)
        {
            lock (_lock)
            {
                return _safeItems.Contains(item);
            }
        }
    }
}

ユニットテストでの効果的な使用方法

Containsメソッドのテストは、様々なエッジケースを考慮する必要があります。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;

[TestClass]
public class ContainsMethodTests
{
    // 文字列検索のテスト
    [TestClass]
    public class StringContainsTests
    {
        [TestMethod]
        [DataRow("Hello, World!", "World", true)]
        [DataRow("Hello, World!", "Python", false)]
        [DataRow("Hello, World!", "world", true)]  // 大文字小文字を区別しない
        public void Contains_BasicScenarios(
            string text, 
            string searchTerm, 
            bool expected)
        {
            // Act
            var actual = text.Contains(
                searchTerm, 
                StringComparison.OrdinalIgnoreCase
            );

            // Assert
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Contains_WithNullInput_ThrowsArgumentNullException()
        {
            // Arrange
            string text = "test";

            // Act & Assert
            Assert.ThrowsException<ArgumentNullException>(() => 
                text.Contains(null));
        }

        [TestMethod]
        public void Contains_WithEmptyString_ReturnsTrue()
        {
            // Arrange
            string text = "test";

            // Act
            bool result = text.Contains(string.Empty);

            // Assert
            Assert.IsTrue(result);
        }
    }

    // コレクション操作のテスト
    [TestClass]
    public class CollectionContainsTests
    {
        private static readonly IEqualityComparer<string> Comparer = 
            StringComparer.OrdinalIgnoreCase;

        [TestMethod]
        public void Contains_WithHashSet_PerformsEfficiently()
        {
            // Arrange
            const int itemCount = 1_000_000;
            var list = new List<int>();
            for (int i = 0; i < itemCount; i++)
                list.Add(i);

            var hashSet = new HashSet<int>(list);

            // Act
            var sw = System.Diagnostics.Stopwatch.StartNew();
            bool listResult = list.Contains(itemCount - 1);
            var listTime = sw.ElapsedTicks;

            sw.Restart();
            bool hashSetResult = hashSet.Contains(itemCount - 1);
            var hashSetTime = sw.ElapsedTicks;

            // Assert
            Assert.IsTrue(listResult);
            Assert.IsTrue(hashSetResult);
            Assert.IsTrue(hashSetTime < listTime);
        }

        [TestMethod]
        public void Contains_WithCustomComparer_WorksAsExpected()
        {
            // Arrange
            var items = new HashSet<string>(Comparer) { "Test" };

            // Act & Assert
            Assert.IsTrue(items.Contains("TEST"));
            Assert.IsTrue(items.Contains("test"));
            Assert.IsFalse(items.Contains("other"));
        }
    }
}
このセクションのポイント
  1. メモリ最適化
    • WeakReferenceを使用してメモリ使用量を制御
    • キャッシュサイズを適切に制限
    • 必要に応じてメモリを解放
  2. 一般的なミスの回避
    • 文字列比較は必ずStringComparisonを指定
    • 大規模データには適切なデータ構造を選択
    • スレッドセーフティを確保
  3. テストの実装
    • エッジケースを含む包括的なテスト
    • パフォーマンステストの実装
    • カスタムComparerのテスト

このセクションで示したベストプラクティスとアンチパターンを意識することで、より効率的で保守性の高いコードを実装できます。

Containsメソッドのまとめ

Containsメソッドは、適切なデータ構造の選択とパフォーマンスの考慮、そして正しいエラー処理を組み合わせることで、その真価を発揮します。
特に大規模なデータを扱う場合や、高頻度な検索が必要な場合は、本記事で紹介した最適化手法を活用することで、より効率的なアプリケーションの実装が可能となります。

この記事の主なポイント
  1. データ構造の選択
    • 検索頻度が高い場合はHashSetを使用
    • 少量のデータではList/配列で十分
    • メモリ使用量とパフォーマンスのバランスを考慮
  2. 実装上の注意点
    • 文字列比較は必ずStringComparisonを指定
    • 適切なエラー処理とnullチェックを実装
    • スレッドセーフティを確保
  3. パフォーマンス最適化
    • 遅延初期化による効率化
    • メモリ使用量の適切な管理
    • 大規模データのバッチ処理
  4. 実装のベストプラクティス
    • カスタムクラスではIEquatable<T>を実装
    • 検索条件に応じた適切なインデックス作成
    • 包括的なユニットテストの実装