-2

I have a base class which implements property changed notifications. I've omitted the implementation technical details for clarity.

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
  }
}

I have another class which inherits from PersonDTO and adds some properties.

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)
  }
}

The problem with this model is that in the base constructor, setting a property invokes the change notification, and the OnChanged method in the descendant class executes when the descendant is not yet constructed (Titles list is null).

Several ways I've been thinking of.

  1. Use the backing fields in the base constructor. This would fix the exception, but the object state was not refreshed accordingly. I need a consistent state all the time, which means that the Gender and the Titles should be in sync.

  2. Include a flag which means that the object is constructed, and check that flag in the OnChanged. This would be valid for simple cases like this, but what if I have a 3 level hierarchy. I would need to ensure that the flag is set when the bottom most constructor had finished running, which is not trivial.

  3. Use a factory method pattern in the base class where after construction I would call something like StartChangeTracking(). This is problematic because the descendant classes can have different constructor arguments, like in this case an IRepository service. Also, the factory method pattern would make for example Json serialization/deserialization fairly harder (I mean for those classes without constructor arguments of course).

Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93
  • 1
    Where do you initialize Titles? Can't you simply initialize it in contructor or like that: public BindingList Titles { get; private set; } = new BindingList(); – 3615 Feb 20 '16 at 14:48
  • I do initialize it in constructor, but the problem is that the parent constructor runs earlier. About that latter syntax, I think it's valid only in C# 6 which I cannot use currently, I have to stick with .NET 4.0 for now. Also, I don't know to what it gets compiled in C# 6, probably it will still be initialized in the constructor OR the initializer will go to the backing field in which case it would be a solution indeed. – Zoltán Tamási Feb 20 '16 at 14:55
  • 1
    @ZoltánTamási: C# 6.0 is not limited to .NET 4.5. You can use C# 6.0 even for .NET 2.0 or older. C# 6.0 is a complier thing. You can still use Visual Studio 2015 and target older frameworks and still use C# 6.0 features as long as they don't require .NET 4.5 features, see http://stackoverflow.com/a/28921749/455493 – Tseng Feb 20 '16 at 16:45
  • But for your actual question, you seem to have some misconceptions about DTOs, Models and ViewModels. You don't want INPC on your DTOs and you don't want to inherit your ViewModels from DTO or Models. And you don't want to directly bind your models to the View. DTOs are supposed to be a simple structure without any logic (or notifications like iNPC), just for transporting data across the boundaries (i.e. from rest/webservice to your application via json, or from the persistence to the domain).You should use automappers to convert from dao to dto and from model to viewmodel, if it makes sense – Tseng Feb 20 '16 at 16:52
  • @Tseng thank you. I've actually checked the possibility to target older frameworks with C# 6 and indeed it's possible, will check it soon. About DTOs, in my experience there are some really liquid topics in software design and one of them is "how much responsibility should I give to XY layer". In the past 4 years I've successfully used this model in WinForms, WPF and even on Web platform too. Only now I found this issue in a case. I think INPC (ie. data binding support), state maintenance and validation can be the responsibility of a "DTOish" object. Maybe I shouldn't call it DTO, but "Model". – Zoltán Tamási Feb 20 '16 at 22:10
  • @ZoltánTamási: Yea, maybe. DTO = Data Transfer Object as the name says, just to transport data across a boundary (send it over the wire, or between two domain bounded contexts). http://martinfowler.com/eaaCatalog/dataTransferObject.html – Tseng Feb 20 '16 at 22:45
  • I would use the backing fields and then call OnChanged manually for each property in the constructor once you're sure everything's been initialized. The only situation where this would break is if derived classes override OnChanged, get change notification for base properties and assume at that stage that their own have initialized, but that's an architectural issue that you really shouldn't be doing in the first place. – Mark Feldman Feb 20 '16 at 23:24

3 Answers3

1

You got couple of choices here. First, you could directly assign the Titles, like below, assuming you won't ever reassign the BindingList<T> (normally in MVVM we use ObservableCollection<T> though) I've made it readonly. This assures that Titles will never be null and you won't have to do null-checks

// 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; } }

Other (less optimal) option of course includes to do the null-check inside the OnChange Method.

        protected override void OnChanged(string propertyName)
        {
            if (propertyName == "Gender")
            {
                if(Titles==null) 
                {
                    Titles = new BindingList<string>();
                }
                Titles.Clear();
                if (Gender == Gender.Female)
                {
                    //Titles.AddRange(new[] { "Ms", "Mrs" });
                    Titles.Add("Ms");
                    Titles.Add("Mrs");
                }
                else
                {
                    Titles.Add("Mr");
                    Titles.Add("Sir");
                }
            }

            base.OnChanged(propertyName);
        }

Less optimal, because depending on the execution order you may end up in another branch where Titles could be null and you have to add additional null-checks and assignments. Further more, when your parent constructor is done executing it will execute your child's constructor and there Titles will be already assigned. If you reassign it here again, you override the assignments done inside OnChanged.

Last but not least, you can do lazy instantiation.

private BindingList<string> titles;
public BindingList<string> Titles { 
    get 
    {
        if(titles == null)
        {
            titles = new BindingList<string>();
        }

        return titles;
    }
}

This way, Title will always return an instance if you access the property and you don't have to instantiate it in the constructor.

You should use the first option I posted above, as it's the best one that follows best practices and for lists and other objects it works well. If you need a runtime parameter to instantiate it you only have constructor or lazy instantiation as option.

You should avoid Initialize() type of methods in your public api (interfaces, classes, etc.) as this is considered a code smell, as it's not obvious that calling this method is required for the correct functionality of the class.

P.S. Depending on the implementation of your OnChange call, you may or may not receive the "Gender" Event inside your overriden OnChange class. I assume Gender is a enum and if it's defined as following

public enum Gender 
{
    Female,
    Male
}

and you call Gender = Gender.Female inside your constructor, it may not trigger OnChange method, if your code looks like

public Gender 
{
    get { return gender; }
    set 
    {
        if(gender!=value) 
        {
            gender = value;
            OnChange("Gender");
        }
    }
}
Tseng
  • 61,549
  • 15
  • 193
  • 205
1

Provided solutions work, but I think the architecture and even view models themselves can be improved as shown below:

1) data models vs. service models/view models separations - already mentioned in a comment, but here illustrated with an example. View models (your "editor" class) should be as loosely coupled as possible to the data models, so inheritance is a no-no. Also, DI advice is to prefer composition over inheritance, although some tricks might be employed.

2) prefer statically-typed over magic strings. E.g. e.PropertyName == "Gender" is vulnerable to property names changes that are usually done via refactoring (automatically), but fails to change these strings.

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
    }
}
Community
  • 1
  • 1
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
  • Thank you for your answer, you definitely have good points. However I've chosen the inheritance-based structure long ago when the project had a WinForms frontend. It was very simple to pass the editor model to the service layer which accepted the corresponding data-model. In the web world things are quite different, and in a new project I think I would go with composition. However to tell the truth, this inheritance-based technique is working nicely without any ugly workarounds even in web application, and at the end of the day this is more important than following the current trends. – Zoltán Tamási Mar 19 '16 at 18:22
  • As for the magic-strings, this was just for the example. In my real codes I'm using expression based syntax with some helpful extension methods to address the refactoring issue. – Zoltán Tamási Mar 19 '16 at 18:23
  • @ZoltánTamási - yes, it depends on the architecture, but in bigger projects things like Unit testing or requirement to port to other framework (e.g. from Windows Forms connecting directly to a database to a WPF application in a 3-tier architecture) happen. In this case, the overhead pays off. – Alexei - check Codidact Mar 19 '16 at 18:46
  • Yes you're right, but I'm starting to feel like maybe we have a huge misunderstanding. Actually I'm not inheriting from my *entity data models* but from my *service data models*. In my structure there is a `Person` entity model, then in the service layer a `PersonEditorModel` (misleadingly named as `PersonDTO` in my original post) and then there is a `PersonEditorViewModel` inherited from `PersonEditorModel`. Still one can say it's not an ideal design but it's been working for me for years now in WinForms and ASP.NET MVC too, and even survived a desktop-to-web migration.. – Zoltán Tamási Mar 20 '16 at 15:59
  • Oh, ok. Then it makes sense. Anyway, names are important, especially since convention over configuration is fashionable :). – Alexei - check Codidact Mar 20 '16 at 17:29
0

I've finally realized that to solve this issue I have to solve the root issues which indirectly caused this behavior. The root issue is virtual member call in constructor (in this case in an indirect way).

So I decided to not use the virtual OnChanged method at all, and instead, self-subscribe to the object's own PropertyChanged event. The self-subscription should be always safe, without the need of unsubscription. The solution looks like this.

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"});
  }
}
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93