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

C# 屬性完整指南:自動屬性、存取子、抽象屬性與繼承覆寫實戰

     編輯:關於C語言

屬性:封裝欄位的優雅介面

屬性提供「欄位般的語法,方法般的封裝」,是 C# 物件導向封裝的核心機制。優於 Java 的 getter/setter 慣例,優於 C++ 的公開欄位。

基本屬性語法

public class Person
{
    // 私有備份欄位
    private string _name = "N/A";
    private int _age = 0;

    // 完整屬性宣告
    public string Name
    {
        get => _name;                    // 讀取
        set => _name = value ?? "N/A";   // 寫入 (value 為隱含參數)
    }

    public int Age
    {
        get => _age;
        set
        {
            if (value < 0 || value > 150)
                throw new ArgumentOutOfRangeException(nameof(value), "年齡必須 0-150");
            _age = value;
        }
    }

    // 唯讀屬性 (僅 get)
    public string Id => Guid.NewGuid().ToString("N")[..8];

    // 唯寫屬性 (僅 set) - 極少用
    public string WriteOnlySecret { set { /* 儲存至安全存儲 */ } }

    public override string ToString() => $"Name={Name}, Age={Age}";
}

// 使用
var p = new Person();
p.Name = "Joe";    // 呼叫 set
p.Age = 99;
Console.WriteLine(p.Name); // 呼叫 get
p.Age += 1;        // get + set
Console.WriteLine(p);      // Name=Joe, Age=100

自動實作屬性

編譯器自動產生隱私備份欄位,簡化程式碼:

public class Product
{
    // 僅需指定型別與名稱
    public string SKU { get; set; }
    public decimal Price { get; set; }
    
    // 唯讀自動屬性 (C# 6+),建構函式初始化
    public DateTime CreatedAt { get; } = DateTime.UtcNow;
    
    // init 存取子 (C# 9+):物件初始化器可設定,之後不可變
    public string Category { get; init; }
}

// 使用
var product = new Product 
{ 
    SKU = "ABC-123", 
    Price = 99.9m, 
    Category = "Electronics" 
};
// product.CreatedAt = DateTime.Now; // 編譯錯誤:唯讀
// product.Category = "Books";       // 編譯錯誤:init 僅限初始化

屬性存取權限非對稱

get/set 可有不同存取層級:

public class BankAccount
{
    public decimal Balance { get; private set; } // 外部可讀,內部可寫
    public string Owner { get; } = "John";       // 唯讀
    
    // 內部邏輯控制
    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("金額需為正");
        Balance += amount;
    }
}

抽象屬性與介面屬性

定義「契約」而非實作,強制衍生類別提供具體行為:

// 抽象基底類別
public abstract class Shape
{
    protected string _id;
    
    public string Id 
    { 
        get => _id; 
        protected set => _id = value; 
    }

    // 抽象屬性:僅宣告 get,不提供實作
    public abstract double Area { get; }
    
    // 抽象屬性:同時需要 get/set
    public abstract double Perimeter { get; set; }

    public Shape(string id) => Id = id;
    
    public override string ToString() => $"{Id}: Area={Area:F2}";
}

// 衍生類別必須實作所有抽象屬性
public class Circle : Shape
{
    private double _radius;
    
    public Circle(string id, double radius) : base(id) => _radius = radius;
    
    // 實作抽象唯讀屬性
    public override double Area => Math.PI * _radius * _radius;
    
    // 實作抽象讀寫屬性 (需自行管理備份欄位)
    private double _perimeter;
    public override double Perimeter 
    { 
        get => _perimeter; 
        set => _perimeter = value > 0 ? value : throw new ArgumentException("周長需為正"); 
    }
}

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    public Rectangle(string id, double w, double h) : base(id) 
    { 
        Width = w; 
        Height = h; 
    }
    
    public override double Area => Width * Height;
    
    public override double Perimeter 
    { 
        get => 2 * (Width + Height); 
        set => throw new NotSupportedException("矩形周長由寬高決定,不可直接設定"); 
    }
}

// 多型使用
Shape[] shapes = 
{
    new Circle("C1", 5),
    new Rectangle("R1", 4, 5)
};

foreach (var s in shapes)
{
    Console.WriteLine(s); // 呼叫 ToString() → 使用 Area 屬性
}
// 輸出:
// C1: Area=78.54
// R1: Area=20.00

介面定義屬性

public interface IEntity
{
    int Id { get; set; }
    DateTime CreatedAt { get; }
    bool IsActive { get; set; }
}

public class User : IEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; } = DateTime.UtcNow;
    public bool IsActive { get; set; } = true;
    public string Username { get; set; }
}

進階模式

1. 通知屬性變更 (INotifyPropertyChanged)

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

public class ViewModel : ObservableObject
{
    private string _title;
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }
}

2. 索引器:陣列式存取語法

public class GradeBook
{
    private readonly Dictionary _grades = new();
    
    public int this[string studentName]
    {
        get => _grades.TryGetValue(studentName, out var g) ? g : 0;
        set => _grades[studentName] = value;
    }
    
    public IEnumerable Students => _grades.Keys;
}

// 使用
var book = new GradeBook();
book["Alice"] = 95;
book["Bob"] = 87;
Console.WriteLine(book["Alice"]); // 95

3. 運算式主體成員 (C# 6+)

public class MathUtils
{
    // 唯讀屬性
    public double Pi => Math.PI;
    
    // 含邏輯的屬性
    public double CircleArea(double r) => Math.PI * r * r;
    
    // 建構函式
    public MathUtils() => Console.WriteLine("Initialized");
    
    // 方法
    public double Add(double a, double b) => a + b;
}

常見陷阱與最佳實務

陷阱後果解決方案
屬性執行耗時操作 (IO、計算、查詢)呼叫者誤以為輕量,導致效能問題改用方法 (如 GetDataAsync()),或快取結果
set 存取子拋出例外但未驗證物件進入無效狀態驗證優先,失敗拋 ArgumentException,不修改欄位
自動屬性隱藏備份欄位導致無法序列化控制無法自訂 JSON/XML 序列化行為改用完整屬性 + [JsonProperty] 等屬性
抽象屬性在建構函式中被呼叫衍生類別尚未初始化,存取未就緒欄位避免在基底建構函式呼叫虛擬/抽象成員
大量唯讀自動屬性未初始化預設值 (null/0/false) 可能非預期使用物件初始化器、建構函式或 ?? 提供預設值

總結

C# 屬性提供:語法糖 (自動屬性)、封裝控制 (存取權限非對稱)、契約定義 (抽象/介面屬性)、擴充點 (INotifyPropertyChanged、索引器)。掌握 get/set/init、自動屬性、抽象屬性多型、運算式主體等現代語法,並避開「屬性做太多事」的反模式,即能撰寫清晰、安全、可維護的物件導向代碼。

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