You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MVVM模式下批量实现对象属性OnPropertyChanged触发方案咨询

问题

我正在用MVVM模式开发,有个包含约50个属性的ClientClass对象。视图里把Picker和Image图标都绑定到Client.ClientType,图标会根据这个属性值变化。但修改ClientType时,OnPropertyChanged没触发,只有给Client重新赋值新的ClientClass实例(比如Client = new ClientClass())才会触发。

我知道可以用包装属性的方式强制触发,代码如下:

public int ClientType {
    get => client.ClientType;
    set {
        if (client.ClientType == value) return;
        client.ClientType = value;
        OnPropertyChanged(nameof(Client));
    }
}

但不想给50个属性都写一遍这种代码。有没有不用逐个声明属性的简便方法实现类似效果?

简化代码示例:

public class BaseVM : INotifyPropertyChanged {
    bool isBusy;
    string? title;
            
    public bool IsBusy {
        get => isBusy;
        set {
            if (isBusy == value) return;
            isBusy = value;
            OnPropertyChanged();
        }
    }
            
    public event PropertyChangedEventHandler? PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string? name = null) =>
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}


public class ClientEditorVM : BaseVM {
    private ClientClass client;
    public ClientClass Client{
        get => client;
        set {
            if (client.Equals(value)) return;
            client = value;
            OnPropertyChanged();
        }
    }

// 这个方法可行,但要重复写50次?
public int ClientType {
    get => client.ClientType;
    set {
        if (client.ClientType == value) return;
        client.ClientType = value;
        OnPropertyChanged(nameof(Client));
    }
}


    public ClientEditorVM(ClientClass c){
        Client = c;
    }
}

public class ClientClass {
    public int ID { get; }
    public int ClientType { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    ...
    // 约50个属性,没有实现OnPropertyChanged,且无法修改这个类
}

解决方案

方法1:创建ClientClass的代理包装类(自动生成属性)

既然不能修改ClientClass,可以写一个继承INotifyPropertyChanged的代理类,内部持有ClientClass实例,再用代码生成工具(比如T4模板、Roslyn代码生成)自动遍历ClientClass的所有属性,生成对应的包装代码,避免手动写50次。

示例框架

public class ClientProxy : INotifyPropertyChanged {
    private readonly ClientClass _originalClient;

    public ClientProxy(ClientClass original) {
        _originalClient = original;
    }

    // 以ClientType为例,其他属性由工具自动生成
    public int ClientType {
        get => _originalClient.ClientType;
        set {
            if (_originalClient.ClientType == value) return;
            _originalClient.ClientType = value;
            OnPropertyChanged(nameof(ClientType));
        }
    }

    // 其他属性的包装代码自动生成...

    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

ViewModel改造

把原Client属性替换为ClientProxy类型:

public class ClientEditorVM : BaseVM {
    private ClientProxy _client;
    public ClientProxy Client{
        get => _client;
        set {
            if (_client?.OriginalClient.Equals(value.OriginalClient) ?? false) return;
            _client = value;
            OnPropertyChanged();
        }
    }

    public ClientEditorVM(ClientClass c){
        Client = new ClientProxy(c);
    }
}

之后视图绑定Client.ClientType时,属性变更会直接触发通知,且无需手动编写所有属性的包装代码。

方法2:用动态代理拦截属性变更

借助动态代理库(比如Castle DynamicProxy),创建ClientClass的代理对象,拦截所有属性的set操作,自动触发ViewModel的Client属性变更通知。

拦截器实现

using Castle.DynamicProxy;

public class ClientPropertyInterceptor : IInterceptor {
    private readonly BaseVM _viewModel;

    public ClientPropertyInterceptor(BaseVM viewModel) {
        _viewModel = viewModel;
    }

    public void Intercept(IInvocation invocation) {
        // 只拦截属性的set方法
        if (invocation.Method.Name.StartsWith("set_")) {
            invocation.Proceed();
            // 触发ViewModel中Client属性的变更通知
            _viewModel.OnPropertyChanged(nameof(ClientEditorVM.Client));
        } else {
            invocation.Proceed();
        }
    }
}

ViewModel改造

public class ClientEditorVM : BaseVM {
    private ClientClass _client;
    public ClientClass Client{
        get => _client;
        set {
            // 为原始Client对象创建代理
            var proxyGenerator = new ProxyGenerator();
            _client = proxyGenerator.CreateClassProxyWithTarget(value, new ClientPropertyInterceptor(this));
            OnPropertyChanged();
        }
    }

    public ClientEditorVM(ClientClass c){
        Client = c;
    }
}

这样不管修改Client的哪个属性,代理都会自动拦截并触发通知,视图绑定的内容会同步更新。

方法3:零散修改场景下的手动触发

如果修改Client属性的代码位置不多,可以在每次修改后手动调用通知:

// 修改ClientType后触发通知
Client.ClientType = newType;
OnPropertyChanged(nameof(Client));

但此方法仅适合修改点较少的场景,修改点多的话还是推荐前两种方案。


内容的提问来源于stack exchange,提问作者Abelucho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 19:05:23