C# CompareToの使い方を完全解説|戻り値・Sortでの並び替え・Equalsとの違い
はじめに
C#で値を比較するとき、==やEqualsだけでなく、CompareToというメソッドを使う場面があります。CompareToは「同じかどうか」だけでなく、「どちらが前に来るか」「どちらが大きいか」「どの順番で並べるか」を判定するためのメソッドです。
特に、List<T>.Sort()やArray.Sort()で並び替えを行うとき、CompareToの考え方を理解しているかどうかで、コードの読みやすさやバグの少なさが大きく変わります。CompareToは、IComparableやIComparable<T>と深く関係しており、独自クラスを自然な順序でソートしたい場合にも重要です。Microsoftの公式ドキュメントでも、IComparable<T>.CompareToはジェネリックコレクションの要素を順序付けするための比較メソッドとして説明されています。
この記事では、C# CompareToの基本、戻り値の意味、Sortでの使い方、Equalsとの違い、独自クラスでの実装方法まで、初心者にもわかりやすく解説します。
1. C#のCompareToとは?基本の役割をわかりやすく解説
1-1. CompareToは「大小関係」や「並び順」を比較するメソッド
CompareToは、現在のオブジェクトと別のオブジェクトを比較し、順序関係をintで返すメソッドです。
たとえば、数値であれば「5は10より小さい」、日付であれば「2024年1月1日は2024年12月31日より前」、文字列であれば「appleはbananaより前」といった比較を行えます。
基本形は次のようになります。
C#int result = value1.CompareTo(value2);
戻り値は次のように解釈します。
C#if (result < 0)
{
// value1 は value2 より前、または小さい
}
else if (result == 0)
{
// value1 と value2 は同じ順序
}
else
{
// value1 は value2 より後、または大きい
}
CompareToは「等しいかどうか」だけを見るものではなく、並び順を決めるための比較メソッドだと考えると理解しやすくなります。
1-2. CompareToがよく使われる場面
CompareToがよく使われるのは、次のような場面です。
| 使用場面 | 例 |
|---|---|
| 数値の大小比較 | 10.CompareTo(20) |
| 文字列の並び順比較 | "apple".CompareTo("banana") |
| 日付の前後比較 | date1.CompareTo(date2) |
| リストのソート | list.Sort() |
| 独自クラスの自然な並び順定義 | Person.CompareTo(Person other) |
特に重要なのはソートです。List<T>.Sort()は、既定の比較子や指定された比較ロジックを使ってリストの要素を並び替えます。MicrosoftのList<T>.Sortのドキュメントでも、既定または指定されたIComparer<T>、あるいはComparison<T>を使って要素を比較すると説明されています。
1-3. CompareToとIComparableの関係
CompareToは、IComparableまたはIComparable<T>インターフェースで定義されるメソッドです。
現在よく使われるのは、型安全なIComparable<T>です。
C#public interface IComparable<T>
{
int CompareTo(T other);
}
たとえば、Personクラスを年齢順に並べたい場合、次のようにIComparable<Person>を実装します。
C#public class Person : IComparable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public int CompareTo(Person other)
{
if (other == null) return 1;
return Age.CompareTo(other.Age);
}
}
このように実装しておくと、List<Person>に対してSort()を呼び出したとき、Ageを基準に並び替えられます。
C#var people = new List<Person>
{
new Person { Name = "Sato", Age = 30 },
new Person { Name = "Tanaka", Age = 25 },
new Person { Name = "Suzuki", Age = 40 }
};
people.Sort();
1-4. CompareToを理解するうえで重要な「自然順序」とは
CompareToを理解するときは、「自然順序」という考え方が重要です。
自然順序とは、その型にとってもっとも自然だと考えられる並び順のことです。
たとえば、次のようなイメージです。
| 型 | 自然順序の例 |
|---|---|
int | 小さい数から大きい数 |
double | 小さい値から大きい値 |
DateTime | 古い日時から新しい日時 |
string | 文字列の並び順 |
Person | 年齢順、社員番号順など設計次第 |
独自クラスでは、何を自然順序にするかを開発者が決めます。社員クラスなら社員番号順、商品クラスなら価格順、注文クラスなら注文日時順など、クラスの用途に合った基準を選ぶことが大切です。
2. C# CompareToの戻り値の意味
2-1. 戻り値が0の場合
CompareToの戻り値が0の場合、現在の値と比較対象は「同じ順序」と判断されます。
C#int result = 10.CompareTo(10);
Console.WriteLine(result); // 0
この場合、10と10は同じ値なので、戻り値は0です。
文字列でも同様です。
C#int result = "apple".CompareTo("apple");
Console.WriteLine(result); // 0
ただし、CompareToの0は「並び順として同じ位置」という意味です。必ずしもEqualsで完全に等しいという意味になるとは限らない点に注意が必要です。
2-2. 戻り値が負の数の場合
戻り値が負の数の場合、現在の値は比較対象より「前」または「小さい」と判断されます。
C#int result = 5.CompareTo(10);
Console.WriteLine(result); // 負の数
この例では、5は10より小さいため、戻り値は負の数です。
文字列の場合も、並び順で前に来ると負の数になります。
C#int result = "apple".CompareTo("banana");
Console.WriteLine(result); // 負の数
2-3. 戻り値が正の数の場合
戻り値が正の数の場合、現在の値は比較対象より「後」または「大きい」と判断されます。
C#int result = 20.CompareTo(10);
Console.WriteLine(result); // 正の数
この例では、20は10より大きいため、戻り値は正の数です。
日付でも同じ考え方です。
C#var date1 = new DateTime(2024, 12, 31);
var date2 = new DateTime(2024, 1, 1);
int result = date1.CompareTo(date2);
Console.WriteLine(result); // 正の数
date1はdate2より後の日付なので、戻り値は正の数になります。
2-4. 戻り値は「-1・0・1」と決め打ちしないほうがよい理由
CompareToの戻り値は、負の数、0、正の数のどれかです。重要なのは「負か、0か、正か」であり、必ず-1、0、1になるとは限りません。
たとえば、次のような判定は避けたほうが安全です。
C#// 避けたほうがよい例
if (a.CompareTo(b) == -1)
{
Console.WriteLine("aはbより小さい");
}
実装によっては、-1ではなく-2や-100など、別の負の数が返る可能性があります。公式ドキュメントでも、CompareToの戻り値は「0未満」「0」「0より大きい」という区分で説明されています。
正しくは、次のように判定します。
C#if (a.CompareTo(b) < 0)
{
Console.WriteLine("aはbより小さい");
}
2-5. CompareToの戻り値をif文で判定する基本パターン
CompareToを使うときの基本パターンは次のとおりです。
C#int result = a.CompareTo(b);
if (result < 0)
{
Console.WriteLine("aはbより小さい、または前に来る");
}
else if (result == 0)
{
Console.WriteLine("aとbは同じ順序");
}
else
{
Console.WriteLine("aはbより大きい、または後に来る");
}
この形を覚えておけば、数値、文字列、日付、独自クラスのどれでも同じ考え方で扱えます。
3. CompareToの基本的な使い方
3-1. intなど数値型でCompareToを使う方法
数値型のCompareToは、大小比較を行います。
C#int x = 100;
int y = 200;
int result = x.CompareTo(y);
if (result < 0)
{
Console.WriteLine("xはyより小さい");
}
else if (result == 0)
{
Console.WriteLine("xとyは等しい");
}
else
{
Console.WriteLine("xはyより大きい");
}
出力結果は次のようになります。
xはyより小さい
数値の比較だけなら<や>でも十分ですが、ソート処理や共通化された比較処理ではCompareToが便利です。
C#var numbers = new List<int> { 3, 1, 5, 2, 4 };
numbers.Sort();
Console.WriteLine(string.Join(", ", numbers));
// 1, 2, 3, 4, 5
3-2. stringでCompareToを使う方法
string.CompareToは、文字列の並び順を比較します。
C#string a = "apple";
string b = "banana";
int result = a.CompareTo(b);
if (result < 0)
{
Console.WriteLine($"{a} は {b} より前に来ます");
}
出力例は次のとおりです。
apple は banana より前に来ます
ただし、文字列の比較ではカルチャ、大文字小文字、日本語、記号などの影響を受けることがあります。string.CompareToはカルチャ依存かつ大文字小文字を区別する比較であり、StringComparisonを指定できるオーバーロードはありません。明示的な比較ルールが必要な場合は、string.Compareやstring.EqualsでStringComparisonを指定するのが安全です。
3-3. DateTimeでCompareToを使う方法
DateTime.CompareToは、日時の前後を比較します。
C#DateTime today = new DateTime(2024, 5, 1);
DateTime deadline = new DateTime(2024, 5, 10);
int result = today.CompareTo(deadline);
if (result < 0)
{
Console.WriteLine("期限前です");
}
else if (result == 0)
{
Console.WriteLine("期限当日です");
}
else
{
Console.WriteLine("期限を過ぎています");
}
出力結果は次のようになります。
期限前です
日付の比較では、CompareToを使うことで「前」「同じ」「後」の3パターンを明確に分岐できます。
3-4. CompareToを使った条件分岐のサンプルコード
次の例では、テストの点数を比較して結果を表示します。
C#int myScore = 85;
int averageScore = 70;
int comparison = myScore.CompareTo(averageScore);
if (comparison > 0)
{
Console.WriteLine("平均点より高いです");
}
else if (comparison == 0)
{
Console.WriteLine("平均点と同じです");
}
else
{
Console.WriteLine("平均点より低いです");
}
出力結果は次のようになります。
平均点より高いです
このように、CompareToは単純な大小比較にも使えますが、特にソート処理や汎用的な比較処理で力を発揮します。
3-5. CompareToで比較できる型・できない型
CompareToで比較できるのは、基本的にIComparableまたはIComparable<T>を実装している型です。
代表的な型には次のようなものがあります。
| 型 | CompareToの意味 |
|---|---|
int | 数値の大小 |
double | 数値の大小 |
decimal | 数値の大小 |
string | 文字列の並び順 |
DateTime | 日時の前後 |
TimeSpan | 時間間隔の大小 |
enum | 列挙値の基になる数値順 |
一方、独自クラスはそのままでは比較できない場合があります。
C#public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
このProductをそのままSort()しようとすると、どのプロパティで並べればよいのかC#側には判断できません。
C#var products = new List<Product>
{
new Product { Name = "A", Price = 100 },
new Product { Name = "B", Price = 50 }
};
products.Sort(); // 比較方法がないためエラーになる可能性がある
独自クラスを自然順序で並べたい場合は、IComparable<T>を実装するか、Sortに比較処理を渡します。
4. string.CompareToの使い方と注意点
4-1. 文字列のCompareToは辞書順・並び順を比較する
string.CompareToは、文字列が並び順としてどちらが前に来るかを比較します。
C#Console.WriteLine("apple".CompareTo("banana")); // 負の数
Console.WriteLine("apple".CompareTo("apple")); // 0
Console.WriteLine("banana".CompareTo("apple")); // 正の数
イメージとしては辞書順に近いですが、単純なUnicodeコードポイントの比較だけではありません。現在のカルチャに基づく比較になるため、環境や文化圏によって結果の解釈に注意が必要です。
4-2. 大文字・小文字の違いによる比較結果
string.CompareToは大文字と小文字を区別します。
C#string a = "apple";
string b = "Apple";
int result = a.CompareTo(b);
Console.WriteLine(result);
この結果は、単純に「人間が見たアルファベット順」と同じになるとは限りません。CompareToは現在のカルチャの影響を受けるためです。
大文字小文字を無視して比較したい場合は、CompareToではなく、string.CompareにStringComparison.OrdinalIgnoreCaseなどを指定する方法が適しています。
C#int result = string.Compare(
"apple",
"Apple",
StringComparison.OrdinalIgnoreCase
);
Console.WriteLine(result); // 0
.NETの文字列比較のベストプラクティスでは、比較ルールを明示するオーバーロードを使うこと、カルチャ非依存の比較ではStringComparison.OrdinalまたはStringComparison.OrdinalIgnoreCaseを安全な既定として使うことが推奨されています。
4-3. 日本語や英字を比較するときの注意点
日本語を含む文字列をCompareToで比較する場合、期待した順番にならないことがあります。
C#var words = new List<string>
{
"さくら",
"サクラ",
"桜",
"あおい"
};
words.Sort();
foreach (var word in words)
{
Console.WriteLine(word);
}
日本語では、ひらがな、カタカナ、漢字、濁点、長音などが絡むため、「自分が思う五十音順」と一致しないことがあります。
また、英字でも、カルチャによって並び順が変わる可能性があります。検索、認証、ID比較、ファイル名比較など、文化圏に依存させたくない比較では、StringComparison.Ordinalを使うほうが適しています。
4-4. 文字列比較でStringComparisonを使うべきケース
次のようなケースでは、CompareToよりもStringComparisonを指定できるメソッドを使うべきです。
| ケース | 推奨例 |
|---|---|
| 大文字小文字を無視したい | StringComparison.OrdinalIgnoreCase |
| IDやキーを比較したい | StringComparison.Ordinal |
| ユーザー表示向けに文化圏を考慮したい | StringComparison.CurrentCulture |
| 固定の文化圏で比較したい | StringComparison.InvariantCulture |
| 等価判定をしたい | string.Equals |
例として、ログインIDの比較なら次のように書けます。
C#bool isSameUser = string.Equals(
inputUserId,
storedUserId,
StringComparison.OrdinalIgnoreCase
);
一方、文字列を並び替える目的なら、string.Compareを使います。
C#int result = string.Compare(
name1,
name2,
StringComparison.CurrentCulture
);
StringComparison列挙型は、現在のカルチャ、不変カルチャ、序数比較、大文字小文字の区別など、文字列比較のルールを指定するために使われます。
4-5. string.CompareToとstring.Compareの違い
string.CompareToとstring.Compareはどちらも文字列の順序比較に使えますが、使い勝手が異なります。
| 比較方法 | 特徴 |
|---|---|
a.CompareTo(b) | インスタンスメソッド。現在の文字列と対象文字列を比較する |
string.Compare(a, b) | 静的メソッド。2つの文字列を比較する |
string.Compare(a, b, comparisonType) | 比較ルールを明示できる |
CompareToは簡潔に書けます。
C#int result = "apple".CompareTo("banana");
一方、比較ルールを明示したい場合はstring.Compareが適しています。
C#int result = string.Compare(
"apple",
"Apple",
StringComparison.OrdinalIgnoreCase
);
実務では、文字列比較の意図を明確にするため、StringComparisonを指定できるstring.Compareやstring.Equalsを選ぶほうが安全です。
5. CompareToをSortで使って並び替える方法
5-1. List<T>.Sortで昇順に並び替える
List<T>.Sort()は、要素の既定の比較方法を使って昇順に並び替えます。
C#var numbers = new List<int> { 5, 2, 8, 1, 3 };
numbers.Sort();
Console.WriteLine(string.Join(", ", numbers));
出力結果は次のようになります。
1, 2, 3, 5, 8
intは比較方法を持っているため、そのままSort()できます。
文字列も同様です。
C#var fruits = new List<string> { "banana", "apple", "orange" };
fruits.Sort();
Console.WriteLine(string.Join(", ", fruits));
出力例は次のとおりです。
apple, banana, orange
5-2. CompareToを使って降順に並び替える
降順にしたい場合は、比較の向きを逆にします。
C#var numbers = new List<int> { 5, 2, 8, 1, 3 };
numbers.Sort((a, b) => b.CompareTo(a));
Console.WriteLine(string.Join(", ", numbers));
出力結果は次のようになります。
8, 5, 3, 2, 1
昇順は次の形です。
C#numbers.Sort((a, b) => a.CompareTo(b));
降順は左右を入れ替えます。
C#numbers.Sort((a, b) => b.CompareTo(a));
このパターンは数値、日付、文字列などでよく使われます。
5-3. Array.SortでCompareToを使う方法
配列を並び替える場合は、Array.Sort()を使います。
C#int[] numbers = { 4, 1, 3, 2 };
Array.Sort(numbers);
Console.WriteLine(string.Join(", ", numbers));
出力結果は次のようになります。
1, 2, 3, 4
Array.Sortは、要素のIComparableまたはIComparable<T>の実装を使って並び替えられます。公式ドキュメントでも、配列全体を各要素のIComparable実装、またはジェネリック配列ではIComparable<T>実装を使ってソートするオーバーロードが説明されています。
独自の比較ロジックを指定することもできます。
C#string[] names = { "Sato", "Tanaka", "Abe" };
Array.Sort(names, (a, b) => b.CompareTo(a));
Console.WriteLine(string.Join(", ", names));
降順で並び替えたい場合にも、CompareToの向きを逆にするだけです。
5-4. OrderBy・OrderByDescendingとの使い分け
C#では、Sort()以外にLINQのOrderByやOrderByDescendingもよく使います。
C#var numbers = new List<int> { 5, 2, 8, 1, 3 };
var sorted = numbers.OrderBy(x => x).ToList();
var sortedDesc = numbers.OrderByDescending(x => x).ToList();
Sort()とOrderByの違いは次のとおりです。
| 方法 | 特徴 |
|---|---|
Sort() | 元のリスト自体を並び替える |
OrderBy() | 並び替えた新しいシーケンスを返す |
OrderByDescending() | 降順の新しいシーケンスを返す |
Sort()は破壊的変更、OrderByは非破壊的な並び替えと考えるとわかりやすいです。
C#var original = new List<int> { 3, 1, 2 };
var sorted = original.OrderBy(x => x).ToList();
Console.WriteLine(string.Join(", ", original)); // 3, 1, 2
Console.WriteLine(string.Join(", ", sorted)); // 1, 2, 3
LINQの多くのメソッドは、結果が列挙されるまで実際にはデータを使わない遅延実行の性質を持つため、ToList()などで明示的に評価する場面もあります。
5-5. CompareToをSortで使うときのよくあるエラー
Sort()でよくあるエラーは、比較対象の型が比較方法を持っていないケースです。
C#public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
var products = new List<Product>
{
new Product { Name = "A", Price = 100 },
new Product { Name = "B", Price = 50 }
};
products.Sort(); // 比較方法がない
この場合は、次のように比較処理を指定します。
C#products.Sort((a, b) => a.Price.CompareTo(b.Price));
または、ProductクラスにIComparable<Product>を実装します。
C#public class Product : IComparable<Product>
{
public string Name { get; set; }
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
Array.Sortでは、比較子がnullで配列の要素がIComparableを実装していない場合、InvalidOperationExceptionが発生する可能性があると説明されています。
6. 独自クラスでCompareToを実装する方法
6-1. IComparable<T>を実装する基本手順
独自クラスでCompareToを使えるようにするには、IComparable<T>を実装します。
基本手順は次のとおりです。
クラスに
IComparable<T>を追加するCompareToメソッドを実装する比較基準となるプロパティを決める
nullの場合の扱いを決める戻り値は
負の数・0・正の数のルールに従う
例を見てみましょう。
C#public class Product : IComparable<Product>
{
public string Name { get; set; }
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
この例では、Productの自然順序を「価格が安い順」と定義しています。
6-2. 1つのプロパティで比較するサンプルコード
商品を価格順に並べる例です。
C#public class Product : IComparable<Product>
{
public string Name { get; set; }
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
使い方は次のとおりです。
C#var products = new List<Product>
{
new Product { Name = "Keyboard", Price = 5000 },
new Product { Name = "Mouse", Price = 2000 },
new Product { Name = "Monitor", Price = 30000 }
};
products.Sort();
foreach (var product in products)
{
Console.WriteLine($"{product.Name}: {product.Price}");
}
出力結果は次のようになります。
Mouse: 2000
Keyboard: 5000
Monitor: 30000
CompareToを実装しておくと、呼び出し側は単にSort()するだけで自然順序に従って並び替えられます。
6-3. 複数条件で比較するサンプルコード
実務では、1つのプロパティだけでなく複数条件で並び替えたいことがあります。
たとえば、商品を「カテゴリ順、同じカテゴリなら価格順」で並べたい場合です。
C#public class Product : IComparable<Product>
{
public string Category { get; set; }
public string Name { get; set; }
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
int categoryResult = string.Compare(
Category,
other.Category,
StringComparison.Ordinal
);
if (categoryResult != 0)
{
return categoryResult;
}
return Price.CompareTo(other.Price);
}
}
このように、最初の条件で差があればその結果を返し、同じ場合だけ次の条件で比較します。
さらに名前順も加えるなら、次のように書けます。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
int categoryResult = string.Compare(Category, other.Category, StringComparison.Ordinal);
if (categoryResult != 0) return categoryResult;
int priceResult = Price.CompareTo(other.Price);
if (priceResult != 0) return priceResult;
return string.Compare(Name, other.Name, StringComparison.Ordinal);
}
複数条件の比較では、条件の順番がそのまま並び替えの優先順位になります。
6-4. nullを比較する場合の実装方法
CompareToを実装するときは、nullとの比較をどう扱うかを決めておく必要があります。
一般的には、「現在のオブジェクトはnullより大きい」として扱うことが多いです。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
IComparable.CompareToの公式ドキュメントでも、定義上、任意のオブジェクトはnullより大きく、2つのnull参照は等しいと説明されています。
ただし、Sort((a, b) => ...)のように比較デリゲートを書く場合は、a側もnullになる可能性があります。その場合は両方をチェックします。
C#products.Sort((a, b) =>
{
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
return a.Price.CompareTo(b.Price);
});
6-5. IComparableとIComparable<T>の違い
IComparableとIComparable<T>の違いは、比較対象の型が明確かどうかです。
| インターフェース | メソッド | 特徴 |
|---|---|---|
IComparable | CompareTo(object obj) | 古い形式。型チェックやキャストが必要 |
IComparable<T> | CompareTo(T other) | 型安全。現在はこちらが推奨されることが多い |
IComparableを使う例です。
C#public class Product : IComparable
{
public int Price { get; set; }
public int CompareTo(object obj)
{
if (obj == null) return 1;
Product other = obj as Product;
if (other == null)
{
throw new ArgumentException("比較対象がProductではありません。");
}
return Price.CompareTo(other.Price);
}
}
IComparable<T>なら、キャストが不要でシンプルです。
C#public class Product : IComparable<Product>
{
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
新しくコードを書くなら、基本的にはIComparable<T>を優先するとよいでしょう。
6-6. CompareToを実装するときの設計ルール
CompareToを実装するときは、次のルールを意識しましょう。
| ルール | 内容 |
|---|---|
| 一貫性を保つ | 同じ2つの値を比較したら、常に同じ結果を返す |
| 自分自身との比較は0 | x.CompareTo(x)は必ず0 |
| 比較方向を逆転させない | x < yならy > xになるようにする |
nullの扱いを決める | nullを小さいとするか、大きいとするか明確にする |
Equalsとの関係を考える | CompareToが0のとき、等価とみなすか設計する |
特に、x.CompareTo(x)が0を返さないような比較ロジックは危険です。Array.Sortのドキュメントでも、比較子の実装に問題がある場合、たとえば自分自身との比較で0を返さない場合にソート中のエラー原因になり得ると説明されています。
7. CompareToとEqualsの違い
7-1. CompareToは順序比較、Equalsは等価比較
CompareToとEqualsは、どちらも比較に使われますが目的が違います。
| メソッド | 目的 |
|---|---|
CompareTo | 順序を比較する |
Equals | 等しいかどうかを判定する |
CompareToは「前か、同じ位置か、後か」を判定します。
C#int result = a.CompareTo(b);
Equalsは「等しいかどうか」だけを判定します。
C#bool result = a.Equals(b);
たとえば、ソートしたいならCompareTo、同一判定をしたいならEqualsを使います。
7-2. CompareToが0ならEqualsもtrueとは限らない理由
CompareToが0を返す場合、それは「並び順として同じ位置」という意味です。
一方、Equalsは「等価であるか」を判定します。比較ルールによっては、CompareToでは同じ順序とみなされても、Equalsでは別物と判定される可能性があります。
特に文字列では注意が必要です。C#の文字列比較では、Equalsや==と、CompareToやCompareでは比較の性質が異なる場合があります。MicrosoftのC#文字列比較のドキュメントでは、Equalsや==による等価判定と、CompareToやCompareによる文字列比較は異なり、CompareToやCompareは現在のカルチャに基づく言語的比較を行うと説明されています。
そのため、等しいかどうかを調べたいだけなら、CompareToではなくEqualsを使うのが基本です。
C#// 等価判定ならこちら
bool isEqual = string.Equals(a, b, StringComparison.Ordinal);
7-3. ==演算子との違い
==演算子は、型によって意味が異なります。
数値型では値の一致を比較します。
C#int a = 10;
int b = 10;
Console.WriteLine(a == b); // True
文字列でも内容の一致を比較します。
C#string s1 = "apple";
string s2 = "apple";
Console.WriteLine(s1 == s2); // True
一方、独自クラスでは、==をオーバーロードしていない限り、参照の一致を比較することが多いです。
C#var p1 = new Product { Name = "A", Price = 100 };
var p2 = new Product { Name = "A", Price = 100 };
Console.WriteLine(p1 == p2); // 通常はFalse
CompareToは順序比較、Equalsは等価比較、==は演算子としての比較です。用途に応じて使い分ける必要があります。
7-4. CompareTo・Equals・Compareの使い分け表
| 比較方法 | 戻り値 | 主な用途 | 例 |
|---|---|---|---|
CompareTo | int | 自分と相手の順序比較 | a.CompareTo(b) |
Equals | bool | 等価判定 | a.Equals(b) |
== | bool | 値または参照の比較 | a == b |
string.Compare | int | 文字列の順序比較 | string.Compare(a, b, StringComparison.Ordinal) |
Comparer<T> | int | 外部比較ロジック | Comparer<T>.Create(...) |
IComparer<T> | int | 複数の並び替え基準 | Sort(comparer) |
判断に迷った場合は、次のように考えるとわかりやすいです。
| やりたいこと | 選ぶ方法 |
|---|---|
| 大小や順序を知りたい | CompareTo |
| 同じかどうかを知りたい | Equals |
| 文字列の比較ルールを明示したい | string.Compareまたはstring.Equals |
| 独自クラスを自然順序で並べたい | IComparable<T> |
| 複数の並び替え方法を使いたい | IComparer<T> |
7-5. 実務ではどの比較方法を選ぶべきか
実務では、次の基準で選ぶと安全です。
数値や日付の単純な大小比較なら、<、>、CompareToのどれでも問題ありません。
C#if (date1.CompareTo(date2) < 0)
{
Console.WriteLine("date1のほうが前です");
}
文字列の等価判定なら、StringComparisonを指定したstring.Equalsがおすすめです。
C#bool isMatch = string.Equals(
input,
expected,
StringComparison.OrdinalIgnoreCase
);
文字列の並び替えなら、string.Compareで比較ルールを明示すると安全です。
C#names.Sort((a, b) => string.Compare(
a,
b,
StringComparison.CurrentCulture
));
独自クラスで自然な順序が1つに決まるなら、IComparable<T>を実装します。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
複数の並び替え方法が必要なら、IComparer<T>やラムダ式を使うほうが柔軟です。
8. CompareToでよくあるエラーと対処法
8-1. 比較対象がnullの場合
CompareToでよくあるエラーの1つが、比較対象がnullのケースです。
C#public int CompareTo(Product other)
{
return Price.CompareTo(other.Price); // otherがnullなら例外
}
このコードでは、otherがnullの場合にNullReferenceExceptionが発生します。
対処法は、先にnullチェックを入れることです。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
比較デリゲートでは、左右両方のnullをチェックします。
C#products.Sort((a, b) =>
{
if (a == null && b == null) return 0;
if (a == null) return -1;
if (b == null) return 1;
return a.Price.CompareTo(b.Price);
});
8-2. 型が異なるオブジェクトを比較した場合
非ジェネリックのIComparableでは、CompareTo(object obj)を実装します。この場合、異なる型が渡される可能性があります。
C#public int CompareTo(object obj)
{
Product other = obj as Product;
return Price.CompareTo(other.Price); // otherがnullなら例外
}
安全に書くなら、型チェックを行います。
C#public int CompareTo(object obj)
{
if (obj == null) return 1;
if (obj is not Product other)
{
throw new ArgumentException("比較対象がProductではありません。");
}
return Price.CompareTo(other.Price);
}
ただし、新しく実装するならIComparable<T>を使うことで、この問題を避けやすくなります。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
8-3. Sort時に「比較できない」と言われる原因
Sort()時に比較できないと言われる主な原因は、対象の型が比較方法を持っていないことです。
C#var products = new List<Product>();
products.Sort(); // Productの比較方法がない
対処法は主に3つあります。
1つ目は、IComparable<T>を実装する方法です。
C#public class Product : IComparable<Product>
{
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
2つ目は、Sortにラムダ式で比較処理を渡す方法です。
C#products.Sort((a, b) => a.Price.CompareTo(b.Price));
3つ目は、IComparer<T>を作る方法です。
C#public class ProductPriceComparer : IComparer<Product>
{
public int Compare(Product x, Product y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
return x.Price.CompareTo(y.Price);
}
}
使い方は次のとおりです。
C#products.Sort(new ProductPriceComparer());
8-4. CompareToの戻り値を逆に判定してしまうミス
CompareToでは、呼び出し元と引数の関係を意識する必要があります。
C#a.CompareTo(b)
この場合、比較しているのは「aがbに対してどうか」です。
C#if (a.CompareTo(b) < 0)
{
Console.WriteLine("aはbより小さい");
}
よくあるミスは、降順ソートで向きを間違えることです。
C#// 昇順
numbers.Sort((a, b) => a.CompareTo(b));
// 降順
numbers.Sort((a, b) => b.CompareTo(a));
また、次のように< 0と> 0を逆に読んでしまうケースもあります。
C#if (a.CompareTo(b) > 0)
{
Console.WriteLine("aはbより大きい");
}
迷ったら、具体的な数字で考えるとわかりやすいです。
C#5.CompareTo(10) // 負の数
10.CompareTo(5) // 正の数
10.CompareTo(10) // 0
8-5. 文字列比較で想定外の順番になる原因
文字列比較で想定外の順番になる原因は、比較ルールが明示されていないことです。
C#var words = new List<string> { "apple", "Apple", "banana" };
words.Sort();
このように書くと、現在のカルチャや大文字小文字の扱いによって、期待と違う順番になることがあります。
対処法は、比較ルールを明示することです。
C#words.Sort((a, b) => string.Compare(
a,
b,
StringComparison.OrdinalIgnoreCase
));
また、等価判定でCompareToを使うのも避けたほうがよいです。
C#// 避けたほうがよい
if (a.CompareTo(b) == 0)
{
Console.WriteLine("同じ");
}
等価判定なら、次のように書きます。
C#if (string.Equals(a, b, StringComparison.Ordinal))
{
Console.WriteLine("同じ");
}
.NETの文字列比較に関するドキュメントでも、String.CompareToは主に並び替えや順序比較のためのものであり、等価判定にはString.Equalsを使うべきだと説明されています。
9. CompareToを使うときのベストプラクティス
9-1. 比較条件は一貫性を持たせる
CompareToの実装では、一貫性が非常に重要です。
悪い例として、比較のたびに結果が変わるような実装があります。
C#public int CompareTo(Product other)
{
return Random.Shared.Next(-1, 2);
}
このような実装では、ソート結果が安定せず、予測できない動作になります。
正しくは、同じ値同士を比較したら常に同じ結果を返すようにします。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
9-2. EqualsやGetHashCodeとの整合性を意識する
独自クラスでCompareToを実装する場合、EqualsやGetHashCodeとの関係も考える必要があります。
たとえば、商品をProductIdで同一とみなし、並び順もProductIdで決めるなら、整合性があります。
C#public class Product : IComparable<Product>, IEquatable<Product>
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return ProductId.CompareTo(other.ProductId);
}
public bool Equals(Product other)
{
if (other == null) return false;
return ProductId == other.ProductId;
}
public override bool Equals(object obj)
{
return Equals(obj as Product);
}
public override int GetHashCode()
{
return ProductId.GetHashCode();
}
}
CompareToが0になる条件と、Equalsがtrueになる条件が大きくズレていると、ソート済みコレクションや重複判定で混乱を招くことがあります。
9-3. 複数の並び替え条件がある場合はComparerを使う
自然順序が1つに決まる場合は、IComparable<T>で問題ありません。
しかし、複数の並び替え条件がある場合は、IComparer<T>やラムダ式を使うほうが適しています。
たとえば、商品を価格順に並べる場合です。
C#products.Sort((a, b) => a.Price.CompareTo(b.Price));
名前順に並べる場合です。
C#products.Sort((a, b) => string.Compare(
a.Name,
b.Name,
StringComparison.Ordinal
));
専用の比較クラスを作ることもできます。
C#public class ProductNameComparer : IComparer<Product>
{
public int Compare(Product x, Product y)
{
if (x == null && y == null) return 0;
if (x == null) return -1;
if (y == null) return 1;
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
}
}
使い方は次のとおりです。
C#products.Sort(new ProductNameComparer());
「このクラスの標準的な順序」はIComparable<T>、「用途ごとに変わる順序」はIComparer<T>と考えると整理しやすいです。
9-4. 文字列比較では比較ルールを明示する
文字列比較では、できるだけ比較ルールを明示しましょう。
避けたい例です。
C#names.Sort((a, b) => a.CompareTo(b));
比較ルールを明示する例です。
C#names.Sort((a, b) => string.Compare(
a,
b,
StringComparison.Ordinal
));
大文字小文字を無視するなら次のようにします。
C#names.Sort((a, b) => string.Compare(
a,
b,
StringComparison.OrdinalIgnoreCase
));
ユーザーに見せる名前の並び順であれば、現在のカルチャを使う選択肢もあります。
C#names.Sort((a, b) => string.Compare(
a,
b,
StringComparison.CurrentCulture
));
重要なのは、「どのルールで比較しているか」をコード上で読み取れるようにすることです。
9-5. 読みやすく保守しやすいCompareTo実装の書き方
CompareToは、短く書こうとしすぎると読みづらくなることがあります。
たとえば、複数条件を1行に詰め込むと、後から修正しにくくなります。
C#// 読みにくい例
public int CompareTo(Product other)
=> Category.CompareTo(other.Category) != 0
? Category.CompareTo(other.Category)
: Price.CompareTo(other.Price);
読みやすさを重視するなら、比較結果を変数に入れて段階的に返すのがおすすめです。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
int categoryResult = string.Compare(
Category,
other.Category,
StringComparison.Ordinal
);
if (categoryResult != 0)
{
return categoryResult;
}
int priceResult = Price.CompareTo(other.Price);
if (priceResult != 0)
{
return priceResult;
}
return string.Compare(Name, other.Name, StringComparison.Ordinal);
}
この書き方なら、比較条件の追加や順番変更がしやすくなります。
10. C# CompareToに関するよくある質問
10-1. CompareToの戻り値は必ず-1・0・1ですか?
いいえ。CompareToの戻り値は、必ず-1、0、1になるとは限りません。
重要なのは、次の3分類です。
| 戻り値 | 意味 |
|---|---|
0未満 | 現在の値が比較対象より前、または小さい |
0 | 同じ順序 |
0より大きい | 現在の値が比較対象より後、または大きい |
そのため、次のように判定します。
C#if (a.CompareTo(b) < 0)
{
Console.WriteLine("aはbより小さい");
}
次のような書き方は避けましょう。
C#if (a.CompareTo(b) == -1)
{
Console.WriteLine("aはbより小さい");
}
10-2. CompareToとCompareはどちらを使うべきですか?
数値や日付の単純な比較なら、CompareToで問題ありません。
C#int result = date1.CompareTo(date2);
文字列比較では、比較ルールを明示できるstring.Compareを使うほうが安全な場面が多いです。
C#int result = string.Compare(
text1,
text2,
StringComparison.OrdinalIgnoreCase
);
独自クラスの自然順序を定義したい場合は、IComparable<T>のCompareToを実装します。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
つまり、自然順序ならCompareTo、比較ルールを外から指定したいならCompareやIComparer<T>を使うのが基本です。
10-3. SortでCompareToが自動的に呼ばれるのはなぜですか?
Sort()は、要素同士を比較しながら並び替えるメソッドです。
要素がIComparable<T>を実装している場合、Sort()はその比較方法を使って順番を決めます。
C#public class Product : IComparable<Product>
{
public int Price { get; set; }
public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
}
このように実装しておけば、次のようにSort()を呼ぶだけで価格順に並び替えられます。
C#products.Sort();
IComparable.CompareToのドキュメントでも、CompareToは値を順序付け・ソートできる型で実装され、Array.Sortなどのソート処理から自動的に呼ばれると説明されています。
10-4. 独自クラスでCompareToを実装しないとどうなりますか?
独自クラスでCompareToを実装しない場合、そのクラスの自然な並び順は定義されません。
たとえば、次のようなクラスがあります。
C#public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
このUserをそのままSort()しようとしても、名前順なのか年齢順なのか、C#には判断できません。
C#var users = new List<User>();
users.Sort(); // 比較方法がない
対処法は、IComparable<User>を実装するか、Sortに比較処理を渡すことです。
C#users.Sort((a, b) => a.Age.CompareTo(b.Age));
自然順序が明確な場合はIComparable<T>、用途ごとに並び順を変えたい場合はラムダ式やIComparer<T>を使うとよいでしょう。
10-5. CompareToで降順ソートするにはどうすればよいですか?
降順ソートするには、CompareToの呼び出し順を逆にします。
昇順です。
C#numbers.Sort((a, b) => a.CompareTo(b));
降順です。
C#numbers.Sort((a, b) => b.CompareTo(a));
日付を新しい順に並べる場合も同じです。
C#dates.Sort((a, b) => b.CompareTo(a));
独自クラスで価格の高い順に並べるなら、次のように書けます。
C#products.Sort((a, b) => b.Price.CompareTo(a.Price));
OrderByDescendingを使う方法もあります。
C#var sorted = products
.OrderByDescending(p => p.Price)
.ToList();
元のリストを書き換えたいならSort()、新しい並び替え結果がほしいならOrderByDescending()を使うとよいでしょう。
まとめ
C#のCompareToは、値やオブジェクトの順序を比較するためのメソッドです。戻り値はintですが、見るべきポイントは具体的な数値ではなく、0未満、0、0より大きいの3パターンです。
C#int result = a.CompareTo(b);
if (result < 0)
{
Console.WriteLine("aはbより前または小さい");
}
else if (result == 0)
{
Console.WriteLine("aとbは同じ順序");
}
else
{
Console.WriteLine("aはbより後または大きい");
}
数値や日付の比較では直感的に使えますが、文字列比較ではカルチャや大文字小文字の影響を受けるため注意が必要です。文字列の比較ルールを明示したい場合は、string.Compareやstring.EqualsにStringComparisonを指定しましょう。
独自クラスをSort()で自然に並び替えたい場合は、IComparable<T>を実装してCompareToを定義します。
C#public int CompareTo(Product other)
{
if (other == null) return 1;
return Price.CompareTo(other.Price);
}
一方で、複数の並び替え条件がある場合や、状況によって並び順を変えたい場合は、IComparer<T>やラムダ式、LINQのOrderByを使うほうが柔軟です。
CompareToは、C#でソートや順序比較を正しく扱うための基本です。戻り値の意味、Equalsとの違い、文字列比較の注意点を押さえておけば、実務でも安全で読みやすい比較処理を書けるようになります。

