Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用属性更改通知和继承_C#_Mvvm - Fatal编程技术网

C# 使用属性更改通知和继承

C# 使用属性更改通知和继承,c#,mvvm,C#,Mvvm,我有一个实现属性更改通知的基类。为了清晰起见,我省略了实现技术细节 public class PersonDTO : INotifyPropertyChanged { // in real these all have the backing field + equality check in setter + OnChanged call implementations public string Name { get; set; } public int Age { get;

我有一个实现属性更改通知的基类。为了清晰起见,我省略了实现技术细节

public class PersonDTO : INotifyPropertyChanged {

  // in real these all have the backing field + equality check in setter + OnChanged call implementations
  public string Name { get; set; }
  public int Age { get; set; }
  public Gender Gender { get; set; }

  public PersonDTO() {
    // initialize default values
    // this invoke OnChanged so the object state can be maintained
    Name = "New person";
    Age = 30;
    Gender = Gender.Female;
  }

  protected virtual void OnChanged(string propertyName) {
    // raise PropertyChanged
  }
}
我有另一个类,它继承自
PersonDTO
,并添加了一些属性

public class PersonEditorModel : PersonDTO {

  public BindingList<string> Titles { get; private set; }

  private readonly IRepository _repository;
  public PersonEditorModel(IRepository repository) {
    _repository = repository;
  }

  protected override void OnChanged(string propertyname) {
    if (propertyName == "Gender") {
       // Here is a NullReferenceException
       Titles.Clear();
       if (Gender == Gender.Female) {
         Titles.AddRange(new[] {"Ms", "Mrs"});
       else
         Titles.AddRange(new[] {"Mr", "Sir"});
    }
    // do some other things perhaps using the _repository (which would raise a NullReferenceException again)
  }
}
公共类PersonEditorModel:PersonDTO{
公共绑定列表标题{get;private set;}
专用只读IRepository存储库;
公共PersonEditorModel(IRepository存储库){
_存储库=存储库;
}
已更改受保护的重写void(字符串propertyname){
如果(propertyName==“性别”){
//这里有一个NullReferenceException
标题。清除();
如果(性别==性别。女性){
Titles.AddRange(新[]{“Ms”、“Mrs”});
其他的
Titles.AddRange(新[]{“先生”、“先生”});
}
//使用_存储库做一些其他事情(这会再次引发NullReferenceException)
}
}
此模型的问题在于,在基本构造函数中,设置属性会调用更改通知,并且子类中的
OnChanged
方法在尚未构造子类时执行(
Titles
list为null)

我一直在想的几种方法。

  • 使用基本构造函数中的支持字段。这将修复异常,但对象状态没有相应地刷新。我需要始终保持一致的状态,这意味着
    性别
    标题
    应该同步

  • 包括一个表示对象已构造的标志,并在
    OnChanged
    中检查该标志。这对于类似这样的简单情况是有效的,但如果我有一个3级层次结构会怎么样。我需要确保在最底层的构造函数完成运行时设置该标志,这一点并不简单

  • 在基类中使用工厂方法模式,在构建之后,我将调用类似
    StartChangeTracking()的东西
    。这是有问题的,因为子类可以有不同的构造函数参数,就像在本例中的
    IRepository
    服务一样。此外,工厂方法模式会使例如Json序列化/反序列化变得相当困难(我指的是那些没有构造函数参数的类)


  • 这里有两种选择。首先,您可以直接分配
    标题
    ,如下所示,假设您永远不会重新分配
    绑定列表
    (通常在MVVM中,我们使用
    可观察集合
    )我已将其设置为只读。这确保了
    标题
    永远不会为空,并且您不必执行
    -检查

    // C# 6.0, read only property
    public BindingList<string> Titles { get; } = new BindingList<string>();
    
    // C# 5.0 and older
    private readonly BindingList<string> titles = new BindingList<string>();
    public BindingList<string> Titles { get { return titles; } }
    
    不太理想,因为根据执行顺序,您可能会进入另一个分支,其中
    标题
    可能为空,您必须添加额外的空检查和赋值。此外,当您的父构造函数执行完毕时,它将执行您的子构造函数,并且那里的
    标题
    将已经被赋值。如果再次在此处重新分配,将覆盖在
    OnChanged
    中完成的分配

    最后但并非最不重要的一点是,您可以进行惰性实例化

    private BindingList<string> titles;
    public BindingList<string> Titles { 
        get 
        {
            if(titles == null)
            {
                titles = new BindingList<string>();
            }
    
            return titles;
        }
    }
    
    在构造函数中调用
    Gender=Gender.Female
    ,如果代码如下所示,它可能不会触发
    OnChange
    方法

    public Gender 
    {
        get { return gender; }
        set 
        {
            if(gender!=value) 
            {
                gender = value;
                OnChange("Gender");
            }
        }
    }
    

    我终于意识到,要解决这个问题,我必须解决间接导致这种行为的根本问题。根本问题是虚成员调用构造函数(在本例中是以间接方式)

    因此,我决定根本不使用虚拟的
    OnChanged
    方法,而是自行订阅对象自己的
    PropertyChanged
    事件。自行订阅应始终安全,无需取消订阅。解决方案如下所示

    public class PersonEditorModel : PersonDTO {
    
      public BindingList<string> Titles { get; private set; }
    
      private readonly IRepository _repository;
      public PersonEditorModel(IRepository repository) {
        _repository = repository;
        Titles = new BindingList<string>();
    
        UpdateTitles();
        PropertyChanged += OnPropertyChanged;
      }
    
      private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
        if (e.PropertyName == "Gender") {
           UpdateTitles();
        }
      }
    
      private void UpdateTitles() {
        Titles.Clear();
        if (Gender == Gender.Female) {
          Titles.AddRange(new[] {"Ms", "Mrs"});
        else
          Titles.AddRange(new[] {"Mr", "Sir"});
      }
    }
    
    公共类PersonEditorModel:PersonDTO{
    公共绑定列表标题{get;private set;}
    专用只读IRepository存储库;
    公共PersonEditorModel(IRepository存储库){
    _存储库=存储库;
    Titles=新绑定列表();
    updatetiles();
    PropertyChanged+=OnPropertyChanged;
    }
    私有void OnPropertyChanged(对象发送方,PropertyChangedEventArgs e){
    如果(例如,PropertyName==“性别”){
    updatetiles();
    }
    }
    私有void updatetiles(){
    标题。清除();
    如果(性别==性别。女性){
    Titles.AddRange(新[]{“Ms”、“Mrs”});
    其他的
    Titles.AddRange(新[]{“先生”、“先生”});
    }
    }
    
    提供的解决方案是可行的,但我认为架构甚至视图模型本身都可以改进,如下所示:

    1)数据模型与服务模型/视图模型的分离-已经在评论中提到,但这里用一个示例进行了说明。视图模型(您的“编辑器”类)应该尽可能松散地耦合到数据模型,因此继承是不允许的。此外,DI建议也可以,尽管可能会使用一些

    2)与魔法字符串相比,更喜欢静态类型。例如
    E.PropertyName==“Gender”
    容易受到通常通过重构(自动)完成的属性名称更改的影响,但无法更改这些字符串

    public enum Gender
    {
        Male,
        Female
    };
    
    // this should be a simple class (POCO), persistence agnostic
    public class PersonDTO
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Gender Gender { get; set; }
    }
    
    // repository interface
    public interface IRepository<T>
    {
        IQueryable<T> GetAll();
        T GetById(int id);
    }
    
    // this is responsible for delivering person related information without exposing fetching details
    public class PersonService
    {
        private IRepository<PersonDTO> _Repository;
    
        public PersonService(IRepository<PersonDTO> repository)
        {
            _Repository = repository;
        }
    
        // normally, service should return service models that are view agnostic, but this requires extra mapping
        // so, for convenience service returns the view model
        public PersonEditorModel GetPerson(int id)
        {
            var ret = AutoMapper.Mapper.Map<PersonEditorModel>(_Repository.GetById(id));
            return ret;
        }
    }
    
    // base editor model (or view model)
    public class BaseEditorModel : INotifyPropertyChanged
    {
        /// <summary>
        ///  Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    
        /// <summary>
        /// Raises the PropertyChanged event
        /// </summary>
        /// <param name="propertyName">Name of the property</param>
        protected void OnPropertyChanged(string propertyName)
        {
            var ev = PropertyChanged;
            if (ev != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    // base editor model (or view model) that allow statically-typed property changed notifications
    public abstract class BaseEditorModel<TVm> : BaseEditorModel
                                  where TVm : BaseEditorModel<TVm>
    {
        /// <summary>
        /// Raises the PropertyChanged event
        /// </summary>
        /// <param name="expr">Lambda expression that identifies the updated property</param>
        protected void OnPropertyChanged<TProp>(Expression<Func<TVm, TProp>> expr)
        {
            var prop = (MemberExpression)expr.Body;
            OnPropertyChanged(prop.Member.Name);
        }
    }
    
    // the actual editor
    // notice that property changed is done directly on setters and without magic strings
    public class PersonEditorModel : BaseEditorModel<PersonEditorModel>
    {
        public BindingList<string> Titles { get; private set; }
    
        public PersonEditorModel()
        {
            Titles = new BindingList<string>();
    
            UpdateTitles();
        }
    
        private Gender _Gender;
        public Gender Gender
        {
            get { return _Gender; }
            set
            {
                _Gender = value;
                UpdateTitles();
                OnPropertyChanged(m => m.Gender);
            }
        }
    
        private void UpdateTitles()
        {
            Titles = Gender == Gender.Female ?
                new BindingList<string>(new[] { "Ms", "Mrs" }) :
                new BindingList<string>(new[] { "Mr", "Sir" });
            OnPropertyChanged(m => m.Titles);
        }
    }
    
    // just an example
    class Program
    {
        static void Main(string[] args)
        {
            // this should be performed one per application run
            // obsolete in a newer version
            AutoMapper.Mapper.CreateMap<PersonDTO, PersonEditorModel>();
    
            var service = new PersonService(null);     // get service using DI
    
            // work on a dummy/mock person
            var somePerson = service.GetPerson(30);
    
            // bind and do stuff with person view model
        }
    }
    
    公共枚举性别
    {
    男,,
    女性
    };
    //这应该是一个简单类(POCO),持久性不可知
    公共类个人
    {
    公共字符串名称{get;set;}
    公共整数{get;set;}
    公共性别{get;set;}
    }
    //存储库接口
    公共接口假定
    {
    IQueryable GetAll();
    T GetById(int-id);
    }
    //它负责传递与人员相关的信息,而不公开获取细节
    公共类人员服务
    {
    私人IRepository_存储库;
    公共人员服务(IRepository存储库)
    {
    _存储库=存储库;
    }
    //通常,服务应该返回视图不可知的服务模型,但这需要额外的映射
    //所以
    
    public class PersonEditorModel : PersonDTO {
    
      public BindingList<string> Titles { get; private set; }
    
      private readonly IRepository _repository;
      public PersonEditorModel(IRepository repository) {
        _repository = repository;
        Titles = new BindingList<string>();
    
        UpdateTitles();
        PropertyChanged += OnPropertyChanged;
      }
    
      private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) {
        if (e.PropertyName == "Gender") {
           UpdateTitles();
        }
      }
    
      private void UpdateTitles() {
        Titles.Clear();
        if (Gender == Gender.Female) {
          Titles.AddRange(new[] {"Ms", "Mrs"});
        else
          Titles.AddRange(new[] {"Mr", "Sir"});
      }
    }
    
    public enum Gender
    {
        Male,
        Female
    };
    
    // this should be a simple class (POCO), persistence agnostic
    public class PersonDTO
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public Gender Gender { get; set; }
    }
    
    // repository interface
    public interface IRepository<T>
    {
        IQueryable<T> GetAll();
        T GetById(int id);
    }
    
    // this is responsible for delivering person related information without exposing fetching details
    public class PersonService
    {
        private IRepository<PersonDTO> _Repository;
    
        public PersonService(IRepository<PersonDTO> repository)
        {
            _Repository = repository;
        }
    
        // normally, service should return service models that are view agnostic, but this requires extra mapping
        // so, for convenience service returns the view model
        public PersonEditorModel GetPerson(int id)
        {
            var ret = AutoMapper.Mapper.Map<PersonEditorModel>(_Repository.GetById(id));
            return ret;
        }
    }
    
    // base editor model (or view model)
    public class BaseEditorModel : INotifyPropertyChanged
    {
        /// <summary>
        ///  Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;
    
        /// <summary>
        /// Raises the PropertyChanged event
        /// </summary>
        /// <param name="propertyName">Name of the property</param>
        protected void OnPropertyChanged(string propertyName)
        {
            var ev = PropertyChanged;
            if (ev != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    
    // base editor model (or view model) that allow statically-typed property changed notifications
    public abstract class BaseEditorModel<TVm> : BaseEditorModel
                                  where TVm : BaseEditorModel<TVm>
    {
        /// <summary>
        /// Raises the PropertyChanged event
        /// </summary>
        /// <param name="expr">Lambda expression that identifies the updated property</param>
        protected void OnPropertyChanged<TProp>(Expression<Func<TVm, TProp>> expr)
        {
            var prop = (MemberExpression)expr.Body;
            OnPropertyChanged(prop.Member.Name);
        }
    }
    
    // the actual editor
    // notice that property changed is done directly on setters and without magic strings
    public class PersonEditorModel : BaseEditorModel<PersonEditorModel>
    {
        public BindingList<string> Titles { get; private set; }
    
        public PersonEditorModel()
        {
            Titles = new BindingList<string>();
    
            UpdateTitles();
        }
    
        private Gender _Gender;
        public Gender Gender
        {
            get { return _Gender; }
            set
            {
                _Gender = value;
                UpdateTitles();
                OnPropertyChanged(m => m.Gender);
            }
        }
    
        private void UpdateTitles()
        {
            Titles = Gender == Gender.Female ?
                new BindingList<string>(new[] { "Ms", "Mrs" }) :
                new BindingList<string>(new[] { "Mr", "Sir" });
            OnPropertyChanged(m => m.Titles);
        }
    }
    
    // just an example
    class Program
    {
        static void Main(string[] args)
        {
            // this should be performed one per application run
            // obsolete in a newer version
            AutoMapper.Mapper.CreateMap<PersonDTO, PersonEditorModel>();
    
            var service = new PersonService(null);     // get service using DI
    
            // work on a dummy/mock person
            var somePerson = service.GetPerson(30);
    
            // bind and do stuff with person view model
        }
    }