C# PR

【C#】structとclassの違い!5分で理解する使い分けのコツ

【C#】structとclassの違い!5分で理解する使い分けのコツ
記事内に商品プロモーションを含む場合があります

こんにちは!
今日は、C#のstructとclassの違いについて詳しく解説していきます。

C#のstructとclassって何が違うの?どっちを使えばいいんだろう?」

そんな疑問を持っている方も多いのではないでしょうか。

C#におけるstructとclassは、データと振る舞いをまとめる方法として使われます。
structは値型、classは参照型という特徴があり、それぞれに適した使用場面があります。
適切に使い分けることで、効率的なプログラムを書くことができるのです。

この記事を読めば、structとclassの違いがスッキリ理解できて、プロジェクトでの適切な選択ができるようになります。

この記事でわかること
  • structとclassの基本的な違い
  • それぞれを使うべき場面と具体例
  • パフォーマンスの観点からの比較
  • コード例で見る使い分け方
  • よくある間違いと注意点

structとclassの基本的な違い

C#でstructclassの違いを理解することは、効率的なプログラミングの第一歩です。
まずは、この二つの基本的な違いについて見ていきましょう。

値型と参照型の概念

structは値型、classは参照型という大きな違いがあります。
これは、データがメモリ上でどのように扱われるかを決定する重要な特徴なんです。

値型のstructは、データそのものがスタックと呼ばれるメモリ領域に直接格納されます。
一方、参照型のclassは、実際のデータはヒープと呼ばれる別のメモリ領域に格納され、スタックにはそのデータへの参照(アドレス)が格納されるんです。

この違いは、変数の代入や関数の引数として渡す際の動作に大きな影響を与えます。
値型のstructは代入時にデータのコピーが作成されますが、参照型のclassは参照のコピーだけが作成されます。

例えば、次のようなコードを見てみましょう。


struct Point {
  public int X { get; set; }
  public int Y { get; set; }
  public Point(int x, int y) {
    X = x;
    Y = y;
  }
}

var point1 = new Point(1, 2);  // struct
var point2 = point1;
point2.X = 3;

Console.WriteLine(point1.X); // 1
Console.WriteLine(point2.X); // 3

class Person
{
  public string Name { get; set; }

  public Person(string name)
  {
    Name = name;
  }
}

var person1 = new Person("Alice");  // class
var person2 = person1;
person2.Name = "Bob";

Console.WriteLine(person1.Name); // Bob
Console.WriteLine(person2.Name); // Bob

このコードの実行後、point1.Xは1のままですが、person1.Nameは”Bob”に変更されています。
これは、structがデータのコピーを作成するのに対し、classが参照のコピーを作成するためです。

メモリ上の扱われ方の違い

先ほど触れたように、structclassはメモリ上で異なる扱いを受けます。
この違いは、プログラムのパフォーマンスに影響を与える可能性があるんです。

structはスタックに直接格納されるため、生成と破棄が高速です。
また、データのサイズが小さい場合はメモリ効率も良いでしょう。

一方、classはヒープに格納されるため、生成と破棄にはやや時間がかかります。
しかし、大きなサイズのデータや、継承などの複雑な構造を持つ場合に適しています。

これらの違いを理解することで、適切な場面でstructclassを使い分けることができるようになります。
次の章では、それぞれを使うべき具体的な場面について詳しく見ていきましょう。

structを使うべき場面

structは特定の状況下で非常に有用です。
ここでは、structを使うべき代表的な場面について詳しく見ていきましょう。

小さなデータ構造の表現

structは、小さなデータ構造を表現するのに適しています。
例えば、2次元の座標や色情報、日付データなどです。

小さなデータ構造をstructで表現することで、メモリ効率が向上し、プログラムの実行速度も改善される可能性があります。

具体的には、16バイト以下のデータ構造はstructで表現するのが良いとされています。
これは、C#の実装上の最適化に関連しています。

頻繁に生成・破棄される軽量オブジェクト

structは値型なので、スタック上に直接格納されます。
そのため、頻繁に生成・破棄される軽量なオブジェクトを表現するのに適しているんです。

例えば、大量の粒子を扱うシミュレーションや、頻繁に計算が行われる数学的な操作などで活用できます。
これらの場合、classを使うとヒープ上のメモリ割り当てと解放が頻繁に発生し、パフォーマンスに影響を与える可能性があります。

具体例: 座標を表すPoint構造体

2次元の座標を表すPoint構造体を例に、structの適切な使用方法を見てみましょう。


public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
}

// 使用例
var point1 = new Point(0, 0);
var point2 = new Point(3, 4);
var distance = point1.DistanceTo(point2);
Console.WriteLine(distance); // 5

このように、Point構造体は座標値(X, Y)と、他の点との距離を計算するメソッドを持っています。
座標は小さなデータ構造で、頻繁に生成・操作される可能性が高いため、structとして定義するのが適切です。

structを使用することで、大量の点を扱う場合でもメモリ効率が良く、高速な処理が期待できます。
また、値型なので意図しない参照の共有を避けられるのも利点ですね。

以上のように、structは小さなデータ構造や頻繁に生成・破棄される軽量オブジェクトに適しています。
ただし、大きなデータや複雑な振る舞いを持つ場合はclassの使用を検討する必要があります。

classを使うべき場面

classstructとは異なる特性を持ち、特定の状況で威力を発揮します。
ここでは、classを使うべき代表的な場面について詳しく見ていきましょう。

複雑なデータ構造や振る舞いを持つオブジェクト

classは、複雑なデータ構造や多くの振る舞い(メソッド)を持つオブジェクトを表現するのに適しています。
例えば、ユーザー情報、商品データ、ゲームのキャラクターなどがこれに当たります。

複雑なデータや振る舞いをclassで表現することで、コードの見通しが良くなり、保守性が向上します。

また、classは参照型なので、大きなデータを扱う際にもメモリ効率が良いんです。
データのコピーではなく参照のコピーが行われるため、メモリ使用量を抑えられます。

継承やポリモーフィズムが必要な場合

classは継承をサポートしているため、オブジェクト指向プログラミングの重要な概念である継承やポリモーフィズムを活用したい場合に適しています。

例えば、動物を表す基底クラスがあり、それを継承して犬や猫のクラスを作成する場合などが考えられます。
これにより、コードの再利用性が高まり、柔軟な設計が可能になります。


public class Animal
{
    public virtual void MakeSound() { }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("ワン!");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("ニャー!");
    }
}

このような継承を用いた設計はclassでのみ可能で、structでは実現できません。

具体例: ユーザー情報を表すUserクラス

ユーザー情報を表すUserクラスを例に、classの適切な使用方法を見てみましょう。


public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime DateOfBirth { get; set; }
    private List<string> _roles = new List<string>();

    public User(string name, string email, DateTime dateOfBirth)
    {
        Name = name;
        Email = email;
        DateOfBirth = dateOfBirth;
    }

    public void AddRole(string role)
    {
        _roles.Add(role);
    }

    public bool HasRole(string role)
    {
        return _roles.Contains(role);
    }

    public int GetAge()
    {
        return DateTime.Now.Year - DateOfBirth.Year;
    }
}

// 使用例
var user = new User("田中太郎", "tanaka@example.com", new DateTime(1990, 1, 1));
user.AddRole("管理者");
var isAdmin = user.HasRole("管理者");
var age = user.GetAge();

Console.WriteLine(isAdmin); // true
Console.WriteLine(age); // 34

このように、Userクラスはユーザーの基本情報(名前、メールアドレス、生年月日)と、役割の追加・確認、年齢計算などの機能を持っています。
ユーザー情報は複雑なデータ構造で、様々な操作が必要なため、classとして定義するのが適切です。

classを使用することで、ユーザー情報の一貫性を保ちつつ、必要に応じて新しい機能を追加することができます。
また、参照型なので、ユーザー情報を複数の場所で共有する際にも効率的です。

以上のように、classは複雑なデータ構造や振る舞いを持つオブジェクト、継承やポリモーフィズムが必要な場合に適しています。
ただし、小さなデータ構造や頻繁に生成・破棄される軽量オブジェクトの場合はstructの使用を検討する必要があります。

パフォーマンスの観点からの比較

structclassの選択は、プログラムのパフォーマンスに大きな影響を与えることがあります。
ここでは、メモリ使用量と処理速度の観点から、両者を比較してみましょう。

メモリ使用量の違い

structclassでは、メモリの使用方法が異なります。
この違いは、特に大量のオブジェクトを扱う場合に顕著になります。

structはスタックに直接格納されるため、小さなデータ構造の場合はメモリ効率が良くなります。
一方、classはヒープに格納され、スタックには参照(ポインタ)のみが格納されます。

大量の小さなオブジェクトを扱う場合、structを使用することでメモリ使用量を削減できる可能性があります。

例えば、100万個の3次元座標を扱う場合を考えてみましょう。


public struct Point3D
{
    public float X, Y, Z;
}

// structを使用した場合
var points1 = new Point3D[1000000];

// classを使用した場合
public class Point3DClass
{
    public float X, Y, Z;
}
var points2 = new Point3DClass[1000000];

この場合、structを使用したpoints1は約12MB(3 * 4バイト * 100万)のメモリを使用しますが、classを使用したpoints2は追加のオーバーヘッドがあるため、より多くのメモリを使用します。

処理速度の違い

処理速度の面でも、structclassには違いがあります。
一般的に、小さなデータ構造の場合はstructの方が高速です。

structはスタックに直接格納されるため、アクセスが高速です。
また、値型なので、メソッド呼び出し時のボックス化(値型から参照型への変換)も発生しません。

一方、classはヒープに格納されるため、アクセスにはわずかなオーバーヘッドが発生します。
ただし、大きなデータ構造の場合、structのコピーにかかる時間がclassの参照のコピーよりも長くなる可能性があります。

ベンチマーク結果の例示

以下は、簡単な2次元座標をstructclassで実装し、大量の座標を生成して距離計算を行った場合のベンチマーク結果の例です。


using System;
using System.Diagnostics;

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
}

public class PointClass
{
    public int X { get; set; }
    public int Y { get; set; }

    public PointClass(int x, int y)
    {
        X = x;
        Y = y;
    }

    public double DistanceTo(PointClass other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }
}

class Program
{
    static void Main()
    {
        const int count = 10000000;
        var structPoints = new Point[count];
        var classPoints = new PointClass[count];

        var structTime = MeasureTime(() => {
            for (int i = 0; i < count; i++)
            {
                structPoints[i] = new Point(i, i);
            }
            var totalDistance = 0.0;
            for (int i = 1; i < count; i++)
            {
                totalDistance += structPoints[i].DistanceTo(structPoints[i - 1]);
            }
        });

        var classTime = MeasureTime(() => {
            for (int i = 0; i < count; i++)
            {
                classPoints[i] = new PointClass(i, i);
            }
            var totalDistance = 0.0;
            for (int i = 1; i < count; i++)
            {
                totalDistance += classPoints[i].DistanceTo(classPoints[i - 1]);
            }
        });

        Console.WriteLine($"Struct time: {structTime}ms");
        Console.WriteLine($"Class time: {classTime}ms");
    }

    static long MeasureTime(Action action)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

この結果、structを使用した場合の方がclassを使用した場合よりも高速に処理できることが多いです。
ただし、実際のパフォーマンスは使用するデータ構造のサイズや操作の内容によって変わるため、具体的な使用場面でのベンチマークが重要です。

以上のように、パフォーマンスの観点からはstructが有利な場合が多いですが、データの大きさや操作の内容によってはclassの方が適している場合もあります。
適切な選択をするためには、実際の使用場面でのベンチマークと、コードの可読性やメンテナンス性のバランスを考慮することが大切です。

キャリア形成/給与還元
ひとつひとつ真摯に向き合う企業
ONE_WEDGE社員募集

株式会社 ONE WEDGEでは、新たな仲間を募集しています!

私たちと一緒に、革新的で充実したキャリアを築きませんか?
当社は、従業員が仕事と私生活のバランスを大切にできるよう、充実した福利厚生を整えています。

  • 完全週休2日制(土日休み)で、祝日や夏季休暇、年末年始休暇もしっかり保証!
  • 様々な休暇制度(有給、慶弔、産前・産後、育児、バースデー休暇)を完備!
  • 従業員の成長と健康を支援するための表彰制度、資格取得支援、健康促進手当など!
  • 生活を支えるテレワーク手当、記事寄稿手当、結婚祝金・出産祝金など、様々な手当を提供!
  • 自己啓発としての書籍購入制度や、メンバー間のコミュニケーションを深める交流費補助!
  • 成果に応じた決算賞与や、リファラル採用手当、AI手当など、頑張りをしっかり評価!
  • ワークライフバランスを重視し、副業もOK!

株式会社 ONE WEDGEでは、一人ひとりの従業員が自己実現できる環境を大切にしています。
共に成長し、刺激を与え合える仲間をお待ちしております。
あなたの能力と熱意を、ぜひ当社で発揮してください。
ご応募お待ちしております!

ホームページ、採用情報は下記ボタンからご確認ください!

応募、ご質問など、LINEでお気軽にご相談ください♪

まとめ

C#プログラミングにおいて、structclassの適切な使い分けは非常に重要です。
この記事では、両者の特徴と使用場面について詳しく解説しました。

値型であるstructは、小さなデータ構造や頻繁に生成・破棄されるオブジェクトに適しています。
メモリ効率が良く、パフォーマンスの向上が期待できるんですよ。
一方、参照型のclassは、複雑なデータ構造や多くのメソッドを持つオブジェクト、継承が必要な場合に適しています。
柔軟性が高く、オブジェクト指向プログラミングの利点を活かせるわけです。

選択の際は、以下のポイントを考慮しましょう。

  • データのサイズ:16バイト以下ならstructが有利かも
  • 生成頻度:頻繁ならstructがおすすめ
  • データの複雑さ:複雑ならclassが適切
  • 値の独立性:独立性が必要ならstruct
  • 継承の必要性:継承を使うならclass一択

適切な選択により、メモリ使用量の削減や処理速度の向上が期待できます。
さらに、コードの可読性も向上するんです。
ただし、プロジェクトの要件やチームの開発スタイルも考慮に入れて、どちらを使うか検討しましょう。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です