首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 自動實作屬性與反射的意外行為:基類自動屬性導致衍生類字段丟失

     編輯:關於C#

前言

在 C# 中,自動實作屬性(Auto-Implemented Properties)讓程式碼更簡潔,但當搭配反射機制使用時,可能會產生令人驚訝的結果。本文透過一個具體範例,示範當基類使用自動實作屬性時,透過反射取得衍生類別的欄位資訊時,會遺漏基類自動屬性對應的後端欄位。

程式碼範例

以下定義了三個類別:Person(基類)、Employee(衍生類別)以及 Program(測試程式)。

Person.cs - 基類

using System;

namespace CompilerGeneratedProps
{
    public class Person
    {
        // 傳統屬性:有明確的後端欄位
        protected string _LastName; // 宣告為 protected 以利擴充

        public string FirstName { get; set; } // 自動實作屬性

        public string LastName
        {
            get { return this._LastName; }
            set { this._LastName = value; }
        }
    }
}

Employee.cs - 衍生類別

using System;

namespace CompilerGeneratedProps
{
    public class Employee : Person
    {
        // 欄位
        protected string Title;
    }
}

Program.cs - 反射測試

using System;
using System.Reflection;

namespace CompilerGeneratedProps
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Fields seen in an instance of Person");
            Console.WriteLine("-------------------------------------------------------");
            Person objPerson = new Person();
            foreach (FieldInfo fi in objPerson.GetType().GetFields(
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            {
                Console.WriteLine(fi.Name);
            }

            Console.WriteLine("\n\n\n\nFields seen in an instance of Employee");
            Console.WriteLine("-------------------------------------------------------");
            Employee objEmployee = new Employee();
            foreach (FieldInfo fi in objEmployee.GetType().GetFields(
                BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            {
                Console.WriteLine(fi.Name);
            }
            Console.Read();
        }
    }
}

執行結果

反射輸出結果

現象分析

從輸出結果可以觀察到:

  • Person 實例:顯示 _LastName 與 <FirstName>k__BackingField 兩個欄位。自動實作屬性 FirstName 編譯器會自動產生一個名為 <FirstName>k__BackingField 的後端欄位。
  • Employee 實例:僅顯示 Title 與 _LastName,缺少 基類自動實作屬性 FirstName 對應的後端欄位。

原因

當使用 GetFields 並指定 BindingFlags.DeclaredOnly(預設不包含繼承的欄位)時,反射只會回傳該類別直接宣告的欄位。基類的自動實作屬性產生的後端欄位屬於基類宣告,不會出現在衍生類別的欄位列表中。若需取得繼承鏈上的所有欄位,必須遞迴遍歷基類,或使用 GetFields() 不指定 DeclaredOnly(但預設行為已包含繼承的 public/protected 欄位,實測仍遺漏私有後端欄位)。

解決方案與建議

  1. 避免在基類使用自動實作屬性:若基類預期會被反射檢查欄位,改用傳統屬性並明確宣告後端欄位(如 LastName 範例),確保欄位可被反射發現。
  2. 反射時遞迴搜尋基類:撰寫遞迴方法,從衍生類別往上遍歷 BaseType,收集各層級的欄位。
  3. 使用 PropertyInfo 而非 FieldInfo:若目的是存取屬性值,直接用 GetProperties 取得屬性資訊,再透過 GetValue/SetValue 操作,不需關心後端欄位。

結語

自動實作屬性雖方便,但在涉及反射、序列化、OR Mapping 等依賴後端欄位的場景時,需謹慎評估。理解編譯器自動產生欄位的命名規則(<PropertyName>k__BackingField)與繼承關係下的反射行為,有助於避開這類「意想不到的行為」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved