Open sandboxFocusImprove this doc

Handling Corner Cases of the NotifyPropertyChanged Aspect

PostSharp includes a number of attributes for customizing the default behavior and for handling special dependencies.

Ignoring changes to properties

Use the IgnoreAutoChangeNotificationAttribute class attribute to prevent an OnPropertyChanged event from being invoked when setting a property.

Example

In this example, the CustomerModel class contains a Country property amongst others. To prevent a property notification from being invoked when the value of this property is set, simply place the IgnoreAutoChangeNotificationAttribute attribute above the property.

[NotifyPropertyChanged]
public class CustomerModel 
{ 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    public string Phone { get; set; } 
    public string Mobile { get; set; } 
    public string Email { get; set; } 

    [IgnoreAutoChangeNotification]
    public string Country { get; set;}
}

Handling virtual calls, delegates, external methods, or complex data flows

If a property getter calls a virtual method from its class or a delegate, or references a property of another object (without using canonical form this.field.Property), PostSharp will generate an error because it cannot resolve such a dependency at build time. The same limitations apply when your property getter contains complex data flows, such as loops, or calls to methods (except property getters) of other classes.

When this happens, you can either refactor your code so that it can be automatically analyzed by PostSharp, or you can take over the responsibility for analyzing the code.

Taking responsibility for dependency analysis

To suppress the error that PostSharp emits when it is unable to fully analyze your code, add the SafeForDependencyAnalysisAttribute custom attribute to the property accessor (or in any method used by the property accessor).

Note

By using SafeForDependencyAnalysisAttribute, you are taking the responsibility that your code only has dependencies that are given either in the canonical form of this.field.Property either explicitly using the On construct (see below). If you are using this custom attribute but have non-canonical dependencies, some property changes may not be detected in which case no notification will be generated.

Adding dependencies manually

Even when a method has the SafeForDependencyAnalysisAttribute attribute, PostSharp still discovers the dependencies that are in canonical form this.field.Property. However, PostSharp does not discover dependencies hidden under delegate or virtual method calls.

To explicitly add a dependency to a property, you can use the On method. The expression passed to the On must be in canonical form this.field.Property, i.e. Depends.On( this.field.Property ).

Example

In the following example, the CustomerModel class contains a virtual method called ValidateCountry which is used by the get accessor of its Country property. The presence of the call to the virtual method prevents PostSharp from fully understanding the dependencies of the Country property. PostSharp discovers the dependency to the _country field but cannot analyze the implementations of the ValidateCountry method, and therefore emits an error. By adding the SafeForDependencyAnalysisAttribute attribute, to the Country property, you remove the error.

Even if you remove the error, PostSharp still analyzes the Country property getter and finds the dependency on the _country field. However, it does not follow the call to the ValidateCountry method and does not find the dependency to the Continent property. That is why we have to add this dependency manually by calling the Depends.On method.

[NotifyPropertyChanged]
public class Address 
{ 
  string _country;
  public string Continent { get; set; }
 
  public virtual bool ValidateCountry(string country)
  {
     return GeoService.ContinentContains( this.Continent, country );
  }

  [SafeForDependencyAnalysisAttribute]
  public string Country 
  { 
    get
    {
      Depends.On( this.Continent );
    
      if(this.ValidateCountry(_country))
        return _country;
      else
        return "Lilliput";
    }
    
    set;
  }
}

Handling dependencies on pure methods

Often times a property will depend on a method which is solely dependent on its input parameters to produce a return value. These methods are called pure and do not need to be analyzed. To mark a method as pure, use the PureAttribute custom attribute.

Example

Consider the following variation to CustomerModel where the ValidPhoneNumber property logic has been moved into a static method called GetValidPhoneNumber() which exists in a separate helper class called ContactHelper:

Since GetValidPhoneNumber() is a standalone method of another class, it is not analyzed. Therefore the PureAttribute attribute needs to be applied to this method to acknowledge this dependency.

public class ContactHelper
{
  [Pure]
  public static string GetValidPhoneNumber(string firstPhoneNumber, string secondPhoneNumber)
  {
    return firstPhoneNumber ?? secondPhoneNumber;
  }
}

[NotifyPropertyChanged]
public class CustomerModel 
{ 
  public Contact PrimaryContact {get; set;}
  public Contact SecondaryContact {get; set;}

  public string ValidPhoneNumber
  { 
    get {
      return ContactHelper.GetValidPhoneNumber(this.PrimaryContact.Phone, this.SecondaryContact.Phone);
    }
  }
}

Handling dependencies on collections

When working with view models, properties often contain collections and other properties that depend on the content of these collections. The default behavior of the aspect is to react only on the collection itself being replaced. However, any change of the collection content should result in change of a dependent property.

To facilititate this, the property containing the collection should be marked with AggregateAllChangesAttribute. This causes any change of the collection (adding an item, replacing an item, removing an item, etc.). to result in notification of the change to any property that depends on it.

Example

In following Recipe class, the LongestStep property gets a duration of the longest of steps in the recipe's Steps collection.

Since the collection property is marked AggregateAllChangesAttribute, each change to this collection will result in a change of LongestStep property even though the property still contains same collection object.

public class RecipeStep
{  
  public TimeSpan Duration { get; }
  
  public RecipeStep(TimeSpan duration)
  {
    Duration = duration;
  }
}

[NotifyPropertyChanged]
public class Recipe 
{ 
  [AggregateAllChanges]
  public ObservableCollection<RecipeStep> Steps { get; }
  
  public Recipe()
  {
    Steps = new ObservableCollection<RecipeStep>();
  }

  [SafeForDependencyAnalysis]
  public int LongestStep
  { 
    get 
    {      
      return this.ShoppingSteps.Max(x => x.Duration);
    }
  }
}

See Also

Reference

PureAttribute
IgnoreAutoChangeNotificationAttribute
SafeForDependencyAnalysisAttribute
On
Depends