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

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

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

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

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

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

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

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

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

[s_ad]

目次

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

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

結論はい!

ということです。

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

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

なので下記の書き方は

        private string _gender;
        public string Gender
        {
            get { return _gender; }
            set { _gender = value; }
        }

下記と一緒です。

        public string Gender { get; set; }

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

        private string _gender;
        public string Gender
        {
            get => _gender;
            set => _gender = value;
        }

プロパティのおいしさ

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

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

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

Datagridに値を表示できる

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

    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()
    {
        
    }
}</code></pre></div>

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

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

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

        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;
        }

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

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

[s_ad]

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

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

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

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

    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();
        }
    }

    /// &lt;summary&gt;
    /// XMLファイルを読み込む
    /// &lt;/summary&gt;
    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;
    }
}</code></pre></div>

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

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

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

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

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

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

            var props = typeof(User).GetProperties();

        var str = new StringBuilder();
        foreach (var user in _users)
        {
            foreach (var prop in props)
            {
                str.Append($@&quot;{prop.Name} : {prop.GetValue(user)}&quot;);
                str.Append(Environment.NewLine);
            }
        }

        MessageBox.Show(str.ToString());</code></pre></div>

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 アクセスで重い処理走ると驚く。

📚 関連記事

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次