Post­Sharp Documentation / Developing Custom Aspects / Developing Simple Aspects / Intercepting Properties and Fields

Intercepting Properties and Fields

In .NET, both fields and properties are "things" that can be set and get. You can intercept get and set operations using the LocationInterceptionAspect. It makes it possible to develop useful aspects, such as validation, filtering, change tracking, change notification, or property virtualization (where the property is backed by a registry value, for instance).

This topic contains the following sections:

Intercepting Get operations

In this example, we will see how to create an aspect that filters the value read from a field or property.

To create an aspect that filters the value read from a field or property

  1. Add a reference to the PostSharp package to your project.

  2. Create an aspect that inherits from LocationInterceptionAspect and add the custom attribute [PSerializableAttribute].

  3. Override the OnGetValue(LocationInterceptionArgs) method.

    C#
    [PSerializable] 
    public class StringCheckerAttribute : LocationInterceptionAspect 
    { 
        public override void OnGetValue(LocationInterceptionArgs args) 
        { 
            base.OnGetValue(args); 
        } 
    
    }
  4. Calling base.OnGetValue actually retrieves the value from the underlying field or property, and populates the Value property. Add some code to check if the property currently is set to null If the current value is null, we want to return a predefined value. To do this we can set the Value property. Any time this property is requested, and it is set to null, the value "foo" will be returned.

    C#
    public override void OnGetValue(LocationInterceptionArgs args) 
    { 
        base.OnGetValue(args); 
    
        if (args.Value == null) 
        { 
            args.Value = "foo"; 
        } 
    }
  5. Now that you have a complete getter interception aspect written you can attach it to the target code. Simply add an attribute to either properties or fields to have the interception attached.

    C#
    public class Customer 
    { 
        [StringChecker] 
        private readonly string _address; 
    
        public Customer(string address) 
        { 
            _address = address; 
        } 
        [StringChecker] 
        public string Name { get; set; } 
        public string Address { get { return _address; } } 
    }
    Note Note

    Adding aspects to target code one property or field at a time can be a tedious process. There are a number of techniques in the article Adding Aspects to Multiple Declarations that explain how to add aspects en mass.

  6. Now when you create an instance of a customer and immediately try to access the Name and Address values the get request will be intercepted and null values will be returned as "foo".

    C#
    class Program 
    { 
        static void Main(string[] args) 
        { 
            var customer = new Customer("123 Main Street"); 
            Console.WriteLine("Address: {0}", customer.Address); 
            Console.WriteLine("Name: {0}", customer.Name); 
            Console.ReadKey(); 
        } 
    }
    Intercepting Properties 1

Property and field interception is a simple and seamless task. Once you have intercepted your target you can act on the target or you can allow the original code to execute.

Intercepting Set operations

The previous section showed how to intercept a get accessor. Intercepting a set accessor is accomplished in a similar manner by implementing OnSetValue(LocationInterceptionArgs) in the LocationInterceptionAspect.

The following snippet shows the addition of OnSetValue(LocationInterceptionArgs) to the StringCheckerAttribute example:

C#
[PSerializable] 
public class StringCheckerAttribute : LocationInterceptionAspect 
{ 
public override void OnGetValue(LocationInterceptionArgs args) 
{ 
base.OnGetValue(args); 
} 

public override void OnSetValue(LocationInterceptionArgs args) 
{ 
base.OnSetValue(args); 
} 
}

When applied to a property with a set operator, OnSetValue(LocationInterceptionArgs) will intercept the set operation. In the Customer example shown below, OnSetValue(LocationInterceptionArgs) will be called whenever the Name property is set:

C#
public class Customer 
{ 
    .
    .
    .
[StringChecker]
public string Name { get; set; } 
}

The SetNewValue(Object) method of LocationInterceptionArgs can be used instead of base.OnSetValue() to pass a different value in for the property. For example, OnSetValue(LocationInterceptionArgs) could be used to check for a null string, and then change the string to a non-null value:

C#
[PSerializable] 
public class StringCheckerAttribute : LocationInterceptionAspect 
{ 
    .
    .
    .
public override void OnSetValue(LocationInterceptionArgs args) 
{ 
if (args.Value == null)
{
args.Value = “Empty String";
} 


args.ProceedSetValue();

} 
}
Getting and setting the underlying property

PostSharp provides a mechanism to check a property’s underlying value via the LocationInterceptionArgs.GetCurrentValue() method. This can be useful to check the current property value when a setter is called and then take some appropriate action.

For example, the following snippet shows a modified OnSetValue(LocationInterceptionArgs) method which gets the current underlying property value and compares the (new) value passed into the setter against the current value. If current and new value don’t match then some message is written:

C#
public override void OnSetValue(LocationInterceptionArgs args)
{
  //get the current underlying value
  string existingValue = (string)args.GetCurrentValue();

  if (((existingValue==null) && (args.Value != null)) || (!existingValue.Equals(args.Value)))
  {
    Console.WriteLine(“Value changed.”);
    args.ProceedSetValue();
  }
}
Note Note

GetCurrentValue() will call the underlying property getter without going through OnGetValue(LocationInterceptionArgs). If several aspects are applied to the property (and/or to the property setter), GetCurrentValue() will go through the next aspect in the chain of invocation.

PostSharp also provides a mechanism to set the underlying property in a getter via the SetNewValue(Object) method of LocationInterceptionArgs. This could be used for example, to ensure that a default value is assigned to the underlying property if there is currently no value. The following snippet shows a modified OnGetValue(LocationInterceptionArgs) method which gets the current underlying value, and sets a default value if the current value is null:

C#
public override void OnGetValue(LocationInterceptionArgs args)
{
    object o = args.GetCurrentValue();
    if (o == null)
    {
        args.SetNewValue("value not set");
    }

    base.OnGetValue(args);
}
Intercepting fields

One benefit to implementing a LocationInterceptionAspect is that it can be applied directly to fields, allowing for reads and writes to those fields to be intercepted, just like with properties.

Applying a LocationInterceptionAspect implementation to a field is simply a matter of setting it as an attribute on a field, just as it was done with a property:

C#
public class Customer 
{ 
    .
    .
    .
[StringChecker]
public string name; 
}

With the attribute applied to the name field, all attempts to get and set that field will be intercepted by StringChecker in its OnGetValue(LocationInterceptionArgs) and OnSetValue(LocationInterceptionArgs) methods.

Note that when a LocationInterceptionAspect is added to a field, the field is replaced by a property of the same field and visibility. The field itself is renamed and made private.

Getting the property or property being accessed

Information about the property or field being intercepted can be obtained through the LocationInterceptionArgs via its Location property. The type of this property, LocationInfo, can represent a FieldInfo, a PropertyInfo, or a ParameterInfo (although LocationInterceptionAspect cannot be added to parameters).

One use for this is to reflect the property name whenever a property is changed. In the following example, we have an Entity class that implements INotifyPropertyChanged and a public OnPropertyChanged method which allows notifications to be made whenever a property is changed. The Customer class has been modified to derive from Entity.

C#
class Entity : INotifyPropertyChanged
{

  public event PropertyChangedEventHandler PropertyChanged;

  public void OnPropertyChanged(string propertyName)
  {
    if (PropertyChanged != null)
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  }
}

 class Customer : Entity
{
  public string Name { get; set; }
}

With the ability to invoke an OnPropertyChanged event, we can create a LocationInterceptionAspect which invokes this event when setting a value and pass in the property name from the underlying PropertyInfo object:

C#
[PSerializable] 
public class NotifyPropertyChangedAttribute : LocationInterceptionAspect 
{ 

public override void OnSetValue(LocationInterceptionArgs args) 
{ 
If ( args.Value != args.GetCurrentValue() )
{
args.Value = args.Value;
args.ProceedSetValue();
((Entity)args.Instance).OnPropertyChanged(args.Location.Name);
} 
} 
}
Note Note

This example is a simplistic implementation of the NotifyPropertyChangedAttribute aspect. For a production-ready implementation, see the section INotifyPropertyChanged.

This aspect can then be applied to the Customer class:

[NotifyPropertyChangedAttribute]
class Customer : INotifyPropertyChanged
{
    public string Name { get; set; }
}

Now when the Name property is changed, NotifyPropertyChangedAttribute will invoke the Entity.OnPropertyChanged method passing in the property name retrieved from its underlying property.

See Also