On a quest for the silver bullet..

Boo AstAttributes explained

Writing extensions for Boo is a very powerful thing. In this post I’m going to explain how to write AstAttributes in Boo. These attributes are much more than normal .net attributes. They are one of the ways you can extend the Boo language.

Before you read on, these posts might be useful to read:

To implement an AstAttribute you have to create a class that inherits the AbstractAstAttribute-class and implements the Apply-method. The class name must be postfixed with “Attribute”, the syntax is then “‘YourAttributeName’Attribute”. Like this:

1
2
3
4
5
6
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
 
class DemoAttribute(AbstractAstAttribute):
	def Apply(type as Node):
		pass

You have now created an AstAttribute named Demo, and you can use it (from another assembly) like this:

1
2
3
4
[Demo]
class MyTestClass:
	def constructor():
		pass

For the moment our attribute does absolutely nothing. To make it do something we have to do some work in the Apply-method we implemented. The method takes a Node as an argument. A Node can be just about any type, since alot of classes inherit the Node-class. Here the Node is the actual “thing” that you tagged the Attribute with. In our example the Node will be a definition of the MyTestClass. Say you tag a Method you will get the method definition (including all its content) passed into the Apply method. We will examine the possibilities this gives us, but let us first take a look at what sub types the Node-class has, that are relevant for us.

Boo ClassDefinition inheritance picture

The picture shows the relevant subtypes. In other words these subtypes show where you can use your attribute, meaning you can tag classes, method parameters, methods, constructors, fields and properties with an AstAttribute that you create.

To experiment with this, I’ve made a little solution for you that you, it will print just about any information that is available at compile-time. (I’ve added it at the bottom)

NotifyPropertyChanged

I want to demonstrate an AstAttribute that I’ve been using in quite a few of the presentations I’ve held on Boo, and that I have also mentioned in this blog earlier (What makes boo great): the NotifyPropertyChanged-attribute. I wrote this attribute about half a year ago, and it really blew my mind on all the possibilities Boo offers.

First let’s look at how we implement the INotifyPropertyChanged in C#, just to remind us of what parts we want to auto generate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Customer: INotifyPropertyChanged
{
    private string firstName;
    private string lastName;
 
    public string FirstName
    {
        get
        {
            return firstName;
        }
        set
        {
            firstName = value;
            NotifyPropertyChanged("FirstName");
        }
    }
    public string LastName
    {
        get
        {
            return lastName;
        }
        set
        {
            lastName = value;
            NotifyPropertyChanged("LastName");
        }
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

That code is as ugly as always. We basically want to write it like this:

1
2
3
4
5
6
[NotifyPropertyChanged]
class BooCustomer:
	[Property(LastName)]
	lastName as String
	[Property(FirstName)]
	firstName as String

Ok, the first thing we have to do is make sure the attribute is used on a class and not somewhere else. Let’s do it like this:

1
2
3
4
5
6
7
8
9
def Apply(target as Node):
	classDef = GetClassDefinition(target)
 
 
def GetClassDefinition(target as Node) as ClassDefinition:
	classDef = target as ClassDefinition
	if classDef is null:
		raise Exception("The NotifyPropertyChangedAttribute can only be used on classes")		
	return classDef

We’re casting the Node to a ClassDefinition, which is the type it must be if the attribute is used on a class. Then we must add the INotifyPropertyChanged to the class definition. Notice that our BooCustomer hasn’t implemented it, since it’s expecting us (the attribute) to do it.

Ok, lets add the code for that:

1
2
3
4
5
6
def Apply(target as Node):
	classDef = GetClassDefinition(target)
	AddInterface(classDef)
 
def AddInterface(classDef as ClassDefinition):
	classDef.BaseTypes.Add(SimpleTypeReference("System.ComponentModel.INotifyPropertyChanged"))

That was simple! Next we need to add the actual Event:

1
2
3
4
5
6
7
8
9
10
def Apply(target as Node):
	classDef = GetClassDefinition(target)
	AddInterface(classDef)
	AddEvent(classDef)
 
def AddEvent(classDef as ClassDefinition):
	notifyEvent = Event()
	notifyEvent.Type = SimpleTypeReference("System.ComponentModel.PropertyChangedEventHandler")
	notifyEvent.Name = "PropertyChanged"
	classDef.Members.Add(notifyEvent)

Here we create an event, set the right type, set the name, and simply add it to our classdefinition.

The last thing to do is to find all the properties, and add some code to the setter of those properties. This is how we find all the properties:

1
2
3
4
5
def AddToAllProperties(classDef as ClassDefinition):
	for member in classDef.Members:
		property = member as Property
		continue if property is null
		AddNewBody(property)

And to conclude it, we must implement the AddNewBody-method:

1
2
3
4
5
6
7
def AddNewBody(property as Property):
	oldBody as Block = property.Setter.Body
	property.Setter.Body = [|
		$oldBody
		if (PropertyChanged is not null):
			PropertyChanged(self,System.ComponentModel.PropertyChangedEventArgs($(property.Name)))
	|]

Ok, here’s something new. The [|...|]-tags. You probably can understand what is happening here, but not really why. The syntax is easy to both read and write. It’s called quasi quotation. I will describe it further in another post.

That concludes our attribute! Here’s the complete code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import System
import Boo.Lang.Compiler
import Boo.Lang.Compiler.Ast
 
class NotifyPropertyChangedAttribute(AbstractAstAttribute):
 
	def Apply(target as Node):
		classDef = GetClassDefinition(target)
		AddInterface(classDef)
		AddEvent(classDef)
		AddToAllProperties(classDef)
 
	def GetClassDefinition(target as Node) as ClassDefinition:
		classDef = target as ClassDefinition
		if classDef is null:
			raise Exception("The NotifyPropertyChangedAttribute can only be used on classes")
		return classDef
 
	def AddInterface(classDef as ClassDefinition):
		classDef.BaseTypes.Add(SimpleTypeReference("System.ComponentModel.INotifyPropertyChanged"))
 
	def AddEvent(classDef as ClassDefinition):
		notifyEvent = Event()
		notifyEvent.Type = SimpleTypeReference("System.ComponentModel.PropertyChangedEventHandler")
		notifyEvent.Name = "PropertyChanged"
		classDef.Members.Add(notifyEvent)
 
	def AddToAllProperties(classDef as ClassDefinition):
		for member in classDef.Members:
			property = member as Property
			continue if property is null
			AddNewBody(property)
 
	def AddNewBody(property as Property):
		oldBody as Block = property.Setter.Body
		property.Setter.Body = [|
			$oldBody
			if (PropertyChanged is not null):
				PropertyChanged(self,System.ComponentModel.PropertyChangedEventArgs($(property.Name)))
		|]

Using that code, you can now write classes like this that actually implement the complete INotifyPropertyChanged-behaviour:

1
2
3
4
5
6
7
8
 
 
[NotifyPropertyChanged]
class BooCustomer:
	[Property(LastName)]
	lastName as String
	[Property(FirstName)]
	firstName as String

Try it yourself and look at the code through reflector to see what is really going on here. Remember to put the attribute and the classes using it in different assemblies.

Any comments on how to improve or clarify any of this will be appreciated.

The solution that prints alot of info on your attribute, and shows where you can place it: Boo AttributeExplorer-solution

- Tore Vestues

December 18th, 2008 at 0:31 (063)


6 Responses to “Boo AstAttributes explained”

  1. Jonas Follesø Says:

    Nice one Tore :)

    Would it be possible to add additional logic to some of the setters/getters of the properties, and still get the event notification?

    Or – say you want to add a third property, like e-mail, where you want to do validation logic in the setter?

    I guess what I’m asking: In class BooCustomer, is the PropertyChanged event available implicitly because of the NotifyPropertyChanged attribute?

  2. Tore Vestues Says:

    @Jonas:

    It is no problem to add additional logic in the getters and setters. What the NotifyPropertyChanged-attribute does is to add, at the end of each setter, the event notification. In the AddNewBody-method I first add the $OldBody to the new body, then add the event notification.

    The PropertyChanged is available from your code since it is added compile time before your logic is resolved. You will not find it through intellisense, but this is more a tools-issue. In addition I will not recommend coding directly against the autogenerated (at compile time) PropertyChanged event. It will couple you too much to the attributes interal workings. That being said, I should probably have given the event a guarantied unique name (which you can easily do) so there won’t be any confusion.

  3. Boris Bluntschli Says:

    First of all, thanks for these great articles, Tore :)

    One slight problem I had, was that the attribute currently does not seem to work when you also have pure getters in your class. To fix that, I added

    return if property.Setter is null

    to the beginning of “AddNewBody”.

  4. Ashish Jain Says:

    This example was awesome…
    It seriously reflects the power BOO exposes..
    Great Article..

  5. Bedding Collections %0B Says:

    `”, I am really thankful to this topic because it really gives great information ~’”

  6. Zoe Munnelly Says:

    Fairly nice post. I simply stumbled upon your site and desired to state that I’ve actually appreciated searching your site posts. All things considered I’ll be subscribing to your own give food to and i also hope you create once more very soon!

Leave a Reply