Archive

Yesterday we introduced members into our target class. Today we’re going to go the other way and introduce members from our target class into our aspect for consumption by the process of importing.

Importing Members

There are cases when you will need to bring in a field or method from a target class so that you have access to it from within the aspect. What we’re doing is making assumptions about the target. It’s important to document what the aspect is assuming about the target code. Another approach instead of importing, is to cast the instance to a interface (or type) known to be implemented on the target.

To import, we use the ImportMember attribute. ImportMember allows us to import fields, properties, methods and events. As an example, let’s look at some sample code.

public interface IIdentifiable
{
    Guid ID { get; }
    void PrintID();

}

public class ExampleClass : IIdentifiable
{
    public Guid ID { get; private set; } //Satisfy the interface

    public ExampleClass()
    {
        this.ID = Guid.NewGuid();
    }

    [ImportExampleAspect]
    public void DoWork()
    {
        Console.WriteLine("Doing work");
    }

    public void PrintID()
    {
        Console.WriteLine(this.ID.ToString());
    }

}

[Serializable]
public class ImportExampleAspect : OnMethodBoundaryAspect, IInstanceScopedAspect
{
    [ImportMember("PrintID", IsRequired = true)]
    public Action PrintID;

    [ImportMember("ID")]
    public Property IDFromClass;

    public override void OnEntry(MethodExecutionArgs args)
    {
        Guid value = IDFromClass.Get();
        PrintID();
        Console.WriteLine("Value from IIdentifiable.ID is {0}", value.ToString());
    }

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return this.MemberwiseClone();
    }

    public void RuntimeInitializeInstance() {}

}

We have an example class that implements a simple interface and an aspect based on OnMethodBoundaryAspect. When you run this example code, the value of the ‘ID’ property will be written to the console.

178411e1-c79b-43fc-9aa0-ff309425abaa
Value from IIdentifiable.ID is 178411e1-c79b-43fc-9aa0-ff309425abaa
Doing work

Notice that our aspect implements the IInstanceScopedAspect interface. ImportMember cannot import static fields so we have to define our aspect as instance scoped. For more information on the life time and scope of aspects, refer to Day 9 and Day 10 of the PostSharp Principals series.

We expect the target to implement the interface which has a PrintID method defined. So we setup a public Action and we call it PrintID. We decorate our Action with the ImportMember attribute passing in the target’s member name which is ‘PrintID’.

Next we defined the IDFromClass public field of type Property<Guid>. This class has two properties: Get and Set. At runtime, the Get and Set properties will contain a delegate to the getter and setter of this property. In order to import an event, we need a public field of type Event<EventArgs>, which has two delegate properties: Add and Remove.

We do not have to use the same name as the target’s member name, so instead of ‘ID’ we are using ‘IDFromClass’.Inside of the OnEntry method we get the value of ID from our target class using the Get delegate provided by IDFromClass, make a call to the PrintID method and then write out the value to the console.

Note: ImportMember will work on members of any visibility, even private.

ImportMemberAttribute

There should be no surprise when I say that we can control the behavior of the import using the attribute’s parameters.

IsRequired

Specifies whether the member being imported is required or not. If true, a compiler error will occur if the member is not present in the target class. If false, the reference to the member will be null if it does not exist in the target.

Order

Order takes an enumeration from PostSharp.Aspects.Advices.ImportMemberOrder to determine when the importing will occur.

· AfterIntroductions (default) – Importing will occur only after the aspect has introduced its own members. This is useful if the imported member is virtual and we need to import the last override.BeforeIntroductions – Importing will occur before any members are introduced by the aspect. This gives you a chance to get a reference to the original member before any overriding occurs. This is similar to calling an overridden method by the “base” keyword.

· Default – The default is AfterIntroductions.

There can be any number of aspects on the same class that override the same method. Importing before the introductions allows you to call the next node in the chain of overwriting.

Better Example

Let’s have a look at a more in-depth example using both importing and introduction, the MakeDirtyOnChange aspect. When working with workspaces or MDI (multi-document interface) applications, many “documents” can be open at one time. There is a need to know when one or more of the “documents” have been modified so that you can inform the user visually and also to know which items need to be updated in the data store. Think about working with five C# files in Visual Studio. When a change is made to a file, you will see an asterisk next to the filename on the file’s tab. This is the indication that a change has been made and not yet saved.

Implementing the IsDirty pattern is similar to the INotifyPropertyChange, the code to wire up the change notification is redundant and tedious. Let’s check out our interface and test class before diving into our aspect

public interface IsDirty
{
    bool IsDirty { get; }
    event EventHandler WasMadeDirty;
    void ResetDirtyState();
    ReadOnlyCollection DirtyProperties { get; }
}

public class DirtyEventArgs : EventArgs
{
    public string DirtyProperty { get; private set; }

    public DirtyEventArgs(string dirtyProperty)
    {
        this.DirtyProperty = dirtyProperty;
    }
}

[MakeDirtyOnChange]
public class Document
{
    private Guid _docId = Guid.NewGuid();
    public Guid DocId { get { return _docId; } }

    public string Title { get; set; }
    public string Author { get; set; }
    public string Content { get; set; }
}

Our IDirty interface has a few requirements that we can and will use to determine if an item has changes. We specify a custom EventArgs class that allows us to provide details about the changes made to the item when invoking the WasMadeDirty event. Our Document class is a clean model that knows nothing about the IsDirty interface.

[Serializable]
[IntroduceInterface(typeof(IsDirty), OverrideAction = InterfaceOverrideAction.Ignore)]
public class MakeDirtyOnChange : InstanceLevelAspect, IsDirty
{
    [OnLocationSetValueAdvice, MulticastPointcut(Targets=MulticastTargets.Property)]
    public void OnValueChanged(LocationInterceptionArgs args)
    {
        MakeDirty(args.LocationName);
    }

    private bool _isDirty;
    private List _dirtyProperties;

    [ImportMember("SetDirty")]
    public Action MakeDirty;

    [IntroduceMember(IsVirtual=true, OverrideAction=MemberOverrideAction.Ignore)]
    public void SetDirty(string property)
    {
        _isDirty = true;
        if (WasMadeDirty != null)
        {
            WasMadeDirty.Invoke(this.Instance, new DirtyEventArgs(property));
        }
    }

    #region IsDirty Members

    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
    public bool IsDirty { get { return _isDirty; } }

    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
    public ReadOnlyCollection DirtyProperties { get { return _dirtyProperties.AsReadOnly(); } }

    [IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
    public event EventHandler WasMadeDirty;

    [IntroduceMember(IsVirtual=true, OverrideAction=MemberOverrideAction.Ignore)]
    public void ResetDirtyState()
    {
        _isDirty = false;
        _dirtyProperties.Clear();
    }
 
    #endregion

    public override void RuntimeInitializeInstance()
    {
        _isDirty = false;
        _dirtyProperties = new List();
    }
}

We decorate our aspect with the IntroduceInterface attribute, specifying the IsDirty interface. Our aspect implements the IsDirty interface to satisfy the requirements and then we introduce those members to the target.

We setup a location interception using OnLocationSetValueAdvice attribute and specify the target is MulticastPointcut.Property ([MARKER, Advice link]). When a property is changed, we’re going to invoke the MakeDirty method which we tell PostSharp to import from the target’s “SetDirty” method, if it has one. We’re using the defaults for ImportMember which means the import will happen after our members are introduced. Since we’re introducing our own SetDirty method, MakeDirty will contain our SetDirty implementation if the target class did not already have its own implementation.

Since our aspect derives from InstanceLevelAspect we override the RuntimeInitializeInstance method and use it to initialize our private members to their default states.

We can use the following code to try out the aspect

class Program
{
    private static List _changedDocuments = new List();
    private static List _openDocuments = new List();

    static void Main(string[] args)
    {
        for (int i = 0; i < 5; i++)
        {
            Document doc = new Document();
            Post.Cast(doc).WasMadeDirty 
                += new EventHandler(doc_WasMadeDirty);

            _openDocuments.Add(doc);
        }

        _openDocuments[0].Author = "Dustin Davis";
        _openDocuments[0].Title = "PostSharp Principals - Day 1";

        _openDocuments[2].Author = "Dustin Davis";
        _openDocuments[2].Title = "PostSharp Principals - Day 3";

        Console.ReadKey();
    }

    static void doc_WasMadeDirty(object sender, DirtyEventArgs e)
    {
        Document doc = (Document)sender;

        if (_changedDocuments.Any(c => c.Equals(doc.DocId)))
        {
            return;
        }
            
        _changedDocuments.Add(doc.DocId);
        Console.WriteLine("Document {0} was modified.", doc.DocId);
    }
}

The code is pretty straight forward. We create five documents and then add them to our open documents collection. Finally we make changes to two documents. When we run the code, we see the following results

Document acbd247a-e742-499e-b27c-ee028e8e6789 was modified.
Document 9ded92cc-008f-48bc-b1b5-e1b0b967e42d was modified.

But wait, how are we handling the WasMadeDirty event? I’m glad you asked.

Post.Cast<>()

Post.Cast<>() allows us to cast an instance of a type to another type at design time. For example, our Document class doesn’t implement the IsDirty interface so we can’t access the IsDirty specific members unless we casting. We use the generic Cast<SourceType, TargetType>(SourceType Instance) method to give us back an instance of TargetType.

It’s basically nothing more than regular casting, but the difference is when you use Post.Cast<>() you receive compile-time errors if the cast cannot take place. The obvious benefit is that you know right away that the cast fails instead of at run time, potentially introducing bugs.

In the final result, the call to Post.Cast<>() is replaced with an actual cast.

Conclusion

Previous aspects we looked at have been pretty disconnected from the targets. Being able to introduce and import members gives us a connection and increased flexibility. Being able to automatically introduce interfaces and boiler plate code that is sometimes only consumed at run time frees us and keeps our code clean.

self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com

Today we continue our descent into the depths of the PostSharp framework to expose even greater powers to utilize in our projects.

Introduction

No, not the “Hello, my name is Dustin!” kind of introduction, but the “injection” type. What does that mean? PostSharp gives us the power to implement an interface on a class…at build time. We can also add (introduce) members to that class such as fields/properties, events and methods too. These members are injected at build time and are available at run time.

Why would you want to do this? As in most cases when applying aspect-oriented programming, you would use this to implement required interfaces that are little more than boilerplate code. One of the most popular examples of interface introduction is the NotifyPropertyChanged aspect which automatically introduces the INotifyPropertyChanged interface and required members. Anyone who has worked with WPF and the MVVM pattern would love to not have to write all of that scaffolding code just to get change notification. Since that aspect uses features we have not yet covered, we will not cover it today. If you’re feeling adventurous, you can check it out here.

Member Introduction

Member introduction allows us to add properties, events and methods to a class. Let’s start off by creating an aspect to introduce a property and a method.

[Serializable]
public class IntroduceAspect : InstanceLevelAspect
{
    [IntroduceMember]
    public int ID { get; set; }

    [IntroduceMember]
    public void SomeMethod(int param1)
    {
        Console.WriteLine("Inside of introduced method");
    }

}

And now our target class

[IntroduceAspect]
public class TargetClass
{

}

You might be laughing at our test class, but don’t worry, our aspect will do the work for us. When we look at the compiled assembly with ILSpy, we see that instead of a blank class we have a few more members than we started with, including the members we wanted to introduce.

image

Amongst the aspect related code, we have our ID property and our SomeMethod method. Notice that the getter and setter of ID are delegated to our aspect and so does our method. This is important to keep in mind because when implementing members, they must be marked as public inside of the aspect (because our target class has to access them). However, if you happen to forget, PostSharp will remind you with a compiler error

image

But what happens if you don’t want the introduced members to be public in the target class? Have no fear, PostSharp thought of that too. Let’s have a look at the IntroduceMember attribute.

IntroduceMember attribute

By default, using IntroduceMember by itself will use public visibility and will cause compiler errors if a member with the same signature is already part of the class. We can control the behavior of how the member is implemented by changing the following parameters.

Visibility

By default, PostSharp will introduce the member to the target class with public visibility. We can specify one of the enumerations from PostSharp.Reflection.Visibility to control what visibility the member will have in the target class. Available values are

· Public (Default) – Is publically available.

· Family – Is available to the class and any derived classes. Same as protected.

· Assembly – Is publicly available within the assembly. Same as internal.

· FamilyOrAssembly – Is available to the class and any derived classes, but only within the assembly. Same as protected internal.

· FamilyAndAssembly – Protected types inside the assembly. There is no C# equivalent.

· Private – Only visible to the class.

OverrideAction

There is a chance that the target class already has a member with the same signature. By default, there will be a compiler error if this scenario is encountered. To change the behavior, we can provide one of the enumerations from PostSharp.Aspects.Advices.MemberOverrideAction.

· Default – Fails with a compiler error.

· Fail – Fails with a compiler error.

· Ignore – Continues on, without trying to introduce the member.

· OverrideOrFail – Tries to override the member with our own implementation. If the existing member is defined in a base class and is sealed or non-virtual, it will fail with a compiler error.

· OverrideOrIgnore – Tries to override the member with our own implementation. If the existing member is defined in a base class and is sealed or non-virtual, it will ignore the member introduction and continue on.

IsVirtual

If you would like to introduced member to be virtual (overridable in derived classes) then you can set IsVirtual to true. The member signature in the base class will be marked as virtual.

CopyCustomAttributesAttribute

Sometimes members need to be decorated with attributes. An example of this would be decorating members of a DataContract with DataMember. However, when introducing members from an aspect, any attributes applied to the member in the aspect will not be introduced along with the member in the target. We can use CopyCustomAttributes attribute in addition to the IntroduceMember attribute to introduce the attributes along with the member. Let’s look at an example.

[Serializable]
public class IntroduceAspect : TypeLevelAspect
{
    [IntroduceMember]
    [DataMember(IsRequired=true)]
    public int ID { get; set; }
}

[IntroduceAspect]
[DataContract]
public class TargetClass
{
    [DataMember]
    public string FirstName { get; set; }
}

Our aspect is introducing a member, ID, which is decorated with DataMember. Let’s look at the result in ILSpy

image

The DataMember attribute is not present on ID. Let’s update the aspect to use CopyCustomAttributes.

[Serializable]
public class IntroduceAspect : TypeLevelAspect
{
    [IntroduceMember, CopyCustomAttributes(typeof(DataMemberAttribute), 
            OverrideAction = CustomAttributeOverrideAction.MergeReplaceProperty)]
    [DataMember(IsRequired=true)]
    public int ID { get; set; }

}

In the constructor for CopyCustomAttributes we pass in the base type for the desired attribute and then we set the override action with a value from the CustomAttributeOverrideAction enumeration. When we look at the end result in ILSpy, we see that the attribute was introduced along with the member.

image

CustomAttributeOverrideAction

CustomAttributeOverrideAction is an enum that lets us tell PostSharp how to handle a situation when an attribute of the same type already exists on the target member.

· Default – Fails with a compile time error.

· Fail – Fails with a compile time error.

· Ignore – Ignores the attribute introduction and does not generate an error.

· Add – Adds the attribute as defined, even if it already exists on the target. This could cause duplicate attributes on the target.

· MergeAddProperty – Combines the existing attribute with the attribute being introduced. Any properties defined by the existing attribute will remain. No override will occur. Any properties defined by the introduced attribute will be added to the existing attribute.

· MergeReplaceProperty – Same as MergeAddProperty except that any properties defined by the existing attribute will overridden by the introduced attribute.

Interface Introduction

When introducing an interface via an aspect, the interface must be implemented on the aspect itself. The type will expose the interface at run time, but the aspect actually implements it. Let’s have a look at our interface:

public interface IFriendlyName
{
    string Name { get; set; }
    void PrintName();
}

And now our aspect:

[Serializable]
[IntroduceInterface(typeof(IFriendlyName))]
public class IntroduceAspect : InstanceLevelAspect, IFriendlyName
{
    #region IFriendlyName Members

    public string Name { get; set; }
        
    public void PrintName()
    {
        Console.WriteLine(this.Name);
    }

    #endregion
}

Our test class remains the same, empty

[IntroduceAspect]
public class TestClass
{

}

When we look at the compiled result we see our interface has been implemented

image

Notice that we didn’t use the IntroduceMember attribute on the interface members. Also notice that the resulting implementations of the interface members are private. To make the interface members public we have to apply the IntroduceMember attribute to the members

[Serializable]
[IntroduceInterface(typeof(IFriendlyName))]
public class IntroduceAspect : InstanceLevelAspect, IFriendlyName
{
    #region IFriendlyName Members

    [IntroduceMember]
    public string Name { get; set; }

    [IntroduceMember]
    public void PrintName()
    {
        Console.WriteLine(this.Name);
    }

    #endregion
}

And now the compiled result shows two implementations of our members

image

Looking at the PrintName method, the explicit interface implementation is private, but we’ve introduced a public version which the interface method calls.

IntroduceInterface attribute

To tell PostSharp that we want to introduce an interface, we decorate the aspect with the IntroduceInterface attribute. To tell PostSharp which interface to implement, we pass in a type using typeof(IFriendlyName). Just like the IntroduceMember attribute, there are parameters to control the behavior of the introduction.

· OverrideAction – Exactly the same as IntroduceMember. Determines what to do when the target already implements the interface. Default is to fail with a compiler error.

· IsProtected – If set to true, the interface is not directly implemented by the type. Instead, the type exposes the interface through the IProtectedInterface<T>. Since protected interfaces are considered obsolete, you should leave this as false (default).

· AncestorOverrideAction – Defines the behavior of the introduction when and ancestor of the interface is already applied to the target class. See example below. Available enumerations in the PostSharp.Aspects.Advices.InterfaceOverrideAction are Default (Fail), Fail and Ignore.

Extended Example

Let’s finish up with a bit more in-depth example using some of the behavior parameters.

public interface IIdentifiable
{
    Guid ID { get; set; }
}

public interface IFriendlyName : IIdentifiable
{
    string Name { get; set; }
    void PrintName();
}

[IntroduceAspect]
public class TargetClass : IIdentifiable
{
    #region IFriendlyNameBase Members

    public Guid ID { get; set; }   

    #endregion

    string Name { get; set; }

    public void PrintName()
    {
        throw new NotImplementedException();
    }
}

[Serializable]
[IntroduceInterface(typeof(IFriendlyName), 
AncestorOverrideAction=InterfaceOverrideAction.Ignore)] public class IntroduceAspect : InstanceLevelAspect, IFriendlyName { #region IFriendlyName Members [IntroduceMember(OverrideAction=MemberOverrideAction.Ignore)] public Guid ID { get; set; } [IntroduceMember(OverrideAction=MemberOverrideAction.Ignore)] public string Name { get; set; } [IntroduceMember(OverrideAction=MemberOverrideAction.OverrideOrFail)] public void PrintName() { Console.WriteLine(this.Name); } #endregion }

We define two interfaces. IFriendlyName implements IIdentifiable. Our test class implements IIdentifiable and also has a PrintName method which throws an exception. Our aspect specifies the introduction of IFriendlyName and also implements the required interface members. We specify that we should ignore any implementation of an ancestor (IIdentifiable) of the introduced interface (IFriendlyName). We also specify that we want to ignore the member introduction on the two properties if they exist in the target class. We mark PrintName with the OverrideOrFail because we want to force our own implementation of the PrintName method. The end result looks like this

image

First take a look at the PrintName method. Instead of the original method body, which threw an exception, we see that there is a call to our aspect which invokes our implementation of that method.

Next we see that both interfaces are implemented, but we only have get/set methods for the Name property, not the ID property. This is because PostSharp ignored the implementation of IIdentifiable since it was already implemented on the target class. If we remove InterfaceOverride.Ignore from the IntroduceInterface attribute, we would get a compiler error.

image

If we removed the implementation of IIdentifiable from our test class, we would see get/set methods for ID in the compiled results.

image

Conclusion

Today we covered some good ground on introducing members and interfaces along with some of the nuances that you have to be aware of. Tomorrow we’ll continue with importing members and accessing introduced members at compile time.

self573_thumb[1]Dustin Davis Davis is an enterprise solutions developer and regularly speaks at user groups and code camps. He can be followed on Twitter @PrgrmrsUnlmtd or his blog Programmers-Unlimited.com

I'm pleased to announce that the third and final Community Technical Preview of PostSharp 2.1 is now available for download on our website and as a NuGet package.

The new CTP is principally a stabilization release, with a large number of bug fixes (including all hot fixes from the 2.0 branch) and some corrections in the new API design. This CTP is more stable than the latest builds on the 2.0 branch, but this has of course to be validated by the community.

What’s New in PostSharp 2.1 CTP 3

  • A streamlined experience for first-time users, including a redesigned PostSharp HQ application and new Learn PostSharp tool window in Visual Studio.
  • PostSharp does not enter the evaluation mode automatically. Instead, a dialog box will ask you to explicitly enter the evaluation mode. This adds a little friction for those evaluating PostSharp but should help to clarify any licensing questions.
  • Complete class reference documentation (the conceptual documentation is still not updated).
  • Support for Windows Phone 7.1 Beta.
  • Support for Silverlight and Windows Phone in the NuGet package.

What’s New in PostSharp 2.1 CTP 2

  • Architecture constraints.
  • Extended build-time reflection API.
  • Support for obfuscation.
  • Support for Silverlight 5.
  • License server.

What's New in PostSharp 2.1 CTP 1

  • Improvements in build-time performance.

What’s Next?

Now that the design is nearly finalized, we should finally blog about and document new features. The release candidate will be published as soon as the documentation is complete (the current release has been tested to release candidate-level quality).

Update Today

If your application is not going to production in the next few months, now is a great time to download PostSharp 2.1 and to upgrade your current application in a feature branch. Doing so will help us to validate the new version and move the final release date forward. Please report all issues on the support forum. Thanks!

Happy PostSharping!

-gael