始めに
C#を書いていると、こんなのに出くわします。
public int Id {get; set;}
getterとsetterを省略して書けるんだ、でも、publicでいいのか?なんて考えながらとりあえずそのまま書いています。そのままでも不都合があるわけではないのですが、ちょっと気になって調べてみました。
Propertyとは
Propertyはprivateのフィールドを読み書き、計算するための柔軟なメカニズムを提供します。Propertyは、accessorsと呼ばれる特別なメソッドを用いることによって、publicなメンバのように使用することができます。この特徴は、データへの手軽なアクセスやメソッドの安全性・柔軟性を促進します。
microsoft, Properties(C# Programming Guide), 更新日 2024/03/15
privateのフィールドに対する読み書きの手段が、Propertyです。そのため、上にも書いた
public int Id{get; set;}
は、あくまで
private int _id
への読み書きの手段になります。しかし、毎回フィールドを定義する必要はなく、そのあたりはコンパイラが自動で用意してくれます。
Propertyは、accessorsと呼ばれるメソッドを用いることによって、Propertyをpublicなメンバのように使用することができます。accessorsは、3種類あります。
accessors
get
getはフィールドの値の読み込みを行います。ただ値を取得するのみではなく、logicを書いたり、expression body(=>)を使って定義することもできます。
// コンパイラがprivateのフィールドを自動で用意する
public String Name{get; set;}
// logicを書いたもの
private String name;
public String Name{
get{
if(name == null){
return "NoName";
}
return name;
};
set;
}
// expression bodyを用いたもの
private String name;
public String Name => name;
set
setは、フィールドに対する書き込みの役割を担います。こちらもgetと同様にlogicを書くことができます。
// コンパイラがフィールドを自動で用意する書き方
public int id{get; set;}
// setterにlogicを用意することも可能
private String _id;
public String id{get; set{
if(value.length > 6){
throw new Exception("idは6文字以下にしてください");
}
_id = value;
}
// read-onlyも可能
private String _id;
public String id{get; private set;}
init
initのみのsetterは、propertyやindexer要素にコンストラクション時のみ値を詰めることができます。initは、propertyに不変性を与えることができるため、オブジェクトを初期化した後は変更することができません。
Microsoft, init(C# Reference). 最終更新日:2023/12/6, 拙訳
init accessorはpropertyに対して、コンストラクション時のみの値の書き込みを許可します。そのため、一度生成した後は値を変更することができません。では、get accessorのみのpropertyとどう違うのか。
internal class Student
{
public Student(int id, String name)
{
Id = id;
Name = name;
}
public int Id { get; }
public String Name { get; init; }
}
public void initStudent()
{
// OK
Student student1 = new Student(id: 1, name: "Bob");
// NG
Student student2 = new Student()
{
id = 2,
Name = "Mike"
};
// OK
Student student3 = new Student(id: 3)
{
Name = "Jecika"
};
}
Studentクラスを例に、いくつかの生成方法を示しました。まず、student1は、コンストラクタで値をpropertyに書き込んでおり、これはコンパイルエラーになりません。student2では、object初期化子で値を書き込んでおり、get accessorのみのpropertyであるidはコンストラクタでのみ値の書き込みができるため、これはコンパイルエラーになります。そのため、student3のように、idはコンストラクタ、Nameはobject初期化子で定義するのであれば、可能です。
Expressionを用いたProperty
microsoft, Properties(C# Programming Guide), 更新日 2024/03/15public class SaleItem { string _name; decimal _cost; public SaleItem(string name, decimal cost) { _name = name; _cost = cost; } public string Name { get => _name; set => _name = value; } public decimal Price { get => _cost; set => _cost = value; } }
こちらのクラスでは、_nameと_constを暗黙的にprivate変数として定義し、それぞれにアクセスするため、publicなフィールドとして、NameとPriceを用いています。また、NameとPriceのaccessor(getter, setter)では、lambda式を使用しております。
自動実装されたProperty
microsoft, Properties(C# Programming Guide), 更新日 2024/03/15public class SaleItem { public string Name { get; set; } public decimal Price { get; set; } }
いくつかの事例では、propertyのget, set accessorsは、追加のlogicなしに、backing fieldへの値の割り当てと値の取得のみを行います。自動実装されたpropertiesを用いることによって、C#コンパイラがbacking fieldを明らかに提供している間、コードを単純化することができます。
microsoft, Properties(C# Programming Guide), 更新日 2024/03/15
propertyのいくつかの書き方では、privateなfield (backing field)はコンパイラによって自動で実装されるため、明示的に定義する必要はないようです。そのため、最初に話した以下のコードは、カブセル化の視点からは問題ないことになります。
public int id{get; set;}
では、いくつかの書き方ってなんだ、というところは、すみません、調べることができませんでした。実際に書きながら試していただけたらと思います。
まとめ
Propertyのaccessorと2つの書き方について調査しました。get・set・initはそれぞれ読み込み・書き込み・生成時のみの書き込みを意味しています。また、C#のコンパイラは、ある特定の書き方であれば、backing fieldを自動で生成してくれるため、明示的に書く手間を省いてくれます。
コメント