C#: 列挙型と構造体
1. 列挙型 (enum)
列挙型(enum)は、名前付きの定数のセットを定義するために使用される値型であり、コードの可読性を高め、型安全性を確保します。
(1) 列挙型の定義
列挙型を定義するには、enum キーワードを使用します。デフォルトの基底型は int で、0 から始まり、順次番号が振られます。
▶ サンプル
CSHARP
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season current = Season.Autumn;
System.Console.WriteLine(current);
System.Console.WriteLine((int)current);
}
}
TEXT
📖 参照専用
Autumn
2
(2) 基本型と明示的な代入
列挙型の基底型は、デフォルトで int になります。別の整数型を明示的に指定したり、メンバに特定の値を割り当てたりすることもできます。
▶ サンプル
CSHARP
enum Priority : byte
{
Low = 1,
Medium = 2,
High = 3
}
enum HttpStatusCode
{
OK = 200,
NotFound = 404,
InternalServerError = 500
}
class Program
{
static void Main()
{
Priority p = Priority.High;
System.Console.WriteLine((byte)p);
System.Console.WriteLine((int)HttpStatusCode.NotFound);
}
}
TEXT
📖 参照専用
3
404
(3) 列挙型の変換
列挙型とその基底型は、互いに明示的に型変換することができます。
▶ サンプル
CSHARP
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season s = Season.Spring;
int value = (int)s;
System.Console.WriteLine(value);
Season fromInt = (Season)2;
System.Console.WriteLine(fromInt);
}
}
TEXT
📖 参照専用
0
Autumn
2. 列挙型クラスのメソッド
System.Enum クラスは、列挙型の操作を行うためのいくつかの静的メソッドを提供しています。
(1) 一般的な手法
| 方法 | 説明 |
|---|---|
Enum.Parse |
文字列を列挙型に変換します。失敗した場合は例外をスローします |
Enum.TryParse |
文字列を列挙型に変換します。失敗した場合は false を返します |
Enum.GetName |
列挙型の値の名前を取得する |
Enum.GetValues |
すべての列挙型値を取得する |
Enum.IsDefined |
値が列挙型で定義されているかどうかを判定します |
▶ サンプル
CSHARP
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season parsed = (Season)Enum.Parse(typeof(Season), "Summer");
System.Console.WriteLine(parsed);
bool ok = Enum.TryParse<Season>("Winter", out Season result);
System.Console.WriteLine($"{ok}, {result}");
string name = Enum.GetName(typeof(Season), 2);
System.Console.WriteLine(name);
foreach (Season s in Enum.GetValues(typeof(Season)))
{
System.Console.WriteLine($"{s} = {(int)s}");
}
System.Console.WriteLine(Enum.IsDefined(typeof(Season), 1));
System.Console.WriteLine(Enum.IsDefined(typeof(Season), 99));
}
}
TEXT
📖 参照専用
Summer
True, Winter
Autumn
Spring = 0
Summer = 1
Autumn = 2
Winter = 3
True
False
3. 構造体 (struct)
構造体(struct)は、少量のデータをカプセル化するのに適した値型です。構造体はスタック上に格納され、代入を行うと値全体がコピーされます。
(1) 構造体の定義
構造体を定義するには、struct キーワードを使用します。
▶ サンプル
CSHARP
struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
class Program
{
static void Main()
{
Point p1 = new Point(3, 4);
Point p2 = p1;
p2.X = 10;
System.Console.WriteLine(p1);
System.Console.WriteLine(p2);
}
}
TEXT
📖 参照専用
(3, 4)
(10, 4)
4. 構造体とクラス
構造体とクラスには、意味論や動作において重要な違いがあります。
(1) 比較
| 機能 | 構造体 | クラス |
|---|---|---|
| 型カテゴリ | 値型 | 参照型 |
| 格納場所 | スタック | ヒープ |
| 代入時の挙動 | 値をコピー | 参照をコピー |
| 継承 | 未対応(継承不可) | 対応 |
| インターフェース | 実装可能 | 実装可能 |
| デストラクタ | なし | あり |
| デフォルト値 | すべてのフィールドがゼロ | null |
▶ サンプル
CSHARP
struct PointStruct
{
public int X;
public int Y;
}
class PointClass
{
public int X;
public int Y;
}
class Program
{
static void Main()
{
PointStruct s1 = new PointStruct();
PointStruct s2 = s1;
s2.X = 5;
System.Console.WriteLine($"struct: s1.X={s1.X}, s2.X={s2.X}");
PointClass c1 = new PointClass();
PointClass c2 = c1;
c2.X = 5;
System.Console.WriteLine($"class: c1.X={c1.X}, c2.X={c2.X}");
}
}
TEXT
📖 参照専用
struct: s1.X=0, s2.X=5
class: c1.X=5, c2.X=5
5. 構造体(Struct)を使うべき場面
構造体がクラスよりも常に優れているとは限りません。次のような場合には、構造体の使用を検討してください:
- データサイズは小さい(16バイト以下が推奨)
- 論理的には単一の値(座標や色など)を表す
- 頻繁に生成・破棄されるため、寿命が短い
- 継承は必要ありません
代表的な例:Point、Color、DateTime、TimeSpan。
⚠️ 上記の条件が満たされない場合は、頻繁なコピーによるパフォーマンスの低下を防ぐために、クラスを使用してください。
6. readonly struct (C# 7.2+)
readonly struct は、すべてのフィールドが不変であることを保証します。コンパイラは、いかなる変更操作も阻止します。
▶ サンプル
CSHARP
readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public double DistanceTo(Point other)
{
int dx = X - other.X;
int dy = Y - other.Y;
return System.Math.Sqrt(dx * dx + dy * dy);
}
}
class Program
{
static void Main()
{
Point p1 = new Point(3, 4);
Point p2 = new Point(0, 0);
System.Console.WriteLine(p1.DistanceTo(p2));
}
}
TEXT
📖 参照専用
5
7. record 構造体 (C# 10+)
record struct は、構造体(struct)の値セマンティクスと、レコードの値による等価性および簡潔な構文を組み合わせたもので、不変性や自動生成されたメンバを必要とするシナリオに適しています。
▶ サンプル
CSHARP
public class Point : IEquatable<Point>
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public Point With(int? x = null, int? y = null)
{
return new Point(x ?? this.X, y ?? this.Y);
}
public bool Equals(Point other)
{
return other != null && X == other.X && Y == other.Y;
}
public override bool Equals(object obj) => Equals(obj as Point);
public override int GetHashCode() => System.HashCode.Combine(X, Y);
public override string ToString() => $"Point {{ X = {X}, Y = {Y} }}";
}
class Program
{
static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.Console.WriteLine(p1 == p2);
System.Console.WriteLine(p1.With(x: 10));
}
}
TEXT
📖 参照専用
True
Point { X = 10, Y = 2 }
💡
record struct は、Equals、ToString、with の式やその他のメンバーを自動生成し、定型コードを削減します。
❓ よくある質問
Q enum 型で整数型以外の型を指定することはできますか?
A いいえ、指定できるのは整数型のみです(byte、sbyte、short、ushort、int、uint、long、ulong)。
Q 構造体にはコンストラクタを定義できますか?
A はい、パラメータ付きコンストラクタを定義することはできますが、パラメータなしのコンストラクタはコンパイラによって自動的に生成されるため、独自に定義することはできません。
Q structはインターフェースを実装できますか?
A はい、structはインターフェースを実装できます。structがインターフェース型に変換される際(値から参照への変換)、ボクシングが発生します。
Q 読み取り専用構造体のフィールドで
set を使用できますか?A いいえ、読み取り専用構造体のすべてのフィールドは読み取り専用でなければならず、コンストラクタを通じてのみ初期化できます。
Q enum と const の違いは何ですか?
A enum は型安全性を備えた名前付き定数の集合であり、const は型をグループ化する機能を持たない単一のコンパイル時定数です。
📖 まとめ
- 列挙型は
enumで定義され、デフォルトの基底型はintで、0 からインクリメントされます。 - 基礎となる型およびメンバーの値を明示的に指定できる
Enum.Parse/TryParse/GetName/GetValues/IsDefined列挙型の操作を提供する- 列挙型と基底型は、互いに明示的に型変換することができます
- 構造体は
structで定義され、値型であり、スタック上に格納される - 構造体の代入では値がコピーされ、クラスの代入では参照がコピーされる
- 構造体は継承できませんが、インターフェースを実装することはできます
- 適したシナリオ:データサイズが小さい(16バイト以下)、不変、有効期間が短い
readonly struct(C# 7.2+) により、すべてのフィールドが不変であることが保証されるrecord struct(C# 10+) は、値セマンティクスとレコードの簡潔な構文を組み合わせたものです
📝 練習問題
enum Direction { North, East, South, West }を定義し、すべての方向を順に処理して、その名称と整数値を出力するコードを記述する- 幅と高さを指定して
struct Rectangleを定義し、面積を計算するメソッドを用意し、代入によって値がコピーされることを確認する - 前の演習で作成した
Rectangleをreadonly structに変更し、すべてのフィールドが読み取り専用であり、コンストラクタを通じて初期化されるようにしてください Enum.TryParseおよびEnum.IsDefinedを使用して、文字列を受け取り、対応する列挙型値またはデフォルト値を返す、安全な列挙型解析メソッドを作成してください。structとclassをメソッドの引数として渡した際の動作の違い(値渡しと参照渡し)を比較するコードを作成してください。