[C#] readonly修飾子の使い方 – 読み取り専用変数の定義

readonly修飾子は、C#でフィールドを読み取り専用にするために使用されます。

readonlyフィールドは、宣言時またはコンストラクタ内でのみ値を設定でき、その後は変更できません。

これにより、オブジェクトの不変性を部分的に保証できます。

readonlyはクラスや構造体のインスタンスフィールドに適用され、静的フィールドにも使用可能です。

例えば、readonly int myValue = 10;のように定義し、コンストラクタ内でのみ値を変更できます。

この記事でわかること
  • readonly修飾子の基本的な役割
  • readonlyフィールドの定義方法
  • readonlyフィールドの利点
  • readonlyフィールドの応用例
  • readonlyフィールドの制約事項

この記事を参考に、C#プログラミングにおけるreadonly修飾子の活用を検討してみることをお勧めします。

目次から探す

readonly修飾子とは

C#におけるreadonly修飾子は、フィールドの値を不変にするために使用されます。

readonly修飾子が付けられたフィールドは、オブジェクトのインスタンスが作成された後に再代入することができません。

これにより、データの整合性を保ち、意図しない変更を防ぐことができます。

readonly修飾子の基本的な役割

readonly修飾子は、フィールドが初期化された後に変更されないことを保証します。

これにより、オブジェクトの状態を不変に保つことができ、特にスレッドセーフなプログラミングにおいて重要です。

class Example
{
    public readonly int ReadOnlyField;
    public Example(int value)
    {
        ReadOnlyField = value; // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Example example = new Example(10);
        // example.ReadOnlyField = 20; // エラー: readonlyフィールドは再代入できません
        Console.WriteLine(example.ReadOnlyField);
    }
}
10

constとの違い

const修飾子readonly修飾子は似ていますが、いくつかの重要な違いがあります。

スクロールできます
特徴constreadonly
初期化のタイミング宣言時に必須コンストラクタ内で可能
値の変更不可能インスタンス作成時のみ可能
型の制約コンパイル時に決定実行時に決定

static readonlyとの違い

static readonlyは、クラス全体で共有される不変のフィールドを定義します。

これに対し、readonlyはインスタンスごとに異なる値を持つことができます。

class Example
{
    public static readonly int StaticReadOnlyField = 100; // クラス全体で共有
    public readonly int InstanceReadOnlyField;
    public Example(int value)
    {
        InstanceReadOnlyField = value; // インスタンスごとに異なる値
    }
}
class Program
{
    static void Main(string[] args)
    {
        Example example1 = new Example(10);
        Example example2 = new Example(20);
        
        Console.WriteLine(Example.StaticReadOnlyField); // 100
        Console.WriteLine(example1.InstanceReadOnlyField); // 10
        Console.WriteLine(example2.InstanceReadOnlyField); // 20
    }
}
100
10
20

readonlyが使われる場面

readonly修飾子は、以下のような場面で特に有用です。

  • 不変オブジェクトの作成: オブジェクトの状態を変更できないようにすることで、バグを防ぎます。
  • スレッドセーフなプログラミング: 複数のスレッドから同時にアクセスされる場合でも、データの整合性を保つことができます。
  • API設計: 外部からの変更を防ぎ、クラスの使用方法を明確にします。

readonlyフィールドの定義方法

C#におけるreadonlyフィールドの定義方法には、いくつかの異なるアプローチがあります。

これにより、プログラマーは必要に応じてフィールドを不変にすることができます。

インスタンスフィールドでのreadonlyの使用

インスタンスフィールドにreadonly修飾子を付けることで、各インスタンスごとに異なる不変の値を持つフィールドを定義できます。

class Person
{
    public readonly string Name; // インスタンスフィールド
    public Person(string name)
    {
        Name = name; // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Person person1 = new Person("Alice");
        Person person2 = new Person("Bob");
        
        Console.WriteLine(person1.Name); // Alice
        Console.WriteLine(person2.Name); // Bob
    }
}
Alice
Bob

静的フィールドでのreadonlyの使用

static readonly修飾子を使用することで、クラス全体で共有される不変のフィールドを定義できます。

これにより、すべてのインスタンスで同じ値を持つフィールドを作成できます。

class Configuration
{
    public static readonly string AppName = "MyApplication"; // 静的フィールド
}
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Configuration.AppName); // MyApplication
    }
}
MyApplication

コンストラクタでの初期化

readonlyフィールドは、コンストラクタ内で初期化することができます。

これにより、インスタンスが作成される際に動的に値を設定することが可能です。

class Circle
{
    public readonly double Radius; // readonlyフィールド
    public Circle(double radius)
    {
        Radius = radius; // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Circle circle = new Circle(5.0);
        Console.WriteLine(circle.Radius); // 5.0
    }
}
5.0

宣言時の初期化

readonlyフィールドは、宣言時に初期化することもできます。

この方法では、コンストラクタを使用せずに値を設定できます。

class Rectangle
{
    public readonly int Width = 10; // 宣言時に初期化
    public readonly int Height = 5;  // 宣言時に初期化
}
class Program
{
    static void Main(string[] args)
    {
        Rectangle rectangle = new Rectangle();
        Console.WriteLine($"Width: {rectangle.Width}, Height: {rectangle.Height}"); // Width: 10, Height: 5
    }
}
Width: 10, Height: 5

readonlyフィールドの使用例

readonlyフィールドは、さまざまな場面で使用されます。

以下に、具体的な使用例を示します。

クラス内でのreadonlyフィールドの定義

クラス内でreadonlyフィールドを定義することで、オブジェクトの状態を不変に保つことができます。

以下の例では、Personクラスreadonlyフィールドを定義しています。

class Person
{
    public readonly string Name; // readonlyフィールド
    public readonly int Age;      // readonlyフィールド
    public Person(string name, int age)
    {
        Name = name; // コンストラクタで初期化
        Age = age;   // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Person person = new Person("Alice", 30);
        Console.WriteLine($"Name: {person.Name}, Age: {person.Age}"); // Name: Alice, Age: 30
    }
}
Name: Alice, Age: 30

構造体でのreadonlyフィールドの使用

構造体でもreadonlyフィールドを使用することができます。

以下の例では、Point構造体にreadonlyフィールドを定義しています。

struct Point
{
    public readonly int X; // readonlyフィールド
    public readonly int Y; // readonlyフィールド
    public Point(int x, int y)
    {
        X = x; // コンストラクタで初期化
        Y = y; // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Point point = new Point(5, 10);
        Console.WriteLine($"X: {point.X}, Y: {point.Y}"); // X: 5, Y: 10
    }
}
X: 5, Y: 10

readonlyフィールドを持つオブジェクトの作成

readonlyフィールドを持つオブジェクトは、コンストラクタを通じて初期化されます。

以下の例では、Bookクラスを定義し、readonlyフィールドを持つオブジェクトを作成しています。

class Book
{
    public readonly string Title; // readonlyフィールド
    public readonly string Author; // readonlyフィールド
    public Book(string title, string author)
    {
        Title = title; // コンストラクタで初期化
        Author = author; // コンストラクタで初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Book book = new Book("C# Programming", "John Doe");
        Console.WriteLine($"Title: {book.Title}, Author: {book.Author}"); // Title: C# Programming, Author: John Doe
    }
}
Title: C# Programming, Author: John Doe

readonlyフィールドの参照とアクセス

readonlyフィールドは、オブジェクトが作成された後に変更できませんが、参照やアクセスは可能です。

以下の例では、Circleクラスreadonlyフィールドにアクセスしています。

class Circle
{
    public readonly double Radius; // readonlyフィールド
    public Circle(double radius)
    {
        Radius = radius; // コンストラクタで初期化
    }
    public double GetArea() // 面積を計算するメソッド
    {
        return Math.PI * Radius * Radius; // 面積の計算
    }
}
class Program
{
    static void Main(string[] args)
    {
        Circle circle = new Circle(3.0);
        Console.WriteLine($"Radius: {circle.Radius}, Area: {circle.GetArea()}"); // Radius: 3.0, Area: 28.274333882308138
    }
}
Radius: 3.0, Area: 28.274333882308138

readonlyフィールドの利点

readonlyフィールドを使用することには、いくつかの重要な利点があります。

これらの利点は、プログラムの信頼性や可読性を向上させるのに役立ちます。

不変性の保証

readonlyフィールドは、一度初期化されるとその値を変更できないため、不変性を保証します。

これにより、オブジェクトの状態が予測可能になり、他の部分のコードがそのフィールドの値を変更することがないため、デバッグが容易になります。

class ImmutableData
{
    public readonly int Value;
    public ImmutableData(int value)
    {
        Value = value; // 初期化時にのみ設定
    }
}
// 使用例
ImmutableData data = new ImmutableData(100);
// data.Value = 200; // エラー: readonlyフィールドは再代入できません

バグの防止

readonlyフィールドを使用することで、意図しない変更を防ぎ、バグを減少させることができます。

特に大規模なプロジェクトでは、他の開発者がフィールドの値を変更することがないため、コードの信頼性が向上します。

class Configuration
{
    public readonly string ApiKey;
    public Configuration(string apiKey)
    {
        ApiKey = apiKey; // 初期化時に設定
    }
}
// 使用例
Configuration config = new Configuration("12345");
// config.ApiKey = "67890"; // エラー: readonlyフィールドは再代入できません

パフォーマンスへの影響

readonlyフィールドは、コンパイラによって最適化されることがあり、パフォーマンスに良い影響を与えることがあります。

特に、オブジェクトの状態が不変であることが保証されているため、キャッシュやメモリ管理が効率的に行われる可能性があります。

class Circle
{
    public readonly double Radius;
    public Circle(double radius)
    {
        Radius = radius; // 初期化時に設定
    }
    public double GetCircumference() // 周の計算
    {
        return 2 * Math.PI * Radius; // 計算
    }
}
// 使用例
Circle circle = new Circle(5.0);
Console.WriteLine(circle.GetCircumference()); // 31.41592653589793

他のメンバーとの相互作用

readonlyフィールドは、他のメンバー(メソッドやプロパティ)との相互作用を明確にします。

特に、readonlyフィールドを使用することで、オブジェクトの状態が変更されないことを前提にしたメソッドを設計することができ、コードの可読性が向上します。

class Rectangle
{
    public readonly int Width;
    public readonly int Height;
    public Rectangle(int width, int height)
    {
        Width = width; // 初期化時に設定
        Height = height; // 初期化時に設定
    }
    public int GetArea() // 面積を計算
    {
        return Width * Height; // 計算
    }
}
// 使用例
Rectangle rectangle = new Rectangle(4, 5);
Console.WriteLine(rectangle.GetArea()); // 20

これにより、GetAreaメソッドは、WidthHeightが変更されないことを前提にしているため、より安全に使用できます。

readonly修飾子の応用

readonly修飾子は、さまざまなプログラミングパターンや設計において非常に有用です。

以下に、readonly修飾子の具体的な応用例を示します。

イミュータブルクラスの作成

イミュータブルクラスは、インスタンスが作成された後にその状態を変更できないクラスです。

readonlyフィールドを使用することで、イミュータブルなオブジェクトを簡単に作成できます。

class ImmutablePoint
{
    public readonly int X; // readonlyフィールド
    public readonly int Y; // readonlyフィールド
    public ImmutablePoint(int x, int y)
    {
        X = x; // 初期化時に設定
        Y = y; // 初期化時に設定
    }
}
// 使用例
ImmutablePoint point = new ImmutablePoint(10, 20);
// point.X = 30; // エラー: readonlyフィールドは再代入できません
Console.WriteLine($"X: {point.X}, Y: {point.Y}"); // X: 10, Y: 20

readonlyとプロパティの組み合わせ

readonlyフィールドをプロパティと組み合わせることで、外部からの読み取りは可能でありながら、内部での変更を防ぐことができます。

これにより、データのカプセル化が強化されます。

class Employee
{
    private readonly string name; // readonlyフィールド
    public Employee(string name)
    {
        this.name = name; // コンストラクタで初期化
    }
    public string Name // プロパティ
    {
        get { return name; } // 読み取り専用
    }
}
// 使用例
Employee employee = new Employee("Alice");
Console.WriteLine(employee.Name); // Alice
// employee.Name = "Bob"; // エラー: プロパティは読み取り専用

readonlyとスレッドセーフなプログラミング

readonlyフィールドは、スレッドセーフなプログラミングにおいて非常に重要です。

複数のスレッドが同じオブジェクトにアクセスする場合でも、readonlyフィールドを使用することで、データの整合性を保つことができます。

class Configuration
{
    public static readonly string ConnectionString = "Server=myServer;Database=myDB;"; // static readonlyフィールド
}
// 使用例
Console.WriteLine(Configuration.ConnectionString); // Server=myServer;Database=myDB;

このように、static readonlyフィールドを使用することで、アプリケーション全体で共有される設定情報を安全に管理できます。

readonlyとデザインパターン

readonly修飾子は、いくつかのデザインパターンにおいても役立ちます。

特に、シングルトンパターンやファクトリーパターンでは、readonlyフィールドを使用して不変の状態を保つことができます。

class Singleton
{
    private static readonly Singleton instance = new Singleton(); // static readonlyフィールド
    private Singleton() { } // コンストラクタは非公開
    public static Singleton Instance
    {
        get { return instance; } // インスタンスを返す
    }
}
// 使用例
Singleton singleton = Singleton.Instance;
Console.WriteLine(singleton.GetHashCode()); // 一意のハッシュコード

このように、シングルトンパターンでは、readonlyフィールドを使用してインスタンスを一度だけ作成し、その後は同じインスタンスを返すことができます。

これにより、アプリケーション全体で一貫した状態を保つことができます。

readonly修飾子の制約

readonly修飾子は、フィールドの不変性を保証するために非常に便利ですが、いくつかの制約があります。

以下に、readonlyフィールドに関する主要な制約を示します。

readonlyフィールドの再代入が許されないケース

readonlyフィールドは、初期化後に再代入することができません。

これは、コンストラクタ内で初期化された場合でも、インスタンスが作成された後は変更できないことを意味します。

以下の例では、再代入を試みるとコンパイルエラーが発生します。

class Example
{
    public readonly int Value;
    public Example(int value)
    {
        Value = value; // 初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Example example = new Example(10);
        // example.Value = 20; // エラー: readonlyフィールドは再代入できません
    }
}

readonlyフィールドとリフレクション

リフレクションを使用してreadonlyフィールドの値を変更することは可能ですが、これは一般的には推奨されません。

リフレクションを使用することで、フィールドの不変性が破られる可能性があるため、注意が必要です。

using System;
using System.Reflection;
class Example
{
    public readonly int Value;
    public Example(int value)
    {
        Value = value; // 初期化
    }
}
class Program
{
    static void Main(string[] args)
    {
        Example example = new Example(10);
        Console.WriteLine(example.Value); // 10
        // リフレクションを使用してreadonlyフィールドを変更
        FieldInfo fieldInfo = typeof(Example).GetField("Value", BindingFlags.Public | BindingFlags.Instance);
        fieldInfo.SetValue(example, 20); // 値を変更
        Console.WriteLine(example.Value); // 20 (不変性が破られた)
    }
}

readonlyフィールドとデフォルトコンストラクタ

readonlyフィールドは、デフォルトコンストラクタで初期化することができません。

デフォルトコンストラクタが存在する場合、readonlyフィールドは必ず他のコンストラクタで初期化する必要があります。

class Example
{
    public readonly int Value;
    // デフォルトコンストラクタ
    public Example() 
    {
        // Value = 10; // エラー: readonlyフィールドはデフォルトコンストラクタで初期化できません
    }
    // 別のコンストラクタ
    public Example(int value)
    {
        Value = value; // 初期化
    }
}
// 使用例
class Program
{
    static void Main(string[] args)
    {
        // Example example = new Example(); // エラー: デフォルトコンストラクタは使用できません
        Example example = new Example(10);
        Console.WriteLine(example.Value); // 10
    }
}

readonlyフィールドと継承

readonlyフィールドは、基底クラスから派生したクラスで再代入することはできません。

派生クラスのコンストラクタで基底クラスのreadonlyフィールドを初期化することはできますが、再代入はできません。

class BaseClass
{
    public readonly int BaseValue;
    public BaseClass(int value)
    {
        BaseValue = value; // 初期化
    }
}
class DerivedClass : BaseClass
{
    public DerivedClass(int value) : base(value) { }
}
// 使用例
class Program
{
    static void Main(string[] args)
    {
        DerivedClass derived = new DerivedClass(10);
        Console.WriteLine(derived.BaseValue); // 10
        // derived.BaseValue = 20; // エラー: readonlyフィールドは再代入できません
    }
}

このように、readonlyフィールドは継承においても不変性を保つための重要な役割を果たしますが、再代入ができないことを理解しておく必要があります。

よくある質問

readonlyフィールドはいつ初期化すべきですか?

readonlyフィールドは、オブジェクトのインスタンスが作成される際に初期化する必要があります。

初期化は、コンストラクタ内で行うことが一般的です。

これにより、フィールドの値が不変であることが保証されます。

また、宣言時に初期化することも可能ですが、初期化のタイミングは設計に応じて選択することが重要です。

  • コンストラクタで初期化: インスタンスごとに異なる値を設定したい場合。
  • 宣言時に初期化: すべてのインスタンスで同じ値を持たせたい場合。

readonlyとconstはどちらを使うべきですか?

readonlyconstは似たような目的で使用されますが、使い方には違いがあります。

  • const: コンパイル時に値が決定され、変更できません。

すべてのインスタンスで同じ値を持つ場合に使用します。

  • readonly: 実行時に初期化され、コンストラクタ内で異なる値を設定できます。

インスタンスごとに異なる値を持たせたい場合に適しています。

選択は、値の変更が必要かどうか、または初期化のタイミングに依存します。

readonlyフィールドはパフォーマンスに影響しますか?

readonlyフィールド自体は、パフォーマンスに大きな影響を与えることはありません。

ただし、readonlyフィールドを使用することで、オブジェクトの状態が不変であることが保証されるため、コンパイラが最適化を行いやすくなります。

これにより、特定の状況ではパフォーマンスが向上する可能性があります。

また、readonlyフィールドを使用することで、データの整合性が保たれ、バグを減少させることができるため、結果的に開発効率が向上し、メンテナンスコストが低下することにもつながります。

まとめ

この記事では、C#におけるreadonly修飾子の使い方や利点、応用例、制約について詳しく解説しました。

readonlyフィールドを適切に使用することで、オブジェクトの不変性を保ち、バグを防ぎ、コードの可読性を向上させることが可能です。

これを踏まえて、実際のプログラミングにおいてreadonly修飾子を積極的に活用し、より安全で効率的なコードを書くことを目指してみてください。

  • URLをコピーしました!
目次から探す