C#のCompare完全ガイド|CompareTo・Equals・比較演算子の違いと正しい使い分け
はじめに
C#で開発していると、数値の大小を比べる、文字列が一致するか調べる、日付順に並べる、独自クラスをソートするといった「比較処理」が頻繁に登場します。
しかし、C#には比較に使える仕組みが複数あります。
CompareTo、Equals、==、!=、<、>、String.Compare、IComparable<T>、IComparer<T>などがあり、目的に合わないものを使うと、意図しない判定結果やバグにつながります。
この記事では、csharp compareを理解したい人に向けて、C#における比較方法の違い、正しい使い分け、文字列比較の注意点、独自クラスの比較実装までを体系的に解説します。
1. C#のCompare完全ガイド|CompareTo・Equals・比較演算子の違いと正しい使い分け
1-1. C#で「比較」が必要になる場面
C#で比較が必要になる場面は多くあります。
たとえば、次のようなケースです。
C#int age = 20;
if (age >= 18)
{
Console.WriteLine("成人です");
}
数値の大小比較だけでなく、文字列の一致判定もよく使います。
C#string input = "admin";
if (input == "admin")
{
Console.WriteLine("管理者です");
}
また、日付順に並び替える場合にも比較が必要です。
C#DateTime today = DateTime.Today;
DateTime deadline = new DateTime(2026, 12, 31);
if (today.CompareTo(deadline) < 0)
{
Console.WriteLine("締切前です");
}
このように、C#の比較処理は条件分岐、検索、ソート、重複判定、辞書や集合のキー判定など、多くの場面で使われます。
1-2. Compare・CompareTo・Equals・== の役割の違い
C#の比較方法は、目的によって使い分けます。
CompareToは、対象同士の大小関係や並び順を判定するために使います。
C#int result = 10.CompareTo(20);
Equalsは、2つの値やオブジェクトが等しいかどうかを判定するために使います。
C#bool result = "abc".Equals("abc");
==は、型によって値の比較になる場合と参照の比較になる場合があります。
C#bool result = 10 == 10;
String.Compareは、文字列同士の並び順を比較するときに使います。
C#int result = string.Compare("apple", "banana", StringComparison.Ordinal);
つまり、等しいかを見たいならEqualsや==、大小や順序を見たいならCompareToやCompareを使うのが基本です。
1-3. この記事で解決できること
この記事では、次の疑問を解決できます。
CompareToとEqualsの違いがわからない、==とEqualsは同じなのか知りたい、文字列比較で大文字小文字を無視したい、独自クラスを正しく比較したい、List.SortやLINQのOrderByで比較処理を使いたい、という人に向けて具体例付きで解説します。
C#の比較処理を正しく理解すると、条件分岐やソート処理のバグを減らせます。
2. C#の比較方法一覧|まずは全体像を整理
2-1. CompareToは大小・順序を比較する
CompareToは、現在のインスタンスと指定した値を比較し、大小関係を数値で返します。
C#int result = 5.CompareTo(10);
Console.WriteLine(result); // 0未満
戻り値は、次のような意味を持ちます。
| 戻り値 | 意味 |
|---|---|
| 負の数 | 自分の方が小さい |
| 0 | 等しい |
| 正の数 | 自分の方が大きい |
重要なのは、戻り値が必ず-1、0、1になるとは限らないことです。
そのため、次のように判定します。
C#if (a.CompareTo(b) < 0)
{
Console.WriteLine("aはbより小さい");
}
2-2. Equalsは等価性を判定する
Equalsは、2つの値やオブジェクトが等しいかどうかをtrueまたはfalseで返します。
C#int x = 10;
int y = 10;
Console.WriteLine(x.Equals(y)); // true
文字列でも使えます。
C#string a = "hello";
string b = "hello";
Console.WriteLine(a.Equals(b)); // true
Equalsは大小関係ではなく、「同じとみなせるか」を判定するためのメソッドです。
2-3. ==・!= は値または参照を比較する
==は等しいか、!=は等しくないかを判定します。
C#int a = 10;
int b = 20;
Console.WriteLine(a == b); // false
Console.WriteLine(a != b); // true
数値型やbool、charなどでは値を比較します。
一方、通常の参照型では参照先が同じオブジェクトかどうかを比較します。ただし、stringのように==がオーバーロードされている型では、内容の比較になります。
2-4. <・>・<=・>= は大小関係を比較する
<、>、<=、>=は大小関係を比較する演算子です。
C#int score = 80;
if (score >= 70)
{
Console.WriteLine("合格です");
}
数値型では自然に使えます。
C#double price = 1200.5;
if (price < 1500)
{
Console.WriteLine("予算内です");
}
ただし、独自クラスでこれらの演算子を使いたい場合は、演算子オーバーロードを実装する必要があります。
2-5. String.Compareは文字列の並び順を比較する
String.Compareは、2つの文字列の並び順を比較するメソッドです。
C#int result = string.Compare("apple", "banana", StringComparison.Ordinal);
if (result < 0)
{
Console.WriteLine("appleはbananaより前に並びます");
}
文字列が等しいかだけを判定したい場合は、string.Equalsを使う方が意図が明確です。
C#bool same = string.Equals("abc", "abc", StringComparison.Ordinal);
3. CompareToの使い方と戻り値の意味
3-1. CompareToとは何か
CompareToは、ある値と別の値を比較し、大小関係を表す整数を返すメソッドです。
多くの型はIComparableまたはIComparable<T>を実装しており、CompareToを使って自然な並び順を定義しています。
たとえば、int、double、DateTime、stringなどで使えます。
C#int result = 100.CompareTo(50);
Console.WriteLine(result); // 正の数
3-2. CompareToが返す「負の数・0・正の数」の意味
CompareToの戻り値は、次の3種類で判断します。
C#int a = 10;
int b = 20;
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より大きい");
}
ここで大切なのは、戻り値をresult == -1のように判定しないことです。
実装によっては-1ではなく、別の負の数が返る可能性があります。
正しい判定は、< 0、== 0、> 0です。
3-3. int・DateTime・stringでCompareToを使う例
intの比較例です。
C#int a = 5;
int b = 10;
Console.WriteLine(a.CompareTo(b)); // 0未満
Console.WriteLine(b.CompareTo(a)); // 0より大きい
Console.WriteLine(a.CompareTo(5)); // 0
DateTimeの比較例です。
C#DateTime start = new DateTime(2026, 1, 1);
DateTime end = new DateTime(2026, 12, 31);
if (start.CompareTo(end) < 0)
{
Console.WriteLine("startはendより前の日付です");
}
stringの比較例です。
C#string a = "apple";
string b = "banana";
if (a.CompareTo(b) < 0)
{
Console.WriteLine("appleはbananaより前です");
}
ただし、文字列比較ではカルチャの影響を受ける場合があります。明示的に比較ルールを指定したい場合は、String.Compareやstring.EqualsでStringComparisonを指定する方が安全です。
3-4. CompareToは等しいかどうかの判定に使うべきか
CompareToで等価判定をすることはできます。
C#if (a.CompareTo(b) == 0)
{
Console.WriteLine("等しい");
}
しかし、単に等しいかどうかを知りたい場合は、Equalsや==を使う方が自然です。
C#if (a.Equals(b))
{
Console.WriteLine("等しい");
}
CompareToは本来、大小関係や並び順を判断するためのものです。
そのため、ソートや順序比較にはCompareTo、等価判定にはEqualsを使うと、コードの意図がわかりやすくなります。
3-5. CompareToでnullを扱うときの注意点
CompareToを呼び出す側がnullの場合、例外が発生します。
C#string value = null;
// NullReferenceException
// value.CompareTo("abc");
安全に比較したい場合は、string.Compareを使う方法があります。
C#string a = null;
string b = "abc";
int result = string.Compare(a, b, StringComparison.Ordinal);
また、独自クラスでCompareToを実装する場合は、引数がnullのときの扱いを決めておく必要があります。
一般的には、nullより通常のインスタンスの方が大きいとする実装がよく使われます。
C#public int CompareTo(Product? other)
{
if (other is null) return 1;
return Price.CompareTo(other.Price);
}
4. Equalsの使い方|==との違いを理解する
4-1. Equalsとは何か
Equalsは、オブジェクト同士が等しいかどうかを判定するメソッドです。
C#string a = "C#";
string b = "C#";
Console.WriteLine(a.Equals(b)); // true
Equalsはobject型に定義されているため、すべての型で利用できます。
ただし、型によって動作が異なります。値型では値の比較、参照型では基本的に参照の比較になりますが、型によっては内容比較にオーバーライドされています。
4-2. 値型におけるEqualsの動作
intやDateTimeなどの値型では、基本的に値そのものを比較します。
C#int a = 100;
int b = 100;
Console.WriteLine(a.Equals(b)); // true
DateTimeでも同様です。
C#DateTime d1 = new DateTime(2026, 1, 1);
DateTime d2 = new DateTime(2026, 1, 1);
Console.WriteLine(d1.Equals(d2)); // true
値型では、同じ値を持っていればEqualsはtrueになります。
4-3. 参照型におけるEqualsの動作
通常のクラスでは、Equalsをオーバーライドしない限り、同じインスタンスかどうかを比較します。
C#class User
{
public string Name { get; set; } = "";
}
var user1 = new User { Name = "Alice" };
var user2 = new User { Name = "Alice" };
Console.WriteLine(user1.Equals(user2)); // false
user1とuser2は同じ値を持っていますが、別々に生成されたインスタンスなのでfalseになります。
内容で比較したい場合は、Equalsをオーバーライドするか、IEquatable<T>を実装します。
4-4. stringのEqualsと==の違い
stringは参照型ですが、Equalsも==も文字列の内容を比較するように実装されています。
C#string a = "hello";
string b = new string(new[] { 'h', 'e', 'l', 'l', 'o' });
Console.WriteLine(a == b); // true
Console.WriteLine(a.Equals(b)); // true
ただし、EqualsではStringComparisonを指定できます。
C#string a = "Hello";
string b = "hello";
bool result = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result); // true
文字列比較では、単純な一致判定なら==でも問題ありませんが、大文字小文字やカルチャを制御したい場合はstring.Equalsを使うのが安全です。
4-5. Equalsをオーバーライドすべきケース
独自クラスで「同じ値を持つなら同じもの」と判定したい場合は、Equalsをオーバーライドします。
たとえば、商品IDが同じなら同じ商品とみなす場合です。
C#class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public override bool Equals(object? obj)
{
if (obj is not Product other) return false;
return Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
このように実装すると、別々のインスタンスでもIdが同じなら等しいと判定できます。
4-6. EqualsとGetHashCodeをセットで実装する理由
Equalsをオーバーライドする場合は、GetHashCodeもセットで実装する必要があります。
理由は、Dictionary<TKey, TValue>やHashSet<T>などのハッシュベースのコレクションで正しく動作させるためです。
C#var set = new HashSet<Product>();
set.Add(new Product { Id = 1, Name = "Pen" });
set.Add(new Product { Id = 1, Name = "Pen" });
Console.WriteLine(set.Count);
Equalsで等しいと判定されるオブジェクトは、同じハッシュコードを返す必要があります。
複数のプロパティを使う場合は、HashCode.Combineを使うと便利です。
C#public override int GetHashCode()
{
return HashCode.Combine(Id, Name);
}
5. 比較演算子の使い方|==・!=・<・>・<=・>=
5-1. ==と!=で等しい・等しくないを判定する
==は等しいか、!=は等しくないかを判定します。
C#int a = 10;
int b = 10;
Console.WriteLine(a == b); // true
Console.WriteLine(a != b); // false
boolやcharでも使えます。
C#char c1 = 'A';
char c2 = 'B';
Console.WriteLine(c1 == c2); // false
5-2. <・>・<=・>=で大小を比較する
大小比較には、<、>、<=、>=を使います。
C#int score = 85;
if (score >= 80)
{
Console.WriteLine("高得点です");
}
日付の場合は、DateTimeでも比較演算子が使えます。
C#DateTime now = DateTime.Now;
DateTime limit = new DateTime(2026, 12, 31);
if (now < limit)
{
Console.WriteLine("期限内です");
}
5-3. 数値型の比較演算子の基本
数値型では、比較演算子を自然に使えます。
C#int x = 5;
double y = 5.0;
Console.WriteLine(x == y); // true
ただし、浮動小数点数の比較では誤差に注意が必要です。
C#double a = 0.1 + 0.2;
double b = 0.3;
Console.WriteLine(a == b); // falseになる場合がある
このような場合は、許容誤差を使って比較します。
C#double tolerance = 0.000001;
if (Math.Abs(a - b) < tolerance)
{
Console.WriteLine("ほぼ等しい");
}
5-4. stringで==が使える理由
stringは参照型ですが、==演算子がオーバーロードされているため、文字列の内容を比較できます。
C#string a = "abc";
string b = "abc";
Console.WriteLine(a == b); // true
通常のクラスでは参照比較になることが多いですが、stringでは内容比較になる点が特徴です。
C#string x = new string(new[] { 'a', 'b', 'c' });
string y = new string(new[] { 'a', 'b', 'c' });
Console.WriteLine(x == y); // true
5-5. object型で==を使うときの落とし穴
object型に代入すると、==の挙動に注意が必要です。
C#object a = "hello";
object b = new string(new[] { 'h', 'e', 'l', 'l', 'o' });
Console.WriteLine(a == b); // false
Console.WriteLine(a.Equals(b)); // true
aとbは中身の文字列は同じですが、object型として==を使うと参照比較になります。
文字列として比較したい場合は、型を明確にするか、string.Equalsを使います。
C#bool result = string.Equals(a as string, b as string, StringComparison.Ordinal);
5-6. 演算子オーバーロードが必要になるケース
独自クラスで==や<などを使いたい場合は、演算子オーバーロードを実装します。
C#class Money
{
public int Amount { get; }
public Money(int amount)
{
Amount = amount;
}
public static bool operator ==(Money? left, Money? right)
{
return EqualityComparer<Money>.Default.Equals(left, right);
}
public static bool operator !=(Money? left, Money? right)
{
return !(left == right);
}
public override bool Equals(object? obj)
{
return obj is Money other && Amount == other.Amount;
}
public override int GetHashCode()
{
return Amount.GetHashCode();
}
}
==をオーバーロードする場合は、!=、Equals、GetHashCodeも整合性が取れるように実装します。
6. 文字列比較の正しい使い分け
6-1. String.CompareとCompareToの違い
string.CompareToは、インスタンスメソッドです。
C#string a = "apple";
string b = "banana";
int result = a.CompareTo(b);
一方、String.Compareは静的メソッドで、比較方法を指定しやすいという特徴があります。
C#int result = string.Compare(a, b, StringComparison.Ordinal);
文字列比較では、StringComparisonを明示できるString.Compareの方が安全です。
6-2. string.Equalsと==の違い
stringでは、==もEqualsも文字列の内容を比較します。
C#string a = "CSharp";
string b = "CSharp";
Console.WriteLine(a == b); // true
Console.WriteLine(a.Equals(b)); // true
ただし、Equalsには比較ルールを指定できるオーバーロードがあります。
C#bool result = string.Equals(
"CSharp",
"csharp",
StringComparison.OrdinalIgnoreCase
);
Console.WriteLine(result); // true
大文字小文字を無視したい場合や、カルチャ非依存で比較したい場合は、string.Equalsを使う方が明確です。
6-3. 大文字・小文字を無視して比較する方法
大文字小文字を無視して比較するには、StringComparison.OrdinalIgnoreCaseを使います。
C#string a = "Admin";
string b = "admin";
if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("一致しました");
}
ToLowerやToUpperに変換して比較する方法もありますが、余計な文字列生成やカルチャ依存の問題を避けるため、StringComparisonを指定する方法がおすすめです。
C#// 推奨
string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
6-4. StringComparison.OrdinalとCurrentCultureの違い
StringComparison.Ordinalは、文字のコード値に基づいて比較します。
C#string.Equals(a, b, StringComparison.Ordinal);
高速で、カルチャの影響を受けません。ID、キー、ファイル名、プロトコル、設定値など、プログラム内部の識別子を比較する場合に向いています。
一方、StringComparison.CurrentCultureは、現在のカルチャに基づいて比較します。
C#string.Equals(a, b, StringComparison.CurrentCulture);
ユーザーに表示する文字列を自然な言語ルールで比較したい場合に使います。
多くのアプリケーション内部の比較では、OrdinalまたはOrdinalIgnoreCaseを選ぶと安全です。
6-5. 文化依存の比較で起こりやすいバグ
文字列比較でカルチャを意識しないと、環境によって結果が変わることがあります。
たとえば、大文字小文字の変換や並び順は、言語や地域のルールによって異なる場合があります。
C#string a = "file";
string b = "FILE";
bool result = string.Equals(a, b, StringComparison.CurrentCultureIgnoreCase);
ユーザー向けの自然言語比較なら問題ありませんが、ログインID、権限名、設定キー、APIパラメータなどの比較でカルチャ依存にすると、不具合やセキュリティ上の問題につながる可能性があります。
そのため、プログラム内部の固定的な文字列比較では、次のように書くのが基本です。
C#string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
6-6. 文字列比較で推奨される書き方
文字列比較では、目的に応じてStringComparisonを明示するのがおすすめです。
完全一致を判定する場合です。
C#bool result = string.Equals(a, b, StringComparison.Ordinal);
大文字小文字を無視する場合です。
C#bool result = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
並び順を比較する場合です。
C#int result = string.Compare(a, b, StringComparison.Ordinal);
単純な文字列比較で==を使うこと自体は間違いではありません。
C#if (a == b)
{
Console.WriteLine("一致");
}
ただし、比較ルールを明確にしたい場面では、string.Equalsやstring.CompareにStringComparisonを指定する方が安全です。
7. カスタムクラスの比較方法
7-1. 独自クラスでEqualsを実装する
独自クラスで内容が同じかどうかを判定したい場合は、Equalsを実装します。
C#class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public override bool Equals(object? obj)
{
if (obj is not User other) return false;
return Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
この実装では、Idが同じなら同じユーザーとみなします。
C#var user1 = new User { Id = 1, Name = "Alice" };
var user2 = new User { Id = 1, Name = "Alicia" };
Console.WriteLine(user1.Equals(user2)); // true
7-2. IEquatable<T>を実装する
型安全で効率の良い等価比較を行うには、IEquatable<T>を実装します。
C#class User : IEquatable<User>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public bool Equals(User? other)
{
if (other is null) return false;
return Id == other.Id;
}
public override bool Equals(object? obj)
{
return Equals(obj as User);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
List<T>.ContainsやHashSet<T>などで、意図した等価比較を行いやすくなります。
7-3. IComparable<T>を実装して並び順を定義する
独自クラスに自然な並び順を持たせたい場合は、IComparable<T>を実装します。
C#class Product : IComparable<Product>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
public int CompareTo(Product? other)
{
if (other is null) return 1;
return Price.CompareTo(other.Price);
}
}
この例では、Priceの昇順で並び替えられます。
C#var products = new List<Product>
{
new Product { Id = 1, Name = "Pen", Price = 100 },
new Product { Id = 2, Name = "Notebook", Price = 200 },
new Product { Id = 3, Name = "Eraser", Price = 50 }
};
products.Sort();
7-4. IComparer<T>で外部から比較ルールを渡す
クラス自体に比較ルールを持たせるのではなく、外部から比較ルールを渡したい場合は、IComparer<T>を使います。
C#class ProductNameComparer : IComparer<Product>
{
public int Compare(Product? x, Product? y)
{
if (ReferenceEquals(x, y)) return 0;
if (x is null) return -1;
if (y is null) return 1;
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
}
}
使い方は次の通りです。
C#products.Sort(new ProductNameComparer());
IComparer<T>を使うと、価格順、名前順、ID順など、複数の並び替えルールを切り替えやすくなります。
7-5. CompareToとEqualsの整合性を保つ
CompareToとEqualsの結果には整合性を持たせることが重要です。
一般的には、CompareToが0を返すなら、Equalsもtrueになる設計が望ましいです。
悪い例です。
C#// CompareToはPriceで比較
// EqualsはIdで比較
このように基準が異なると、ソート済みコレクションや重複判定で混乱が起きる可能性があります。
必ずしもすべてのケースで完全一致させる必要があるわけではありませんが、違う基準を使う場合は明確な理由を持つべきです。
7-6. カスタムクラス比較の実装例
Idで等価判定し、Priceで並び替える例です。
C#class Product : IEquatable<Product>, IComparable<Product>
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Price { get; set; }
public bool Equals(Product? other)
{
if (other is null) return false;
return Id == other.Id;
}
public override bool Equals(object? obj)
{
return Equals(obj as Product);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
public int CompareTo(Product? other)
{
if (other is null) return 1;
int priceResult = Price.CompareTo(other.Price);
if (priceResult != 0) return priceResult;
return Id.CompareTo(other.Id);
}
}
価格が同じ場合はIdで比較することで、並び順を安定させています。
8. Sort・OrderBy・LINQでCompareを活用する
8-1. List.SortでCompareToが使われる仕組み
List<T>.Sort()は、要素がIComparable<T>を実装している場合、そのCompareToを使って並び替えます。
C#var numbers = new List<int> { 3, 1, 2 };
numbers.Sort();
Console.WriteLine(string.Join(", ", numbers)); // 1, 2, 3
独自クラスでも、IComparable<T>を実装していればSort()で並び替えられます。
C#products.Sort();
このとき、Product.CompareToの実装が使われます。
8-2. OrderBy・ThenByで並び替える方法
LINQでは、OrderByを使って並び替えできます。
C#var sorted = products.OrderBy(p => p.Price);
複数条件で並び替える場合は、ThenByを使います。
C#var sorted = products
.OrderBy(p => p.Price)
.ThenBy(p => p.Name);
降順にする場合は、OrderByDescendingやThenByDescendingを使います。
C#var sorted = products
.OrderByDescending(p => p.Price)
.ThenBy(p => p.Name);
8-3. Comparison<T>で簡単に比較処理を書く
Comparison<T>を使うと、ラムダ式で比較処理を書けます。
C#products.Sort((x, y) => x.Price.CompareTo(y.Price));
降順にしたい場合は、比較順を逆にします。
C#products.Sort((x, y) => y.Price.CompareTo(x.Price));
複数条件も書けます。
C#products.Sort((x, y) =>
{
int result = x.Price.CompareTo(y.Price);
if (result != 0) return result;
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
});
8-4. IComparer<T>を使った複数条件ソート
複雑な比較ルールを再利用したい場合は、IComparer<T>を実装します。
C#class ProductComparer : IComparer<Product>
{
public int Compare(Product? x, Product? y)
{
if (ReferenceEquals(x, y)) return 0;
if (x is null) return -1;
if (y is null) return 1;
int priceResult = x.Price.CompareTo(y.Price);
if (priceResult != 0) return priceResult;
return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
}
}
使用例です。
C#products.Sort(new ProductComparer());
比較ルールをクラス化しておくと、複数箇所で同じ並び順を使えます。
8-5. 昇順・降順を切り替える方法
数値や日付では、OrderByとOrderByDescendingを使い分けるのが簡単です。
C#var asc = products.OrderBy(p => p.Price);
var desc = products.OrderByDescending(p => p.Price);
List.Sortで降順にしたい場合は、比較の左右を逆にします。
C#products.Sort((x, y) => y.Price.CompareTo(x.Price));
また、昇順で並べた後にReverseする方法もあります。
C#products.Sort((x, y) => x.Price.CompareTo(y.Price));
products.Reverse();
ただし、複数条件がある場合は、最初から降順の比較ルールを書く方が明確です。
9. C#比較処理のよくある間違いと対策
9-1. CompareToの戻り値を1・0・-1だけだと思い込む
CompareToの戻り値は、負の数、0、正の数のいずれかです。
必ず-1、0、1になるとは限りません。
悪い例です。
C#if (a.CompareTo(b) == -1)
{
Console.WriteLine("aはbより小さい");
}
正しい例です。
C#if (a.CompareTo(b) < 0)
{
Console.WriteLine("aはbより小さい");
}
9-2. CompareToで等価判定をしてしまう
CompareToで等価判定はできますが、意図がわかりにくくなる場合があります。
C#if (a.CompareTo(b) == 0)
{
Console.WriteLine("等しい");
}
等しいかどうかだけを見たい場合は、Equalsや==を使いましょう。
C#if (a.Equals(b))
{
Console.WriteLine("等しい");
}
CompareToは、順序や大小を扱うときに使うのが基本です。
9-3. string比較でカルチャ差を考慮しない
文字列比較でStringComparisonを省略すると、意図しないカルチャ依存の比較になる場合があります。
内部的な識別子を比較する場合は、Ordinalを明示するのがおすすめです。
C#bool result = string.Equals(
input,
"admin",
StringComparison.OrdinalIgnoreCase
);
特に、ID、キー、コード、ファイル名、設定値、APIパラメータなどは、カルチャに依存しない比較を使うべきです。
9-4. null比較で例外が発生する
インスタンスメソッドとしてEqualsやCompareToを呼び出す場合、呼び出し元がnullだと例外になります。
C#string value = null;
// NullReferenceException
// value.Equals("abc");
安全に比較するには、静的メソッドを使います。
C#bool result = string.Equals(value, "abc", StringComparison.Ordinal);
比較対象がnullになる可能性がある場合は、ReferenceEqualsやis nullも活用します。
C#if (value is null)
{
Console.WriteLine("nullです");
}
9-5. Equalsだけ実装してGetHashCodeを忘れる
Equalsだけを実装し、GetHashCodeを実装しないと、HashSet<T>やDictionary<TKey, TValue>で意図しない動作になる可能性があります。
悪い例です。
C#public override bool Equals(object? obj)
{
return obj is User other && Id == other.Id;
}
良い例です。
C#public override bool Equals(object? obj)
{
return obj is User other && Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
Equalsで同じと判定されるオブジェクトは、同じハッシュコードを返す必要があります。
9-6. ==とEqualsの結果が一致しない
独自クラスでEqualsを実装しても、==をオーバーロードしない限り、==は参照比較のままです。
C#var a = new User { Id = 1 };
var b = new User { Id = 1 };
Console.WriteLine(a.Equals(b)); // true
Console.WriteLine(a == b); // false
==でも同じ判定にしたい場合は、演算子オーバーロードを実装します。
ただし、独自クラスで==をオーバーロードするときは、!=、Equals、GetHashCodeとの整合性を必ず保ちましょう。
10. 目的別の使い分け早見表
10-1. 等しいか判定したい場合
等しいかどうかを判定したい場合は、基本的にEqualsまたは==を使います。
C#if (a.Equals(b))
{
Console.WriteLine("等しい");
}
文字列の場合は、比較ルールを指定できるstring.Equalsが安全です。
C#string.Equals(a, b, StringComparison.Ordinal);
10-2. 大小・順序を判定したい場合
大小や順序を判定したい場合は、CompareToを使います。
C#if (a.CompareTo(b) > 0)
{
Console.WriteLine("aはbより大きい");
}
数値や日付では、比較演算子もわかりやすいです。
C#if (date1 < date2)
{
Console.WriteLine("date1はdate2より前です");
}
10-3. 文字列を安全に比較したい場合
文字列を安全に比較したい場合は、StringComparisonを指定します。
C#string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
並び順を比較する場合は、string.Compareを使います。
C#string.Compare(a, b, StringComparison.Ordinal);
内部的なキーやIDでは、OrdinalまたはOrdinalIgnoreCaseを使うのが基本です。
10-4. コレクションを並び替えたい場合
コレクションを並び替えたい場合は、List.Sort、LINQのOrderBy、ThenByを使います。
C#products.Sort((x, y) => x.Price.CompareTo(y.Price));
LINQなら次のように書けます。
C#var sorted = products
.OrderBy(p => p.Price)
.ThenBy(p => p.Name);
比較ルールを再利用したい場合は、IComparer<T>を実装します。
10-5. 独自クラスを比較したい場合
独自クラスで等価判定をしたい場合は、EqualsとGetHashCodeを実装します。
C#public override bool Equals(object? obj)
{
return obj is User other && Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
型安全にするならIEquatable<T>を実装します。
並び順を定義したい場合は、IComparable<T>を実装します。
C#public int CompareTo(User? other)
{
if (other is null) return 1;
return Id.CompareTo(other.Id);
}
11. C# Compareに関するよくある質問
11-1. CompareToとEqualsはどちらを使うべきですか
等しいかどうかを判定したい場合はEqualsを使います。
C#a.Equals(b);
大小や並び順を判定したい場合はCompareToを使います。
C#a.CompareTo(b);
CompareToが0なら等しいと判定できますが、目的が等価判定だけならEqualsの方が意図が明確です。
11-2. string.Compareとstring.CompareToの違いは何ですか
string.CompareToはインスタンスメソッドです。
C#a.CompareTo(b);
string.Compareは静的メソッドです。
C#string.Compare(a, b, StringComparison.Ordinal);
文字列比較では、比較ルールを指定できるstring.Compareの方が安全です。
特に、カルチャ依存を避けたい場合は、StringComparison.OrdinalやStringComparison.OrdinalIgnoreCaseを明示しましょう。
11-3. ==とEqualsは同じ結果になりますか
型によります。
数値型やstringでは、多くの場合同じ結果になります。
C#string a = "abc";
string b = "abc";
Console.WriteLine(a == b); // true
Console.WriteLine(a.Equals(b)); // true
しかし、通常の参照型では、==は参照比較、Equalsは実装次第です。
独自クラスでEqualsだけを実装しても、==の結果は変わらない点に注意しましょう。
11-4. CompareToが0ならEqualsもtrueになりますか
多くの型では、CompareToが0ならEqualsもtrueになるように設計されています。
ただし、独自クラスでは実装次第です。
たとえば、CompareToは価格で比較し、EqualsはIDで比較するように実装すると、CompareToが0でもEqualsがfalseになる可能性があります。
混乱を避けるため、CompareToとEqualsの整合性はできるだけ保つべきです。
11-5. nullを安全に比較するにはどうすればよいですか
文字列の場合は、静的メソッドのstring.Equalsを使うと安全です。
C#string? a = null;
string? b = "abc";
bool result = string.Equals(a, b, StringComparison.Ordinal);
並び順を比較する場合は、string.Compareを使えます。
C#int result = string.Compare(a, b, StringComparison.Ordinal);
独自クラスでは、CompareToやEqualsの中でnullを明示的に扱います。
C#public int CompareTo(Product? other)
{
if (other is null) return 1;
return Price.CompareTo(other.Price);
}
11-6. 大文字小文字を無視して比較するにはどう書きますか
大文字小文字を無視して等価判定する場合は、StringComparison.OrdinalIgnoreCaseを使います。
C#string a = "CSharp";
string b = "csharp";
bool result = string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
Console.WriteLine(result); // true
並び順を比較する場合は、string.Compareに指定します。
C#int result = string.Compare(
a,
b,
StringComparison.OrdinalIgnoreCase
);
ToLowerやToUpperで変換してから比較するよりも、StringComparisonを指定する方が明確で安全です。
まとめ
C#の比較処理では、目的に応じて適切な方法を選ぶことが重要です。
等しいかどうかを判定するならEqualsや==、大小や順序を判定するならCompareToやString.Compare、数値や日付の大小比較なら<、>、<=、>=を使います。
文字列比較では、StringComparisonを明示することで、カルチャ依存や大文字小文字の違いによるバグを防ぎやすくなります。
独自クラスでは、等価判定にはEqualsとGetHashCode、型安全な比較にはIEquatable<T>、並び順の定義にはIComparable<T>、外部から比較ルールを渡す場合にはIComparer<T>を使います。
csharp compareを正しく理解するポイントは、次の通りです。
| 目的 | 使うもの |
|---|---|
| 等しいか判定したい | Equals、== |
| 等しくないか判定したい | != |
| 大小を比較したい | <、>、<=、>= |
| 順序を比較したい | CompareTo、String.Compare |
| 文字列を安全に比較したい | string.Equals + StringComparison |
| 独自クラスの等価性を定義したい | Equals、GetHashCode、IEquatable<T> |
| 独自クラスの並び順を定義したい | IComparable<T>、IComparer<T> |
比較処理は一見単純に見えますが、==とEqualsの違い、CompareToの戻り値、文字列比較のカルチャ差、nullの扱いなど、注意すべき点が多くあります。
それぞれの役割を理解し、目的に合った比較方法を選ぶことで、読みやすく安全なC#コードを書けるようになります。

