C#プロパティとは?get/setの基本から自動実装・使い方まで初心者向けに解説

はじめに

C#を学び始めると、クラスの中でよく登場するのが「プロパティ」です。

C#
public string Name { get; set; }

このような書き方を見て、「フィールドと何が違うの?」「getやsetは何をしているの?」「{ get; set; }だけでなぜ動くの?」と疑問に感じる方は多いでしょう。

C# property、つまりC#のプロパティは、クラスやオブジェクトのデータを安全に扱うための仕組みです。外部から値を取得したり、代入したりする窓口として使われます。

この記事では、C#プロパティの基本から、get/set、自動実装プロパティ、private set、init、readonlyとの違いまで、初心者にもわかりやすく解説します。

1. C#プロパティとは?フィールドとの違いから基本を理解しよう

1-1. プロパティとは何か

C#のプロパティとは、クラスの内部データにアクセスするための仕組みです。

たとえば、Personクラスに名前を持たせたい場合、次のようにプロパティを定義できます。

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

このNameがプロパティです。

プロパティを使うと、次のようにオブジェクトの値を取得したり、代入したりできます。

C#
Person person = new Person();

person.Name = "Taro";
Console.WriteLine(person.Name);

見た目はフィールドに直接アクセスしているように見えますが、実際にはgetsetというアクセサを通して値を扱っています。

1-2. フィールドとプロパティの違い

フィールドは、クラスの中にデータを直接保存する変数です。

C#
public class Person
{
public string name;
}

一方、プロパティはデータへのアクセス方法を定義するメンバーです。

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

フィールドは値そのものを保持します。プロパティは値を取得・設定するための入り口です。

違いを簡単にまとめると、次のようになります。

種類役割
フィールドデータを直接保持する
プロパティデータを取得・設定するための窓口

C#では、クラスの外部に公開するデータには、基本的にpublicフィールドではなくプロパティを使うのが一般的です。

1-3. プロパティを使うメリット

プロパティを使う大きなメリットは、値の取得や設定のタイミングで処理を追加できることです。

たとえば、年齢にマイナスの値が入らないようにチェックできます。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢にマイナスの値は設定できません。");
}

age = value;
}
}

このように、プロパティを使うと単に値を保存するだけでなく、値の検証や加工ができます。

また、あとから内部実装を変更しやすい点もメリットです。最初は単純なプロパティとして作っておき、必要になったら値チェックや計算処理を追加できます。

1-4. publicフィールドではなくプロパティを使う理由

次のようなpublicフィールドは、一見シンプルです。

C#
public class Person
{
public int Age;
}

しかし、この場合は外部から自由に値を変更できてしまいます。

C#
Person person = new Person();
person.Age = -10;

年齢に-10のような不正な値が入っても、チェックできません。

プロパティを使えば、外部からのアクセス方法を制御できます。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

このように、クラスの状態を安全に保つために、C#ではpublicフィールドではなくプロパティを使うのが基本です。

2. C#プロパティの基本構文と書き方

2-1. プロパティの基本構文

C#プロパティの基本構文は次のとおりです。

C#
アクセス修飾子  プロパティ名
{
get
{
return ;
}
set
{
値を代入する処理;
}
}

具体例を見てみましょう。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

この例では、nameというフィールドに対して、Nameというプロパティを用意しています。

外部からはNameプロパティを通して、nameフィールドの値を取得・変更します。

2-2. getアクセサとは

getアクセサは、プロパティの値を取得するときに呼び出される処理です。

C#
private string name;

public string Name
{
get
{
return name;
}
}

この場合、次のようにNameを参照するとgetが実行されます。

C#
Console.WriteLine(person.Name);

getの中では、通常はフィールドの値をreturnします。

C#
get
{
return name;
}

つまり、getは「このプロパティを読み取ったときに、どの値を返すか」を決める部分です。

2-3. setアクセサとは

setアクセサは、プロパティに値を代入するときに呼び出される処理です。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

次のようにプロパティへ値を代入すると、setが実行されます。

C#
person.Name = "Taro";

このとき、代入された"Taro"valueキーワードで受け取れます。

C#
set
{
name = value;
}

つまり、setは「このプロパティに値が代入されたとき、どのように保存するか」を決める部分です。

2-4. valueキーワードの意味

valueは、setアクセサの中で使える特別なキーワードです。

C#
set
{
name = value;
}

たとえば、次のように書いたとします。

C#
person.Name = "Hanako";

この場合、setアクセサの中のvalueには"Hanako"が入ります。

C#
set
{
name = value; // valueは"Hanako"
}

valueの型は、プロパティの型と同じです。

C#
public int Age
{
set
{
// valueはint型
}
}
C#
public string Name
{
set
{
// valueはstring型
}
}

valueは自分で宣言する必要はありません。C#がsetの中で自動的に使えるようにしてくれます。

2-5. プロパティを使った値の取得・代入方法

プロパティは、通常の変数のような感覚で使えます。

C#
public class Person
{
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
}

値を代入する場合は、次のように書きます。

C#
Person person = new Person();
person.Name = "Taro";

このとき、setアクセサが呼ばれます。

値を取得する場合は、次のように書きます。

C#
Console.WriteLine(person.Name);

このとき、getアクセサが呼ばれます。

外部から見るとシンプルな代入・参照ですが、内部ではgetsetを通して処理が行われています。

3. get/setを使った通常プロパティの実装例

3-1. バッキングフィールドとは

バッキングフィールドとは、プロパティの値を実際に保存するためのフィールドです。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

この例では、nameがバッキングフィールド、Nameがプロパティです。

プロパティ自体は値の受け渡しを担当し、実際の値はバッキングフィールドに保存されます。

バッキングフィールドは、クラスの外部から直接触らせないようにprivateにするのが一般的です。

C#
private string name;

そして、外部からはプロパティを通してアクセスします。

C#
person.Name = "Taro";

このようにすることで、クラス内部のデータを安全に管理できます。

3-2. getでフィールドの値を返す書き方

getでは、バッキングフィールドの値を返します。

C#
private string name;

public string Name
{
get
{
return name;
}
}

このプロパティはgetだけを持っているため、外部から値を読み取ることはできますが、代入はできません。

C#
Console.WriteLine(person.Name);

ただし、次のような代入はできません。

C#
person.Name = "Taro"; // エラー

getは値を返すためのアクセサなので、return文を使って値を返します。

3-3. setでフィールドに値を代入する書き方

setでは、受け取った値をバッキングフィールドに代入します。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

この例では、Nameプロパティに値が代入されると、setアクセサが実行されます。

C#
person.Name = "Taro";

このとき、valueには"Taro"が入ります。

C#
set
{
name = value;
}

その結果、バッキングフィールドname"Taro"が保存されます。

3-4. 値チェックを入れるプロパティの例

プロパティの大きな利点は、値を設定するときにチェックを入れられることです。

たとえば、年齢に0未満の値を設定できないようにするには、次のように書きます。

C#
public class Person
{
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}
}

使い方は次のとおりです。

C#
Person person = new Person();

person.Age = 20;
Console.WriteLine(person.Age);

person.Age = -1; // 例外が発生

このように、setの中で条件分岐を使うことで、不正な値がオブジェクトに入ることを防げます。

3-5. 計算結果を返す読み取り専用プロパティの例

プロパティは、フィールドの値をそのまま返すだけでなく、計算結果を返すこともできます。

たとえば、姓と名からフルネームを返すプロパティを作れます。

C#
public class Person
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";

public string FullName
{
get
{
return LastName + " " + FirstName;
}
}
}

使い方は次のとおりです。

C#
Person person = new Person();
person.LastName = "Yamada";
person.FirstName = "Taro";

Console.WriteLine(person.FullName);

FullNamegetだけなので、外部から代入できません。

C#
person.FullName = "Suzuki Hanako"; // エラー

このように、他の値から計算できる情報は、読み取り専用プロパティとして定義すると便利です。

4. 自動実装プロパティとは?{ get; set; }の意味

4-1. 自動実装プロパティの基本構文

自動実装プロパティとは、バッキングフィールドを自分で書かずに定義できるプロパティです。

C#
public string Name { get; set; }

これは、次のような通常プロパティを簡潔に書いたものと考えるとわかりやすいです。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

自動実装プロパティでは、getsetの処理内容を書きません。

C#
public int Age { get; set; }

値をそのまま取得・代入するだけでよい場合は、自動実装プロパティを使うとコードが短くなります。

4-2. 通常プロパティとの違い

通常プロパティでは、自分でバッキングフィールドを用意し、getsetの処理を書きます。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
age = value;
}
}

一方、自動実装プロパティでは、次のように短く書けます。

C#
public int Age { get; set; }

主な違いは、getsetの中に独自処理を書けるかどうかです。

値チェックや加工が必要な場合は、通常プロパティを使います。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

単純に値を保存するだけなら、自動実装プロパティで十分です。

C#
public int Age { get; set; }

4-3. コンパイラが自動で生成するバッキングフィールド

自動実装プロパティでは、バッキングフィールドを書いていません。

C#
public string Name { get; set; }

しかし、値を保存する場所は必要です。

そこでC#コンパイラが、内部的にバッキングフィールドを自動生成します。

開発者はそのフィールドを直接操作できませんが、プロパティを通して値を取得・代入できます。

C#
Person person = new Person();
person.Name = "Taro";

Console.WriteLine(person.Name);

つまり、{ get; set; }だけで動くのは、C#コンパイラが裏側で必要なフィールドと処理を用意してくれるからです。

4-4. 自動実装プロパティを使うべき場面

自動実装プロパティは、値をそのまま保持するだけの場面で使います。

たとえば、次のようなクラスでは自動実装プロパティが向いています。

C#
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
}

IdNamePriceに特別な値チェックや計算処理が不要であれば、自動実装プロパティで十分です。

逆に、次のような場合は通常プロパティを使った方がよいです。

C#
private decimal price;

public decimal Price
{
get
{
return price;
}
set
{
if (value < 0)
{
throw new ArgumentException("価格は0以上である必要があります。");
}

price = value;
}
}

単純なデータ保持なら自動実装プロパティ、独自処理が必要なら通常プロパティ、と覚えるとよいでしょう。

4-5. 自動実装プロパティに初期値を設定する方法

自動実装プロパティには、定義時に初期値を設定できます。

C#
public class Person
{
public string Name { get; set; } = "未設定";
public int Age { get; set; } = 0;
}

この場合、オブジェクトを作成した時点で初期値が入っています。

C#
Person person = new Person();

Console.WriteLine(person.Name); // 未設定
Console.WriteLine(person.Age); // 0

リストや配列などのプロパティにも初期値を設定できます。

C#
public class Team
{
public List<string> Members { get; set; } = new List<string>();
}

初期値を設定しておくと、nullによるエラーを防ぎやすくなります。

5. プロパティのアクセス制御を理解しよう

5-1. public get; set; の意味

次のようなプロパティは、外部から読み取りも書き込みもできます。

C#
public string Name { get; set; }

これは、次の2つが両方ともpublicであることを意味します。

C#
get; // 外部から取得できる
set; // 外部から代入できる

使い方は次のとおりです。

C#
Person person = new Person();

person.Name = "Taro";
Console.WriteLine(person.Name);

public get; set;は便利ですが、どこからでも値を変更できるため、クラスの状態を厳密に管理したい場合には注意が必要です。

5-2. private setで外部からの変更を防ぐ

private setを使うと、外部からは値を読み取れるが、外部からは変更できないプロパティを作れます。

C#
public class Person
{
public string Name { get; private set; }

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

この場合、クラスの外部からNameを読み取ることはできます。

C#
Person person = new Person("Taro");
Console.WriteLine(person.Name);

しかし、外部から代入することはできません。

C#
person.Name = "Hanako"; // エラー

一方、クラス内部からは変更できます。

C#
public void ChangeName(string name)
{
Name = name;
}

private setは、外部から勝手に変更されたくない値を扱うときに便利です。

5-3. getのみの読み取り専用プロパティ

getだけを持つプロパティは、読み取り専用プロパティです。

C#
public string Name { get; }

このプロパティは、外部から値を取得できますが、代入はできません。

C#
Console.WriteLine(person.Name);
person.Name = "Taro"; // エラー

ただし、コンストラクタの中では値を設定できます。

C#
public class Person
{
public string Name { get; }

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

このようにすると、オブジェクト作成時に値を決め、その後は変更できないプロパティを作れます。

5-4. setのみの書き込み専用プロパティは使うべきか

C#では、setだけのプロパティを作ることもできます。

C#
public string Password
{
set
{
// 値を保存する、またはハッシュ化するなど
}
}

この場合、外部から値を設定することはできますが、読み取ることはできません。

C#
user.Password = "secret";

ただし、setのみのプロパティは使いどころが限られます。

読み取りができないプロパティは、使う側から見ると動作がわかりにくくなることがあります。そのため、実務ではメソッドにした方が自然な場合も多いです。

C#
user.ChangePassword("secret");

何かの操作を行う目的であれば、setのみのプロパティよりもメソッドを使う方が意図が伝わりやすいです。

5-5. アクセス修飾子を使い分ける実用例

プロパティのアクセス制御は、値をどこから変更できるようにするかで使い分けます。

たとえば、商品クラスを考えてみます。

C#
public class Product
{
public int Id { get; }
public string Name { get; private set; }
public decimal Price { get; private set; }

public Product(int id, string name, decimal price)
{
Id = id;
Name = name;
Price = price;
}

public void ChangePrice(decimal price)
{
if (price < 0)
{
throw new ArgumentException("価格は0以上である必要があります。");
}

Price = price;
}
}

この例では、Idは作成後に変更できないようにしています。

C#
public int Id { get; }

NamePriceは外部から直接変更できません。

C#
public string Name { get; private set; }
public decimal Price { get; private set; }

価格を変更するときは、専用のメソッドを通します。

C#
product.ChangePrice(1200);

このように設計すると、不正な変更を防ぎながら、必要な操作だけを外部に公開できます。

6. init・readonly・コンストラクタとプロパティの関係

6-1. initアクセサとは

initアクセサは、オブジェクトの初期化時だけ値を設定できるプロパティです。

C#
public class Person
{
public string Name { get; init; } = "";
}

initを使うと、オブジェクト初期化子で値を設定できます。

C#
Person person = new Person
{
Name = "Taro"
};

しかし、オブジェクト作成後に値を変更することはできません。

C#
person.Name = "Hanako"; // エラー

initは、作成時には柔軟に値を設定したいが、その後は変更されたくないデータに向いています。

6-2. get; init; と get; private set; の違い

get; init;get; private set;は似ていますが、意味が異なります。

C#
public string Name { get; init; }

get; init;は、初期化時だけ値を設定できます。外部からはオブジェクト初期化子で値を設定できますが、作成後は変更できません。

C#
Person person = new Person
{
Name = "Taro"
};

person.Name = "Hanako"; // エラー

一方、get; private set;は、外部からは変更できませんが、クラス内部からはいつでも変更できます。

C#
public class Person
{
public string Name { get; private set; }

public void ChangeName(string name)
{
Name = name;
}
}

違いを簡単に言うと、次のようになります。

書き方外部からの設定クラス内部での変更
get; init;初期化時のみ可能初期化時のみ
get; private set;不可いつでも可能

作成後に変更しないデータにはinit、クラス内部のメソッドで変更したいデータにはprivate setが向いています。

6-3. コンストラクタでプロパティを初期化する方法

読み取り専用プロパティは、コンストラクタで初期化できます。

C#
public class Person
{
public string Name { get; }
public int Age { get; }

public Person(string name, int age)
{
Name = name;
Age = age;
}
}

使い方は次のとおりです。

C#
Person person = new Person("Taro", 20);

Console.WriteLine(person.Name);
Console.WriteLine(person.Age);

NameAgegetだけなので、作成後は変更できません。

C#
person.Name = "Hanako"; // エラー

コンストラクタを使うと、オブジェクト作成時に必要な値を強制できます。必ず設定されるべき値には、コンストラクタでの初期化が向いています。

6-4. オブジェクト初期化子でプロパティを設定する方法

オブジェクト初期化子を使うと、オブジェクト作成と同時にプロパティへ値を設定できます。

C#
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
}

次のように書けます。

C#
Person person = new Person
{
Name = "Taro",
Age = 20
};

これは、次のように書くよりも簡潔です。

C#
Person person = new Person();
person.Name = "Taro";
person.Age = 20;

initプロパティも、オブジェクト初期化子で設定できます。

C#
public class Person
{
public string Name { get; init; } = "";
public int Age { get; init; }
}
C#
Person person = new Person
{
Name = "Taro",
Age = 20
};

オブジェクト初期化子は、プロパティが多いクラスを作成するときに便利です。

6-5. 変更できないデータを扱うプロパティ設計

変更できないデータを扱う場合は、プロパティの設計が重要です。

たとえば、ユーザーIDのように一度決めたら変わらない値は、getのみのプロパティにできます。

C#
public class User
{
public int Id { get; }

public User(int id)
{
Id = id;
}
}

オブジェクト初期化子で設定したい場合は、initを使います。

C#
public class User
{
public int Id { get; init; }
public string Name { get; init; } = "";
}
C#
User user = new User
{
Id = 1,
Name = "Taro"
};

readonlyフィールドを使う方法もあります。

C#
public class User
{
private readonly int id;

public int Id
{
get
{
return id;
}
}

public User(int id)
{
this.id = id;
}
}

readonlyはフィールドに対して使うことが多く、コンストラクタ以外で代入できないようにする仕組みです。

プロパティで変更不可の設計をしたい場合は、主に次のように使い分けます。

目的書き方
コンストラクタで必ず設定したいget;
オブジェクト初期化子で設定したいget; init;
クラス内部では変更したいget; private set;
フィールド自体を再代入不可にしたいreadonlyフィールド

7. C#プロパティの使い方をサンプルコードで解説

7-1. Personクラスで学ぶ基本的なプロパティ

まずは、基本的なPersonクラスを見てみましょう。

C#
public class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
}

このクラスには、NameAgeという2つのプロパティがあります。

使い方は次のとおりです。

C#
Person person = new Person();

person.Name = "Taro";
person.Age = 20;

Console.WriteLine(person.Name);
Console.WriteLine(person.Age);

person.Name = "Taro";ではNameプロパティのsetが呼ばれます。

Console.WriteLine(person.Name);ではNameプロパティのgetが呼ばれます。

自動実装プロパティを使うと、シンプルなクラスを短く書けます。

7-2. 値チェック付きのAgeプロパティ

次に、Ageプロパティに値チェックを追加します。

C#
public class Person
{
private int age;

public string Name { get; set; } = "";

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}
}

使い方は次のとおりです。

C#
Person person = new Person();

person.Age = 20;
Console.WriteLine(person.Age);

person.Age = -5; // 例外が発生

このように、通常プロパティを使うと、値の設定時にルールを追加できます。

年齢、価格、数量、在庫数など、範囲チェックが必要な値にはプロパティのsetが便利です。

7-3. 読み取り専用のFullNameプロパティ

姓と名からフルネームを返すプロパティを作ってみましょう。

C#
public class Person
{
public string FirstName { get; set; } = "";
public string LastName { get; set; } = "";

public string FullName
{
get
{
return $"{LastName} {FirstName}";
}
}
}

使い方は次のとおりです。

C#
Person person = new Person
{
LastName = "Yamada",
FirstName = "Taro"
};

Console.WriteLine(person.FullName);

FullNameFirstNameLastNameから計算できるため、値を保存する必要がありません。

そのため、setは不要です。

より短く書くなら、式形式のプロパティも使えます。

C#
public string FullName => $"{LastName} {FirstName}";

これは次のコードとほぼ同じ意味です。

C#
public string FullName
{
get
{
return $"{LastName} {FirstName}";
}
}

計算結果を返すだけの読み取り専用プロパティでは、式形式を使うとコードがすっきりします。

7-4. 自動実装プロパティを使ったシンプルなクラス

データを保持するだけのクラスでは、自動実装プロパティがよく使われます。

C#
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
}

オブジェクト初期化子を使うと、次のように書けます。

C#
Product product = new Product
{
Id = 1,
Name = "Laptop",
Price = 120000
};

Console.WriteLine(product.Name);
Console.WriteLine(product.Price);

このようなクラスは、データの受け渡しや画面表示、APIのレスポンスなどでよく使われます。

ただし、価格にマイナスを許可したくない場合は、Priceだけ通常プロパティにすることもできます。

C#
public class Product
{
private decimal price;

public int Id { get; set; }
public string Name { get; set; } = "";

public decimal Price
{
get
{
return price;
}
set
{
if (value < 0)
{
throw new ArgumentException("価格は0以上である必要があります。");
}

price = value;
}
}
}

自動実装プロパティと通常プロパティは、同じクラスの中で組み合わせて使えます。

7-5. 実務でよく使うプロパティの書き方

実務では、すべてのプロパティをpublic get; set;にするのではなく、変更してよい範囲を考えて設計します。

たとえば、注文を表すクラスを考えてみます。

C#
public class Order
{
public int Id { get; }
public DateTime CreatedAt { get; }
public decimal TotalAmount { get; private set; }

public Order(int id)
{
Id = id;
CreatedAt = DateTime.Now;
}

public void AddAmount(decimal amount)
{
if (amount <= 0)
{
throw new ArgumentException("金額は0より大きい必要があります。");
}

TotalAmount += amount;
}
}

この例では、IdCreatedAtは作成後に変更できません。

C#
public int Id { get; }
public DateTime CreatedAt { get; }

TotalAmountは外部から直接変更できませんが、クラス内部では変更できます。

C#
public decimal TotalAmount { get; private set; }

金額を追加するときは、AddAmountメソッドを使います。

C#
order.AddAmount(1000);

このように、プロパティとメソッドを組み合わせることで、クラスの状態を安全に管理できます。

8. 初心者がつまずきやすいC#プロパティの注意点

8-1. フィールドとプロパティを混同しない

初心者がよく混同しやすいのが、フィールドとプロパティです。

フィールドは、値を保存する変数です。

C#
private string name;

プロパティは、値を取得・設定するための窓口です。

C#
public string Name { get; set; }

通常、フィールドはprivateにして外部から隠し、プロパティをpublicにして外部へ公開します。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

外部から直接フィールドを操作させると、不正な値が入る可能性があります。クラスの状態を守るためにも、外部公開にはプロパティを使うのが基本です。

8-2. get/setの中で無限ループを起こす原因

プロパティでよくあるミスが、プロパティの中で同じプロパティを呼び出してしまうことです。

たとえば、次のコードは問題があります。

C#
public string Name
{
get
{
return Name;
}
set
{
Name = value;
}
}

getの中でNameを返していますが、Nameを読み取るとまたgetが呼ばれます。すると、getが自分自身を呼び続けてしまいます。

setも同じです。

C#
set
{
Name = value;
}

Nameに代入すると、またsetが呼ばれます。これも無限ループの原因になります。

正しくは、バッキングフィールドを使います。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

プロパティの中では、プロパティ自身ではなく、対応するフィールドを使うようにしましょう。

8-3. 自動実装プロパティでは複雑な処理を書けない

自動実装プロパティは便利ですが、getsetの中に複雑な処理を書くことはできません。

C#
public int Age { get; set; }

この書き方では、値をそのまま保存して、そのまま返すだけです。

値チェックをしたい場合は、通常プロパティにする必要があります。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

自動実装プロパティは、単純なデータ保持に向いています。条件チェック、ログ出力、値の変換などをしたい場合は、通常プロパティを使いましょう。

8-4. private setとreadonlyの違いに注意する

private setreadonlyは、どちらも「外部から変更しにくい」設計に使われますが、意味は違います。

private setは、外部からは代入できないが、クラス内部からは代入できるプロパティです。

C#
public string Name { get; private set; }

クラス内部のメソッドからは変更できます。

C#
public void ChangeName(string name)
{
Name = name;
}

一方、readonlyは主にフィールドに使い、コンストラクタ以外で再代入できないようにします。

C#
private readonly string name;

public string Name
{
get
{
return name;
}
}

public Person(string name)
{
this.name = name;
}

違いをまとめると、次のようになります。

種類変更できる場所
private setクラス内部なら変更できる
readonlyフィールド宣言時またはコンストラクタでのみ代入できる
getのみの自動プロパティコンストラクタで代入できる
init初期化時のみ設定できる

「作成後も内部処理で変更したい」のか、「作成後は一切変更したくない」のかを考えて使い分けましょう。

8-5. プロパティ名とフィールド名の命名ルール

C#では、プロパティ名はパスカルケースで書くのが一般的です。

C#
public string Name { get; set; }
public int Age { get; set; }

パスカルケースとは、単語の先頭を大文字にする書き方です。

C#
FirstName
LastName
TotalAmount

一方、privateフィールドはキャメルケースやアンダースコア付きで書かれることが多いです。

C#
private string name;
private int age;

または、次のようにアンダースコアを付ける書き方もよく使われます。

C#
private string _name;
private int _age;

プロパティとフィールドを組み合わせる場合は、次のように書くと区別しやすくなります。

C#
private string _name;

public string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

命名ルールを統一しておくと、コードが読みやすくなり、プロパティとフィールドの混同も防ぎやすくなります。

9. C#プロパティに関するよくある質問

9-1. C#のプロパティはgetter/setterと同じですか?

C#のプロパティは、getter/setterに近い仕組みです。

C#では、値を取得する処理をgetアクセサ、値を設定する処理をsetアクセサとして書きます。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

他の言語では、次のようなメソッドでgetter/setterを書くことがあります。

C#
public string GetName()
{
return name;
}

public void SetName(string value)
{
name = value;
}

C#のプロパティは、これをより自然な構文で扱えるようにしたものと考えるとわかりやすいです。

C#
person.Name = "Taro";
Console.WriteLine(person.Name);

メソッドを呼び出しているようには見えませんが、内部ではgetsetの処理が呼ばれています。

9-2. フィールドではなくプロパティを使うのはなぜですか?

プロパティを使う理由は、クラスのデータを安全に扱うためです。

publicフィールドにすると、外部から自由に値を変更できてしまいます。

C#
public int Age;

この場合、不正な値も代入できます。

C#
person.Age = -10;

プロパティを使えば、値を設定するときにチェックできます。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

また、あとから内部処理を追加しやすいというメリットもあります。

最初は単純な自動実装プロパティとして書いておき、必要になったら通常プロパティに変更できます。

C#
public int Age { get; set; }

このように、保守性や安全性を高めるために、C#ではフィールドではなくプロパティを使うのが一般的です。

9-3. { get; set; }だけでなぜ動くのですか?

{ get; set; }だけで動くのは、自動実装プロパティだからです。

C#
public string Name { get; set; }

このように書くと、C#コンパイラが内部的にバッキングフィールドを自動生成します。

開発者が次のようなフィールドを明示的に書かなくても、値を保存する仕組みが用意されます。

C#
private string name;

そのため、次のように普通の変数のように使えます。

C#
Person person = new Person();

person.Name = "Taro";
Console.WriteLine(person.Name);

ただし、自動実装プロパティではgetsetの中に独自処理を書けません。

値チェックなどが必要な場合は、通常プロパティを使います。

9-4. getだけのプロパティはいつ使いますか?

getだけのプロパティは、外部から値を読み取らせたいが、変更はさせたくない場合に使います。

C#
public string Name { get; }

たとえば、オブジェクト作成時に決まる値に向いています。

C#
public class User
{
public int Id { get; }

public User(int id)
{
Id = id;
}
}

この場合、Idはコンストラクタで設定され、その後は変更できません。

また、計算結果を返すプロパティにもgetだけを使います。

C#
public string FullName
{
get
{
return LastName + " " + FirstName;
}
}

さらに短く書くなら、式形式のプロパティも使えます。

C#
public string FullName => LastName + " " + FirstName;

getだけのプロパティは、変更されたくない値や、他の値から計算できる値に使うと便利です。

9-5. 自動実装プロパティと通常プロパティはどちらを使うべきですか?

基本的には、単純に値を保持するだけなら自動実装プロパティを使います。

C#
public string Name { get; set; }
public int Age { get; set; }

コードが短く、読みやすいからです。

一方、次のような処理が必要な場合は通常プロパティを使います。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

通常プロパティを使うべき場面は、たとえば次のような場合です。

場面使うプロパティ
単純に値を保存するだけ自動実装プロパティ
値チェックをしたい通常プロパティ
値を加工して保存したい通常プロパティ
計算結果を返したいgetのみのプロパティ
外部から変更させたくないprivate setinit

最初は自動実装プロパティで書き、必要に応じて通常プロパティに変更する、という考え方で問題ありません。

まとめ

C#プロパティは、クラスのデータを安全に扱うための重要な仕組みです。

フィールドは値を直接保持するものですが、プロパティはその値にアクセスするための窓口です。外部から値を取得するときはget、値を代入するときはsetが使われます。

C#
private string name;

public string Name
{
get
{
return name;
}
set
{
name = value;
}
}

単純に値を保持するだけなら、自動実装プロパティを使うと簡潔に書けます。

C#
public string Name { get; set; }
public int Age { get; set; }

値チェックや加工が必要な場合は、通常プロパティを使います。

C#
private int age;

public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentException("年齢は0以上である必要があります。");
}

age = value;
}
}

また、外部から変更されたくない値には、private setgetのみ、initなどを使います。

C#
public string Name { get; private set; }
public int Id { get; }
public string Title { get; init; }

C# propertyを理解するうえで大切なのは、単にget; set;の書き方を覚えることではありません。どの値を外部に公開し、どの値を変更可能にし、どの値を守るべきかを考えることです。

プロパティを適切に使えるようになると、C#のクラス設計がわかりやすくなり、安全で保守しやすいコードを書けるようになります。