Skip to content

C# 封装和属性

本章将深入探讨面向对象编程的核心原则之一——封装,以及 C# 中属性的高级用法,帮助你构建更安全、更易维护的代码。

封装的概念

什么是封装

csharp
// 封装的基本思想:隐藏内部实现细节,只暴露必要的接口

// ❌ 不好的设计 - 直接暴露字段
public class BadBankAccount
{
    public decimal balance;  // 直接暴露,任何人都可以修改
    public string accountNumber;
    
    public void DisplayBalance()
    {
        Console.WriteLine($"余额: {balance}");
    }
}

// ✅ 好的设计 - 使用封装
public class GoodBankAccount
{
    private decimal balance;  // 私有字段,隐藏实现
    private readonly string accountNumber;
    
    public GoodBankAccount(string accountNumber, decimal initialBalance = 0)
    {
        this.accountNumber = accountNumber;
        this.balance = initialBalance >= 0 ? initialBalance : 0;
    }
    
    // 通过方法控制访问
    public decimal GetBalance()
    {
        return balance;
    }
    
    public bool Deposit(decimal amount)
    {
        if (amount > 0)
        {
            balance += amount;
            return true;
        }
        return false;
    }
    
    public bool Withdraw(decimal amount)
    {
        if (amount > 0 && amount <= balance)
        {
            balance -= amount;
            return true;
        }
        return false;
    }
    
    public string GetAccountNumber()
    {
        return accountNumber;
    }
}

static void EncapsulationDemo()
{
    // 使用封装良好的类
    var account = new GoodBankAccount("12345", 1000m);
    
    Console.WriteLine($"账号: {account.GetAccountNumber()}");
    Console.WriteLine($"余额: {account.GetBalance():C}");
    
    // 通过方法安全地操作数据
    account.Deposit(500m);
    Console.WriteLine($"存款后余额: {account.GetBalance():C}");
    
    bool success = account.Withdraw(2000m);  // 尝试取出超额金额
    Console.WriteLine($"取款{(success ? "成功" : "失败")}");
    Console.WriteLine($"当前余额: {account.GetBalance():C}");
}

封装的好处

csharp
public class Temperature
{
    private double celsius;
    
    // 封装的好处1:数据验证
    public double Celsius
    {
        get { return celsius; }
        set 
        { 
            if (value < -273.15)  // 绝对零度检查
                throw new ArgumentException("温度不能低于绝对零度");
            celsius = value; 
        }
    }
    
    // 封装的好处2:计算属性
    public double Fahrenheit
    {
        get { return celsius * 9.0 / 5.0 + 32; }
        set { Celsius = (value - 32) * 5.0 / 9.0; }
    }
    
    public double Kelvin
    {
        get { return celsius + 273.15; }
        set { Celsius = value - 273.15; }
    }
    
    // 封装的好处3:内部状态管理
    public string TemperatureCategory
    {
        get
        {
            if (celsius < 0) return "冰点以下";
            if (celsius < 10) return "寒冷";
            if (celsius < 20) return "凉爽";
            if (celsius < 30) return "温暖";
            return "炎热";
        }
    }
    
    public Temperature(double celsius = 0)
    {
        Celsius = celsius;  // 使用属性确保验证
    }
    
    public void DisplayTemperature()
    {
        Console.WriteLine($"温度: {Celsius:F1}°C / {Fahrenheit:F1}°F / {Kelvin:F1}K ({TemperatureCategory})");
    }
}

static void TemperatureDemo()
{
    var temp = new Temperature(25);
    temp.DisplayTemperature();
    
    // 通过不同单位设置温度
    temp.Fahrenheit = 100;  // 设置华氏温度
    temp.DisplayTemperature();
    
    temp.Kelvin = 300;      // 设置开尔文温度
    temp.DisplayTemperature();
    
    try
    {
        temp.Celsius = -300;  // 尝试设置无效温度
    }
    catch (ArgumentException ex)
    {
        Console.WriteLine($"错误: {ex.Message}");
    }
}

属性的深入应用

自动属性的演进

csharp
// C# 3.0 自动属性
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// C# 6.0 自动属性初始化器
public class PersonV2
{
    public string Name { get; set; } = "Unknown";
    public int Age { get; set; } = 0;
    public DateTime CreatedDate { get; } = DateTime.Now;  // 只读自动属性
    public List<string> Hobbies { get; } = new List<string>();  // 只读集合属性
}

// C# 7.0+ 表达式体属性
public class PersonV3
{
    private string firstName;
    private string lastName;
    private DateTime birthDate;
    
    public string FirstName 
    { 
        get => firstName; 
        set => firstName = value?.Trim(); 
    }
    
    public string LastName 
    { 
        get => lastName; 
        set => lastName = value?.Trim(); 
    }
    
    public DateTime BirthDate 
    { 
        get => birthDate; 
        set => birthDate = value <= DateTime.Now ? value : throw new ArgumentException("出生日期不能是未来时间"); 
    }
    
    // 计算属性
    public string FullName => $"{FirstName} {LastName}";
    public int Age => DateTime.Now.Year - BirthDate.Year - (DateTime.Now.DayOfYear < BirthDate.DayOfYear ? 1 : 0);
    public bool IsAdult => Age >= 18;
    public string AgeGroup => Age switch
    {
        < 13 => "儿童",
        < 20 => "青少年",
        < 60 => "成年人",
        _ => "老年人"
    };
}

属性访问器的高级用法

csharp
public class SecureProperty
{
    private string password;
    private int loginAttempts = 0;
    private DateTime lastLoginAttempt = DateTime.MinValue;
    private bool isLocked = false;
    
    public string Password
    {
        get 
        { 
            // 密码不能直接获取
            throw new UnauthorizedAccessException("密码不能直接访问");
        }
        set
        {
            // 密码设置时的验证
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("密码不能为空");
                
            if (value.Length < 8)
                throw new ArgumentException("密码长度至少8位");
                
            if (!HasUpperCase(value) || !HasLowerCase(value) || !HasDigit(value))
                throw new ArgumentException("密码必须包含大小写字母和数字");
                
            password = HashPassword(value);  // 存储哈希值
            Console.WriteLine("密码设置成功");
        }
    }
    
    public bool IsLocked 
    { 
        get 
        { 
            // 检查是否因为多次失败尝试而锁定
            if (isLocked && DateTime.Now.Subtract(lastLoginAttempt).TotalMinutes > 30)
            {
                isLocked = false;  // 30分钟后自动解锁
                loginAttempts = 0;
            }
            return isLocked; 
        } 
    }
    
    public int RemainingAttempts => Math.Max(0, 3 - loginAttempts);
    
    public bool VerifyPassword(string inputPassword)
    {
        if (IsLocked)
        {
            Console.WriteLine("账户已锁定,请稍后再试");
            return false;
        }
        
        lastLoginAttempt = DateTime.Now;
        
        if (HashPassword(inputPassword) == password)
        {
            loginAttempts = 0;  // 重置失败次数
            Console.WriteLine("密码验证成功");
            return true;
        }
        else
        {
            loginAttempts++;
            if (loginAttempts >= 3)
            {
                isLocked = true;
                Console.WriteLine("密码错误次数过多,账户已锁定30分钟");
            }
            else
            {
                Console.WriteLine($"密码错误,还有 {RemainingAttempts} 次机会");
            }
            return false;
        }
    }
    
    private bool HasUpperCase(string str) => str.Any(char.IsUpper);
    private bool HasLowerCase(string str) => str.Any(char.IsLower);
    private bool HasDigit(string str) => str.Any(char.IsDigit);
    private string HashPassword(string password) => password.GetHashCode().ToString();  // 简化的哈希
}

static void SecurePropertyDemo()
{
    var secure = new SecureProperty();
    
    try
    {
        // 设置密码
        secure.Password = "MySecure123";
        
        // 验证密码
        secure.VerifyPassword("wrong");
        secure.VerifyPassword("MySecure123");
        
        // 尝试直接获取密码
        // string pwd = secure.Password;  // 这会抛出异常
    }
    catch (Exception ex)
    {
        Console.WriteLine($"异常: {ex.Message}");
    }
}

索引器属性

csharp
public class Matrix
{
    private double[,] data;
    private int rows;
    private int cols;
    
    public Matrix(int rows, int cols)
    {
        this.rows = rows;
        this.cols = cols;
        this.data = new double[rows, cols];
    }
    
    // 索引器 - 允许像数组一样访问对象
    public double this[int row, int col]
    {
        get
        {
            if (row < 0 || row >= rows || col < 0 || col >= cols)
                throw new IndexOutOfRangeException("索引超出范围");
            return data[row, col];
        }
        set
        {
            if (row < 0 || row >= rows || col < 0 || col >= cols)
                throw new IndexOutOfRangeException("索引超出范围");
            data[row, col] = value;
        }
    }
    
    // 字符串索引器
    public double this[string position]
    {
        get
        {
            var parts = position.Split(',');
            if (parts.Length != 2 || 
                !int.TryParse(parts[0], out int row) || 
                !int.TryParse(parts[1], out int col))
                throw new ArgumentException("位置格式错误,应为 'row,col'");
            return this[row, col];
        }
        set
        {
            var parts = position.Split(',');
            if (parts.Length != 2 || 
                !int.TryParse(parts[0], out int row) || 
                !int.TryParse(parts[1], out int col))
                throw new ArgumentException("位置格式错误,应为 'row,col'");
            this[row, col] = value;
        }
    }
    
    public int Rows => rows;
    public int Cols => cols;
    
    public void Fill(double value)
    {
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                data[i, j] = value;
            }
        }
    }
    
    public void DisplayMatrix()
    {
        Console.WriteLine($"Matrix ({rows}x{cols}):");
        for (int i = 0; i < rows; i++)
        {
            for (int j = 0; j < cols; j++)
            {
                Console.Write($"{data[i, j],8:F2}");
            }
            Console.WriteLine();
        }
    }
}

public class Dictionary<TKey, TValue>
{
    private List<KeyValuePair<TKey, TValue>> items = new List<KeyValuePair<TKey, TValue>>();
    
    // 索引器实现字典功能
    public TValue this[TKey key]
    {
        get
        {
            var item = items.FirstOrDefault(kvp => kvp.Key.Equals(key));
            if (item.Equals(default(KeyValuePair<TKey, TValue>)))
                throw new KeyNotFoundException($"键 '{key}' 不存在");
            return item.Value;
        }
        set
        {
            var index = items.FindIndex(kvp => kvp.Key.Equals(key));
            if (index >= 0)
            {
                items[index] = new KeyValuePair<TKey, TValue>(key, value);
            }
            else
            {
                items.Add(new KeyValuePair<TKey, TValue>(key, value));
            }
        }
    }
    
    public bool ContainsKey(TKey key)
    {
        return items.Any(kvp => kvp.Key.Equals(key));
    }
    
    public void Remove(TKey key)
    {
        items.RemoveAll(kvp => kvp.Key.Equals(key));
    }
    
    public IEnumerable<TKey> Keys => items.Select(kvp => kvp.Key);
    public IEnumerable<TValue> Values => items.Select(kvp => kvp.Value);
    public int Count => items.Count;
}

static void IndexerDemo()
{
    // 矩阵索引器
    var matrix = new Matrix(3, 3);
    
    // 使用数字索引器
    matrix[0, 0] = 1.0;
    matrix[1, 1] = 2.0;
    matrix[2, 2] = 3.0;
    
    // 使用字符串索引器
    matrix["0,1"] = 0.5;
    matrix["1,0"] = 0.5;
    
    matrix.DisplayMatrix();
    
    Console.WriteLine($"matrix[1,1] = {matrix[1, 1]}");
    Console.WriteLine($"matrix['0,1'] = {matrix["0,1"]}");
    
    // 自定义字典索引器
    var dict = new Dictionary<string, int>();
    dict["apple"] = 5;
    dict["banana"] = 3;
    dict["orange"] = 8;
    
    Console.WriteLine($"\n字典内容:");
    foreach (var key in dict.Keys)
    {
        Console.WriteLine($"{key}: {dict[key]}");
    }
}

字段与属性的选择

何时使用字段,何时使用属性

csharp
public class FieldVsPropertyDemo
{
    // ✅ 使用字段的情况:
    
    // 1. 常量
    public const double PI = 3.14159265359;
    
    // 2. 静态只读字段
    public static readonly DateTime ApplicationStartTime = DateTime.Now;
    
    // 3. 私有实现细节
    private List<string> items = new List<string>();
    private int cacheVersion = 0;
    
    // ✅ 使用属性的情况:
    
    // 1. 公共数据访问
    public string Name { get; set; }
    
    // 2. 需要验证的数据
    private int age;
    public int Age
    {
        get => age;
        set => age = value >= 0 ? value : throw new ArgumentException("年龄不能为负数");
    }
    
    // 3. 计算属性
    public bool IsAdult => Age >= 18;
    
    // 4. 延迟计算
    private string expensiveCalculationResult;
    public string ExpensiveCalculation
    {
        get
        {
            if (expensiveCalculationResult == null)
            {
                // 模拟昂贵的计算
                Thread.Sleep(100);
                expensiveCalculationResult = $"计算结果_{DateTime.Now.Ticks}";
            }
            return expensiveCalculationResult;
        }
    }
    
    // 5. 通知属性更改
    public event PropertyChangedEventHandler PropertyChanged;
    
    private string description;
    public string Description
    {
        get => description;
        set
        {
            if (description != value)
            {
                description = value;
                OnPropertyChanged();
            }
        }
    }
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    // 6. 版本控制和缓存失效
    private string cachedData;
    public string CachedData
    {
        get
        {
            if (cachedData == null || cacheVersion < GetCurrentVersion())
            {
                cachedData = LoadData();
                cacheVersion = GetCurrentVersion();
            }
            return cachedData;
        }
    }
    
    private string LoadData() => $"数据_{DateTime.Now.Millisecond}";
    private int GetCurrentVersion() => DateTime.Now.Second;
}

// 属性更改通知接口
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

public class PropertyChangedEventArgs : EventArgs
{
    public string PropertyName { get; }
    
    public PropertyChangedEventArgs(string propertyName)
    {
        PropertyName = propertyName;
    }
}

public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

// CallerMemberName 属性
public class CallerMemberNameAttribute : Attribute { }

实践示例

示例 1:配置管理类

csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

public class ConfigurationManager
{
    private static ConfigurationManager instance;
    private static readonly object lockObject = new object();
    private Dictionary<string, object> settings;
    private string configFilePath;
    private DateTime lastModified;
    
    // 单例模式
    public static ConfigurationManager Instance
    {
        get
        {
            if (instance == null)
            {
                lock (lockObject)
                {
                    if (instance == null)
                        instance = new ConfigurationManager();
                }
            }
            return instance;
        }
    }
    
    private ConfigurationManager()
    {
        configFilePath = "config.json";
        settings = new Dictionary<string, object>();
        LoadConfiguration();
    }
    
    // 索引器用于访问配置项
    public T GetValue<T>(string key, T defaultValue = default(T))
    {
        CheckForConfigChanges();
        
        if (settings.TryGetValue(key, out object value))
        {
            try
            {
                if (value is JsonElement jsonElement)
                {
                    return JsonSerializer.Deserialize<T>(jsonElement.GetRawText());
                }
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch
            {
                return defaultValue;
            }
        }
        return defaultValue;
    }
    
    public void SetValue<T>(string key, T value)
    {
        CheckForConfigChanges();
        settings[key] = value;
        SaveConfiguration();
    }
    
    // 强类型配置属性
    public string DatabaseConnectionString
    {
        get => GetValue<string>("DatabaseConnectionString", "Server=localhost;Database=MyApp;");
        set => SetValue("DatabaseConnectionString", value);
    }
    
    public int MaxRetryAttempts
    {
        get => GetValue<int>("MaxRetryAttempts", 3);
        set => SetValue("MaxRetryAttempts", Math.Max(1, value));
    }
    
    public TimeSpan RequestTimeout
    {
        get => TimeSpan.FromSeconds(GetValue<double>("RequestTimeoutSeconds", 30.0));
        set => SetValue("RequestTimeoutSeconds", value.TotalSeconds);
    }
    
    public bool EnableLogging
    {
        get => GetValue<bool>("EnableLogging", true);
        set => SetValue("EnableLogging", value);
    }
    
    public LogLevel LogLevel
    {
        get => Enum.Parse<LogLevel>(GetValue<string>("LogLevel", "Information"));
        set => SetValue("LogLevel", value.ToString());
    }
    
    // 嵌套配置对象
    public EmailSettings Email => new EmailSettings(this);
    
    private void LoadConfiguration()
    {
        try
        {
            if (File.Exists(configFilePath))
            {
                string json = File.ReadAllText(configFilePath);
                settings = JsonSerializer.Deserialize<Dictionary<string, object>>(json) ?? new Dictionary<string, object>();
                lastModified = File.GetLastWriteTime(configFilePath);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"加载配置文件失败: {ex.Message}");
            settings = new Dictionary<string, object>();
        }
    }
    
    private void SaveConfiguration()
    {
        try
        {
            string json = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
            File.WriteAllText(configFilePath, json);
            lastModified = File.GetLastWriteTime(configFilePath);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"保存配置文件失败: {ex.Message}");
        }
    }
    
    private void CheckForConfigChanges()
    {
        if (File.Exists(configFilePath))
        {
            var currentModified = File.GetLastWriteTime(configFilePath);
            if (currentModified > lastModified)
            {
                LoadConfiguration();
            }
        }
    }
    
    public void ReloadConfiguration()
    {
        LoadConfiguration();
    }
    
    public IReadOnlyDictionary<string, object> GetAllSettings()
    {
        CheckForConfigChanges();
        return new Dictionary<string, object>(settings);
    }
}

public class EmailSettings
{
    private readonly ConfigurationManager config;
    
    internal EmailSettings(ConfigurationManager config)
    {
        this.config = config;
    }
    
    public string SmtpServer
    {
        get => config.GetValue<string>("Email.SmtpServer", "smtp.gmail.com");
        set => config.SetValue("Email.SmtpServer", value);
    }
    
    public int SmtpPort
    {
        get => config.GetValue<int>("Email.SmtpPort", 587);
        set => config.SetValue("Email.SmtpPort", value);
    }
    
    public string Username
    {
        get => config.GetValue<string>("Email.Username", "");
        set => config.SetValue("Email.Username", value);
    }
    
    public bool EnableSsl
    {
        get => config.GetValue<bool>("Email.EnableSsl", true);
        set => config.SetValue("Email.EnableSsl", value);
    }
}

public enum LogLevel
{
    Debug,
    Information,
    Warning,
    Error,
    Critical
}

static void ConfigurationDemo()
{
    var config = ConfigurationManager.Instance;
    
    // 使用强类型属性
    config.DatabaseConnectionString = "Server=prod-server;Database=MyApp;";
    config.MaxRetryAttempts = 5;
    config.RequestTimeout = TimeSpan.FromMinutes(2);
    config.EnableLogging = true;
    config.LogLevel = LogLevel.Warning;
    
    // 使用嵌套配置
    config.Email.SmtpServer = "smtp.company.com";
    config.Email.SmtpPort = 25;
    config.Email.EnableSsl = false;
    
    // 使用泛型方法
    config.SetValue("CustomSetting", "CustomValue");
    string customValue = config.GetValue<string>("CustomSetting");
    
    Console.WriteLine("当前配置:");
    Console.WriteLine($"数据库连接: {config.DatabaseConnectionString}");
    Console.WriteLine($"最大重试次数: {config.MaxRetryAttempts}");
    Console.WriteLine($"请求超时: {config.RequestTimeout}");
    Console.WriteLine($"启用日志: {config.EnableLogging}");
    Console.WriteLine($"日志级别: {config.LogLevel}");
    Console.WriteLine($"SMTP服务器: {config.Email.SmtpServer}");
    Console.WriteLine($"自定义设置: {customValue}");
    
    // 显示所有设置
    Console.WriteLine("\n所有配置项:");
    foreach (var setting in config.GetAllSettings())
    {
        Console.WriteLine($"  {setting.Key}: {setting.Value}");
    }
}

示例 2:数据验证和绑定

csharp
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;

public class ValidationAttribute : Attribute
{
    public string ErrorMessage { get; set; }
}

public class RequiredAttribute : ValidationAttribute
{
    public RequiredAttribute()
    {
        ErrorMessage = "此字段为必填项";
    }
}

public class RangeAttribute : ValidationAttribute
{
    public double Minimum { get; }
    public double Maximum { get; }
    
    public RangeAttribute(double minimum, double maximum)
    {
        Minimum = minimum;
        Maximum = maximum;
        ErrorMessage = $"值必须在 {minimum} 到 {maximum} 之间";
    }
}

public class EmailAttribute : ValidationAttribute
{
    public EmailAttribute()
    {
        ErrorMessage = "请输入有效的邮箱地址";
    }
}

public class ValidatableObject : INotifyPropertyChanged, IDataErrorInfo
{
    private Dictionary<string, string> errors = new Dictionary<string, string>();
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        ValidateProperty(propertyName);
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    
    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
            
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
    
    private void ValidateProperty(string propertyName)
    {
        var property = GetType().GetProperty(propertyName);
        if (property == null) return;
        
        var value = property.GetValue(this);
        var attributes = property.GetCustomAttributes(typeof(ValidationAttribute), true)
                                .Cast<ValidationAttribute>();
        
        errors.Remove(propertyName);
        
        foreach (var attribute in attributes)
        {
            bool isValid = attribute switch
            {
                RequiredAttribute => value != null && !string.IsNullOrWhiteSpace(value.ToString()),
                RangeAttribute range when value is IComparable comparable => 
                    comparable.CompareTo(range.Minimum) >= 0 && comparable.CompareTo(range.Maximum) <= 0,
                EmailAttribute => value?.ToString()?.Contains("@") == true && value.ToString().Contains("."),
                _ => true
            };
            
            if (!isValid)
            {
                errors[propertyName] = attribute.ErrorMessage;
                break;
            }
        }
    }
    
    public string Error => string.Join("; ", errors.Values);
    
    public string this[string columnName] => errors.TryGetValue(columnName, out string error) ? error : null;
    
    public bool IsValid => !errors.Any();
    
    public Dictionary<string, string> GetAllErrors() => new Dictionary<string, string>(errors);
}

public class User : ValidatableObject
{
    private string firstName;
    private string lastName;
    private string email;
    private int age;
    private decimal salary;
    
    [Required]
    public string FirstName
    {
        get => firstName;
        set => SetProperty(ref firstName, value);
    }
    
    [Required]
    public string LastName
    {
        get => lastName;
        set => SetProperty(ref lastName, value);
    }
    
    [Required]
    [Email]
    public string Email
    {
        get => email;
        set => SetProperty(ref email, value);
    }
    
    [Range(0, 150)]
    public int Age
    {
        get => age;
        set => SetProperty(ref age, value);
    }
    
    [Range(0, 1000000)]
    public decimal Salary
    {
        get => salary;
        set => SetProperty(ref salary, value);
    }
    
    // 计算属性
    public string FullName => $"{FirstName} {LastName}".Trim();
    public bool IsAdult => Age >= 18;
    public string SalaryCategory => Salary switch
    {
        < 30000 => "入门级",
        < 60000 => "中级",
        < 100000 => "高级",
        _ => "专家级"
    };
    
    public override string ToString()
    {
        return $"{FullName} ({Age}岁) - {Email} - {Salary:C} ({SalaryCategory})";
    }
}

// 接口定义
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

public interface IDataErrorInfo
{
    string Error { get; }
    string this[string columnName] { get; }
}

public class PropertyChangedEventArgs : EventArgs
{
    public string PropertyName { get; }
    
    public PropertyChangedEventArgs(string propertyName)
    {
        PropertyName = propertyName;
    }
}

public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

public class CallerMemberNameAttribute : Attribute { }

static void ValidationDemo()
{
    var user = new User();
    
    // 订阅属性更改事件
    user.PropertyChanged += (sender, e) =>
    {
        Console.WriteLine($"属性 '{e.PropertyName}' 已更改");
        var error = user[e.PropertyName];
        if (!string.IsNullOrEmpty(error))
        {
            Console.WriteLine($"  验证错误: {error}");
        }
    };
    
    Console.WriteLine("=== 用户数据验证演示 ===");
    
    // 设置有效数据
    Console.WriteLine("\n设置有效数据:");
    user.FirstName = "张";
    user.LastName = "三";
    user.Email = "zhangsan@example.com";
    user.Age = 28;
    user.Salary = 75000m;
    
    Console.WriteLine($"用户信息: {user}");
    Console.WriteLine($"数据有效性: {user.IsValid}");
    
    // 设置无效数据
    Console.WriteLine("\n设置无效数据:");
    user.FirstName = "";  // 必填项为空
    user.Email = "invalid-email";  // 无效邮箱
    user.Age = 200;  // 超出范围
    user.Salary = -1000;  // 负数薪资
    
    Console.WriteLine($"数据有效性: {user.IsValid}");
    Console.WriteLine("所有验证错误:");
    foreach (var error in user.GetAllErrors())
    {
        Console.WriteLine($"  {error.Key}: {error.Value}");
    }
    
    // 修正数据
    Console.WriteLine("\n修正数据:");
    user.FirstName = "李";
    user.Email = "lisi@example.com";
    user.Age = 30;
    user.Salary = 80000m;
    
    Console.WriteLine($"修正后用户信息: {user}");
    Console.WriteLine($"数据有效性: {user.IsValid}");
}

本章小结

本章深入探讨了 C# 中的封装和属性:

关键要点:

  • 封装原则:隐藏实现细节,提供受控的访问接口
  • 属性优势:数据验证、计算属性、延迟加载、通知机制
  • 索引器:使对象支持类似数组或字典的访问语法
  • 字段vs属性:根据使用场景选择合适的数据暴露方式

封装的好处:

  • 数据安全:防止无效数据破坏对象状态
  • 代码维护:内部实现变更不影响外部代码
  • 功能扩展:可以在不改变接口的情况下添加功能
  • 调试支持:可以在属性访问时添加日志和断点

最佳实践:

  • 优先使用属性而不是公共字段
  • 为属性添加适当的验证逻辑
  • 使用只读属性暴露计算结果
  • 实现 INotifyPropertyChanged 支持数据绑定
  • 合理使用索引器简化对象访问

设计原则:

  • 最小权限原则:只暴露必要的接口
  • 单一职责:每个属性应该有明确的职责
  • 开闭原则:对扩展开放,对修改关闭

下一步: 在下一章中,我们将学习继承,了解如何通过继承实现代码重用和扩展。

延伸阅读

本站内容仅供学习和研究使用。