動くコード図鑑技術記事現場の渡り方キャリア論すべての記事About
技術記事

【C#】プロパティとフィールド(メンバ変数)の決定的な違い!

バイブス父さん
現役の業務SE
2020年10月31日9 min read
【C#】プロパティとフィールド(メンバ変数)の決定的な違い!

みなさんこんにちは!ひろぽんです!

今回はC#のプロパティとフィールド(メンバ変数)の違いについて書いていきたいと思います。

Privateなフィールド(メンバ変数)とPublicなプロパティならなんとなく違いが分かるかと思いますが、Publicなフィールド(メンバ変数)とPublicなプロパティってどう違うの?ってなる人も多いのではないでしょうか?

結論から言うと全く違います。

シマウマとウマくらい違います。

インドネシアとインドくらい違います。

中国と中国地方くらい違います。

っと本当に違うので、この記事で覚えていってください。

[s_ad]

プロパティはフィールド(メンバ変数)へのアクセスする手段でしかない

💡 動的にプロパティ取得する方法は別記事 プロパティを動的検索して値取得 で書いてます。

結論はい!

ということです。

プロパティはフィールドにアクセスするためのものなのです。

一方のフィールドはメモリに値を一時的に保存するためのもの。いわば特定のClass内でのスコープが確保された変数といった感じです。

なので下記の書き方は

プロパティはフィールド(メンバ変数)へのアクセスする手段でしかない (csharp)#03c4cbbe71da
        private string _gender;
        public string Gender
        {
            get { return _gender; }
            set { _gender = value; }
        }
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

下記と一緒です。

        public string Gender { get; set; }

なんならこれとも一緒です。

プロパティはフィールド(メンバ変数)へのアクセスする手段でしかない (csharp)#f0d0d8bc2968
        private string _gender;
        public string Gender
        {
            get => _gender;
            set => _gender = value;
        }
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

プロパティのおいしさ

じゃあプロパティとフィールドが違うのはわかったけど、プロパティは何がおいしいのか?という点になってくると思います。

結論から言うとプロパティは下記のことができます。

  • Datagridに値を表示できる
  • シリアライズしてXMLにできる
  • プロパティを動的に探して色々できる

Datagridに値を表示できる

例えば下記のようなクラスがあったとします。

Datagridに値を表示できる (csharp)#147badd2385c
    public class User
    {
        public int ID { get; set; }
        public string Name { get; set; }
 
    public int Age;
    public string Address { get; set; }
 
    private string _gender;
    public string Gender
    {
        get => _gender;
        set => _gender = value;
    }
 
    public User(int age, int id, string name, string address,string gender)
    {
        Age = age;
        ID = id;
        Name = name;
        Address = address;
        _gender = gender;
    }
 
    public User()
    {
 
    }
}
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

プロパティはIDとNameとAddressとGenderだけです。

Ageと_genderはあくまでもフィールドです。

で、上記のクラスのリストを作って、DatagridのDatasourceにします。

Datagridに値を表示できる (csharp)#10a217f49692
        private void Form1_Load(object sender, EventArgs e)
        {
            _users = new List<User>()
            {
                new User(20,1,"杉山","東京都","男")
                ,new User(30,2,"山田","愛知県","女")
                ,new User(25,3,"鈴木","秋田県","男")
            };
            dataGridView1.DataSource = _users;
        }
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

するとデータが表示できます。

Ageと_genderはフィールドなので表示はされていません。

[s_ad]

シリアライズしてXMLにできる(Privateのみ出力できない)

次にシリアライズして外部にXMLを保存する場合です。

下記のようなXMLHelperを使って先ほど紹介したクラスを外部に保存してみます。

ジェネリックTのクラスをシリアライズして、保存したりデシリアライズして復元したりするクラスですね!

シリアライズしてXMLにできる(Privateのみ出力できない) (csharp)#7d09cf0c607a
    public class XmlHelper<T> where T : class
    {
        public string TargetPath { get; set; }
        private Encoding encoding => System.Text.Encoding.GetEncoding("shift_jis");
 
    public XmlHelper(string targetPath)
    {
        TargetPath = targetPath;
    }
 
    public XmlHelper() : base()
    {
    }
 
    public void Save(T data)
    {
        var serializer = new XmlSerializer(typeof(T));
        using (var writer = new StreamWriter(TargetPath, false, encoding))
        {
            serializer.Serialize(writer, data);
            writer.Close();
        }
    }
 
    /// <summary>
    /// XMLファイルを読み込む
    /// </summary>
    public T Read()
    {
        var serializer = new XmlSerializer(typeof(T));
        T obj;
        using (var reader = new StreamReader(TargetPath, encoding))
        {
            obj = (T)serializer.Deserialize(reader);
            reader.Close();
        }
        return obj;
    }
}
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

すると下記のような感じで保存できています。

ここではprivateなフィールドの_genderだけ出力できていません。

それ以外は出力できています。

プロパティを動的に探して色々できる

最後にプロパティは動的に検索ができます。

どういうことかというと、下記のコードを見てください。

プロパティを動的に探して色々できる (csharp)#bf30b0e5ea43
            var props = typeof(User).GetProperties();
 
        var str = new StringBuilder();
        foreach (var user in _users)
        {
            foreach (var prop in props)
            {
                str.Append($@"{prop.Name} : {prop.GetValue(user)}");
                str.Append(Environment.NewLine);
            }
        }
 
        MessageBox.Show(str.ToString());
▸ この snippet は実行結果未収録
▸ 実行結果は未収録です

typeOf()で特定のクラスのType型を取得し、そのType型のGetPropaties関数をするとPropertyInfoの配列が返ってきます。

で、そのPropertyInfoにインスタンスを投げるとそのインスタンス内の指定のプロパティの値が取得できます。

上記の関数を実行すると下記のようになります。

ここではPublicフィールドでも出力されることはありません。

プロパティとフィールド(メンバ変数)は全く違った!

って事でプロパティとフィールドが全く違うことを証明できたかと思います!

以上!

💡 補足: 業務系の現場でよくハマるパターン

俺もこの プロパティ vs フィールドの使い分け、 業務でハマってきたところを3つ並べておきます。

① public フィールドで直接公開して後で苦労

初期は public string Name で十分と思って書いた → 後で「値変更時に通知したい」「バリデーション入れたい」になって全箇所書き直し。 public はプロパティ一択が業務系の定石。

② auto-property の隠れフィールド名問題

public int Age { get; set; } の auto-property は内部で <Age>k__BackingField という隠れフィールドを生成。 リフレクション GetFields() で見ると変な名前のが出てきて混乱する。

③ readonly フィールド + init-only プロパティの混同

readonly int _age はコンストラクタ内のみ代入可。 C# 9 の { get; init; } はコンストラクタ呼び出し時の object initializer も OK。 init の方が使い勝手が良い。 不変オブジェクトを作る時は init で。

❓ よくある質問

Q1. プロパティ vs フィールド、 何で判断する?

A. public はプロパティ、 private はフィールドでOK。 future-proof で考えれば public は全部プロパティ。 readonly フィールドだけ例外。

Q2. struct でもプロパティ使うべき?

A. readonly struct + init-only プロパティの組み合わせが現代的。 mutable struct はバグの温床なので避ける。

Q3. JSON シリアライズで両方使える?

A. System.Text.Json はデフォルトでプロパティのみ。 フィールド対象にしたいなら JsonSerializerOptions { IncludeFields = true }。 業務系では基本プロパティで書く。

Q4. INotifyPropertyChanged はフィールドで実装できる?

A. できない (フィールドに setter が無い)。 必ずプロパティで set { _name = value; OnPropertyChanged(); }。 WPF / WinForms データバインディングで必須。

Q5. 計算プロパティ vs メソッド どっち?

A. 「副作用なし・軽い計算」はプロパティ ( public string FullName => First + Last; )、 「副作用あり・重い処理」はメソッド。 obj.X アクセスで重い処理走ると驚く。

📚 関連記事

執筆者

バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。

🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る

この記事のコードと手順は ぜんぶ動作検証済み。 安心して現場で試してくれ。
バイブス父さん

現役の業務SE。C# / SQL Server 保守の現場から、コードも人もキャリアも全部書く。 実体験ベース。

運営者について