.NETでは「変更できない値」をconstキーワード/readonly修飾子/読み取り専用プロパティなどを使って宣言できる。それらの使いどころや違いをまとめよう。
定数/readonly修飾子/不変な読み取り専用プロパティは、いずれも変更できない。どれも定数のようなものである。本稿ではその違いを整理して解説する。また、不変なクラスと構造体の作り方も説明する。
特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。
なお、一部を除き.NET Frameworkの初期バージョンから利用できるが、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2017 Update 5(バージョン15.5)以降が必要である。また、サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。
using System;
using static System.Console;
Imports System.Console
定数は、メソッド内およびクラスや構造体などのメンバに記述できる(次のコード)。
class Program
{
// メンバに定義した定数
const int N1 = 1; // 数値
const string S1 = "定数1"; // 文字列
const ConsoleColor C1 = ConsoleColor.Blue; // 列挙体(enum)
const object O1 = null; // 参照型はnullのみ可能
//const DateTimeOffset D1 = default; // 構造体は不可(コンパイルエラー)
static void Main(string[] args)
{
// メソッド内に定義した定数
const int N2 = 2; // 数値
const string S2 = "定数2"; // 文字列
const ConsoleColor C2 = ConsoleColor.Green; // 列挙体(enum)
const object O2 = null; // 参照型はnullのみ可能
//const DateTimeOffset D2 = default; // 構造体は不可(コンパイルエラー)
}
}
Module Module1
' メンバに定義した定数
Const N1 As Integer = 1 '数値
Const S1 As String = "定数1" ' 文字列
Const C1 As ConsoleColor = ConsoleColor.Blue ' 列挙体(Enum)
Const O1 As Object = Nothing ' 参照型はNothingのみ可能
'Const D1 As DateTimeOffset = #2017/12/24# ' 構造体は不可(コンパイルエラー)
Sub Main()
' メソッド内に定義した定数
Const N2 As Integer = 2 ' 数値
Const S2 As String = "定数2" ' 文字列
Const C2 As ConsoleColor = ConsoleColor.Green ' 列挙体(Enum)
Const O2 As Object = Nothing ' 参照型はNothingのみ可能
'Const D2 As DateTimeOffset = #2017/12/24# ' 構造体は不可(コンパイルエラー)
End Sub
End Module
定数はコンパイル時に解決される。コードに記述した定数の部分は、コンパイル後は実際の値(例えば上のコードのN2であれば整数の2)に置き換えられるのである。
そのためパフォーマンスはよいが、定数を変更したときにはその定数を使っているコードを全てコンパイルし直さねばならない。クラスライブラリなどの複数のアプリから使われるコードで定数をpublicにすると、もはやその定数を変更するのは不可能といえるだろう。publicにしたい場合は、後述の読み取り専用プロパティにすべきである。また、構造体とnull以外の参照型は定数にできないので、後述するreadonly修飾子か読み取り専用プロパティを使う。
定数の使いどころは次のようにまとめられる。
次にreadonly修飾子の使いどころを見てみる。
メンバ変数の宣言にreadonly修飾子を付けると、読み取り専用になる(次のコード)。その値やオブジェクトは、実行時に初期化される。初期化は、変数の宣言箇所かコンストラクタで行う。
class Program
{
private readonly int NR1 = 10;
// 省略するが、文字列/列挙体も可能
private readonly UriBuilder OR1 = new UriBuilder(); // 参照型もOK
private readonly DateTimeOffset DR2 = DateTimeOffset.Now; // 構造体もOK
}
Module Module1
Private ReadOnly NR1 As Integer = 10
' 省略するが、文字列/列挙体も可能
Private ReadOnly OR1 As UriBuilder = New UriBuilder() ' 参照型もOK
Private ReadOnly DR2 As DateTimeOffset = DateTimeOffset.Now ' 構造体もOK
End Module
readonly修飾子は、(定数と異なり)参照型と構造体でも使える。逆に、定数のようにメソッド内に書くことはできない。
readonly修飾子を付けたメンバ変数は、初期化時以外に代入できないことを除けば、通常のメンバ変数と同じだ。従って、インスタンスメンバと静的メンバの違いがある(次のコード)。
class Program
{
// readonly修飾子を付けた静的メンバ変数
static private readonly DateTimeOffset DR1 = DateTimeOffset.Now;
// readonly修飾子を付けたインスタンスメンバ変数
private readonly DateTimeOffset DR2 = DateTimeOffset.Now;
// async MainはC# 7.1以降
static async System.Threading.Tasks.Task Main(string[] args)
{
// 静的メンバ
WriteLine($"DR1={Program.DR1:ss.fff}");
// 出力例:DR1=33.874
// インスタンスメンバ
var instance1 = new Program();
WriteLine($"DR2={instance1.DR2:ss.fff}");
// 出力例:DR2=34.096
// 約1秒間、待機する
await System.Threading.Tasks.Task.Delay(1000);
// 静的メンバ(1秒前と同じ結果)
WriteLine($"DR1={Program.DR1:ss.fff}");
// 出力例:DR1=33.874
// 新しく生成したインスタンスメンバ(この時刻で初期化される)
var instance2 = new Program();
WriteLine($"DR2={instance2.DR2:ss.fff}");
// 出力例:DR2=35.154
#if DEBUG
ReadKey();
#endif
}
}
なお、参照型のメンバ変数にreadonly修飾子を付けた場合、そのメンバ変数に代入することはできないが、そのメンバ変数が参照するオブジェクトのプロパティやメンバ変数を変更することはできてしまうので注意が必要だ(次のコード)。この問題に対処するには、メンバ変数を後述する不変なクラスにする。また、配列やコレクションにreadonly修飾子を付けた場合もその要素は変更可能であり、対処するにはReadOnlyCollection<T>クラス(System.Collections.ObjectModel名前空間)などを利用する。
class Program
{
private readonly UriBuilder OR1 = new UriBuilder();
static void Main(string[] args)
{
var instance1 = new Program();
//instance1.OR1 = new UriBuilder(); // コンパイルエラー
instance1.OR1.Scheme = "https"; // プロパティやメンバ変数は書き換え可能
#if DEBUG
ReadKey();
#endif
}
}
readonly修飾子を付けたメンバ変数は、実行時に初期化される。そのため、その初期値を変更しても、(定数とは異なり)利用している側のコードをコンパイルし直す必要はない。ただし、メンバ変数を外部に公開するのは推奨されないので、そういう場合には読み取り専用プロパティを使う方がよい。
readonly修飾子の使いどころは次のようにまとめられる。
次に不変な読み取り専用プロパティの使いどころを見てみる。
読み取り専用のプロパティは、外部からは書き換えできない。そこで、プロパティのバッキングフィールドを初期化時以外には変更できないようにすれば、不変な読み取り専用プロパティになる。
従来のプロパティの書き方をする場合は、バッキングフィールドにreadonly修飾子を付ける(次のコード)。これでクラス内からもプロパティが変更されないことを保証できる。
public class MyClass1
{
private readonly string _name;
public string Name { get { return _name; } }
private readonly DateTimeOffset _created = DateTimeOffset.Now;
public DateTimeOffset Created { get { return _created; } }
public MyClass1(string name)
{
_name = name;
}
}
Public Class MyClass1
Private ReadOnly _name As String
Public ReadOnly Property Name As String
Get
Return _name
End Get
End Property
Private ReadOnly _created As DateTimeOffset = DateTimeOffset.Now
Public ReadOnly Property Created As DateTimeOffset
Get
Return _created
End Get
End Property
Public Sub New(name As String)
_name = name
End Sub
End Class
「公開するならメンバ変数ではなくプロパティにすべき」と分かってはいても、しかし上のコードのように長々と書くのは勘弁してほしいと思うだろう。
Visual Studio 2015からは、自動実装プロパティで簡潔に実装できるようになっている(次のコード)。
public class MyClass2
{
public string Name { get; }
public DateTimeOffset Created { get; } = DateTimeOffset.Now;
public MyClass2(string name) => Name = name;
}
Public Class MyClass2
Public ReadOnly Property Name As String
Public ReadOnly Property Created As DateTimeOffset = DateTimeOffset.Now
Public Sub New(name As String)
Me.Name = name
End Sub
End Class
なお、上のような全てのメンバ変数/プロパティが(初期化後は)不変なクラスは、「不変クラス」(immutable class/イミュータブルなクラス)と呼ばれる。参照型のメンバ変数にreadonly修飾子を付けてもそのオブジェクト内のメンバは書き換え可能だと先ほど指摘したが、それを避けるにはこのような不変クラスを作ってメンバ変数を置き換える。
不変な読み取り専用プロパティの使いどころは次のようにまとめられる。
最後にC# 7.2で導入された不変性を強制する構造体を紹介する。
上記のようなイミュータブルなクラスにするのは開発者の責任だ。イミュータブルにしたつもりが、うっかり実装し間違えることもあり得る。
構造体でも事情は同じだった。ところがC# 7.2では、構造体に不変性を強制できるようになったのである。構造体定義にreadonly修飾子を付けると、メンバ変数にreadonly修飾子が必須になり、プロパティも読み取り専用しか許されなくなる(次のコード)。
public readonly struct MyStruct2
{
//public string Name { get; set; } // コンパイルエラー
public string Name { get; } // 読み取り専用が強制される
//private string _name2; // コンパイルエラー
private readonly string _name2; // readonlyが強制される
public string Name2 { get => _name2; }
public MyStruct2(string name)
{
Name = name;
_name2 = name;
}
}
定数はメソッド内またはメンバで、公開しない数値/文字列/列挙体に用いる。それ以外のメンバでは、Visual Studio 2015以降は読み取り専用の自動実装プロパティを使うとよい。Visual Studio 2015以前では、公開しないものにはreadonly修飾子を付けたメンバ変数を使う。また、オブジェクト自体を不変にする方法も解説した。
利用可能バージョン:Visual Studio 2008以降(一部、Visual Studio 2015以降、またはVisual Studio 2017以降)
カテゴリ:C# 処理対象:言語構文
カテゴリ:Visual Basic 処理対象:言語構文
関連TIPS:手軽にプロパティを実装するには?[C#、VS 2008、3.5]
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0/7.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
Copyright© Digital Advantage Corp. All Rights Reserved.