Attributes in REALbasic

| | Comments (11)

One of the new compiler features of 2008r3 are "attributes", which are a powerful language feature that enable you to specify compile-time metadata attached to a code item. If you work in .NET languages, then you've probably encountered this sort of feature before. If attributes are an entirely new concept to you, then this blog posting will hopefully help you to understand them.

When you make a property, what you are doing is creating a named piece of storage that can be accessed at runtime. Attributes are very similar in that they're a named piece of storage to hold information. However, this is information that can be used at *compile* time as well as at runtime. This might seem a bit strange to you at first blush, since it's kind of hard to grasp what that really means. So let's use a concrete example: deprecations. Let's say that you want to deprecate an API in your project; you have a class property that you no longer want people to use. You want the compiler to warn the user whenever they try to access that class property so that the deprecation has a bit more visibility. That means you need some way to alert the compiler to the fact that the class property is deprecated. This is where attributes come in -- it's metadata that allows you to pass information along to the compiler which can be used at compile time.

Attributes consist of a name, and optionally, a value. The name must always be a valid identifier, or a string literal (something contained in quotes). The value, if present, must be a valid literal (numbers, boolean true/false, a string, etc). You create attributes by right-clicking on any code item or project item, and selecting the "Attributes..." menu. Note that not everything allows attributes, so the menu may be disabled. You can specify attributes on any basic class, window, module or interface. You can also specify attributes for any code item contained within one of those project items.

As of 2008r3, there are only two reserved identifiers for attributes: Deprecated and Hidden. However, additional identifiers may be added in the future. When naming your own attributes, you should generally use string literals instead of identifiers to avoid any naming conflicts. However, I should note that there's no enforceable restrictions when it comes to naming attributes -- so you can use an identifier if you'd like, but you run the risk of conflicting with one the compiler might use (even in the future), without any sort of way to generate a compile error.

The Deprecated attribute has an optional value that specifies a replacement API. So, for instance, if you mark your class property as being Deprecated="Foobar", then whenever a usage of the deprecated API is seen while analyzing a project, the warning will tell the user to use "Foobar" instead. This way, you can deprecate an API with or without a replacement API.

The Hidden attribute has no value, and is used to "hide" an API. I'm purposefully being ambiguous here, since this feature will be extended in the future. But for right now, Hidden APIs are not introspectable, and will not be displayed in the debugger and that's it. However, this may be extended in the future to hide APIs from autocomplete, search results, or any number of other areas. This attribute is meant to be a replacement for the absolutely terrible habit people got into years ago of naming things with a prefixed "_" in attempts to hide them from autocomplete.

Speaking of introspection, I mentioned that attributes could be used at compile time or at runtime. In order to access an attribute at runtime, you use the Introspection system. Anything which is a TypeInfo or MemberInfo has a GetAttributes function on it that returns an array of AttributeInfo objects. The Name property is pretty self-explanatory. However, the Value property requires a bit of explanation. If the attribute has no value assigned to it, then the the Value's IsNil method will return true. If the attribute does have a value assigned to it, then the Value property won't report being Nil, and will instead will report the value verbatim. Finally, since attributes are a compile-time property, it doesn't make sense to attempt to mutate one at runtime. Because of that, AttributeInfo.Value is read-only.

We're almost done with the details, I promise! The only other detail I want to cover is with regards to inheritance and overriding. All attributes are inheritted when subclassing. So if you have:


Attributes( Awesome ) Class Foo
End Class

Class Bar
Inherits Foo
End Class


Then Bar will have the Awesome attribute.

Attributes are also overriddeable, so if Bar had the Awesome attribute and assigned a value to it, that's acceptable. Let's say you have the following:


Attributes( Awesome = 12 ) Class Foo
End Class

Attributes( Awesome = "Hello" ) Class Bar
Inherits Foo
End Class


When you are vieing the AttributeInfo for Bar, it will have Awesome="Hello", and when you view it for Foo, it will have Awesome=12.

Phew! Ok, now that we're through what attributes are, we can talk a bit about why you might want to use them yourself. We've already talked about things like the Deprecated and Hidden attributes which are already provided for you. But you can create your own attributes to pass extra information along to be used at runtime. I'm going to use examples of how we can use attributes internally, but you can pretty quickly get ideas of your own.

We could use attributes on methods to say "this method is really a menu handler." Then, when a window opens, it can loop over all the methods on it to find the ones with the "MenuHandler" attribute, and do some hook-up so that events fire. Or, we could use an attribute to say "this property corresponds to that database column", so that we could make some sort of ORM system. Or we could use attributes to resurrect the "bindings" functionality, but in a more easily extended fashion than previously. Or we could use an attribute to denote serialization support for a class, and whether it should be deep-copied or not. The list goes on and on.

Hopefully you now have a better understanding of what attributes are and how you can make use of them in your own applications. Don't be too shocked to see new attributes being added to augment compiler, IDE, or runtime functionality, because I can see all sorts of areas where attributes will make for a very clean approach to solve various problems.

11 Comments

Seems to be a nice feature altough I do not have a concrete use at the moment. Perhaps you could outline a concrete example in a separate blog post? Am I right to assume that Attributes are about the same as Annotations in Java?

@Jef -- I listed a concrete example of how I plan to use it in REALbasic itself for dealing with menu handlers. Or are you looking for more examples than that (or code that demonstrates what I mean)? And yes, Attributes are basically the same thing as Java's Annotations.

So really, Attributes are a power feature that probably most RB users won't use. Sort of like Introspection where unless you realize you need it you'll probably never use it.

Seems like this gives you some powerful tools to enhance RB quickly and hopefully easily. Seems like a good feature to have in the core language.

@Bob -- yes, this is a power feature akin to Introspection. But it's one that will certainly make for a much cleaner underpinning to your application. The compiler is already making use of the attribute system internally, which has cleaned a lot of things up. It's also allowed us to expose more information to Introspection (like Shared and Scope information). So the compiler certainly loves this feature. But the IDE's rendering code will also get a significant boost from Attributes to clean some of it up.

I think users will mostly benefit from the built-in attributes like Deprecated and Hidden. Of course, making your own is helpful too, if you run into the case where you need it. For instance, I can see using attributes in conjunction with some serialization code to denote whether to do a deep copy or a shallow copy.

@Aaron: Outlining a concrete example as some kind of hands-on tutorial would probably be very useful (but I might eventually figure it out myself). Especially, this would make a great addition to the RB documentation because as far as I see, the docs don't explain how to use attributes (I had to check the release notes to find out how to add attributes to a property)

@Jef -- sounds like a great thing to put in the Suggestion Box. ;-) As for the docs, the LR is supposed to tell you about the language, not how the IDE works (generally speaking). I am pretty sure the User's Guide is what has the IDE side of things.

@Aaron. New idea in the suggestion box ;-)

Regarding the LR, I absolutely agree. But since attributes are essentially a language concept, I had expected to find at least some information in the LR. Frankly, I would not have looked that up in the User's Guide if you hadn't mentioned it (yes, Attributes are covered there).

About LR vs. User's Guide: The LR should definately mention attributes, at least in the way how to write them in plain RB code, along with their syntax rules, like used in the examples here.

All this new features that you explain, Instrospection and Attributes, seems in my opinion to finally allow us to create DLLs and Shared Libraries in RealBasic... If I am right, Realbasic will be a huge success, as frankly, it's the easiest way to program even some times I hate it.

Well, make it Thread Safe will be make it 100% to use real threads via MB Plugin or maybe RB new Thread classes.

Hope I am right.

Great tutorial Aaron!

It's mentioned above that Attributes can be used with serialization to do a deep copy or a shallow copy -- what does this mean? I'll admit that I have no idea how to write serialization code or even exactly what it is. Code to incorporate serial code protection for a program (something I'd be interested in) or having to do with serial port communications? Pardon my ignorance, but I really would like to know more about this. Thank you!

Geoffrey A Rabe

@Geoff -- serialization, sometimes also known as "pickling", is the process of taking an object in memory, and converting it to some sort of string that you can save to a disk (or send over the network, etc). So, for instance, let's say I have a Person class, with a FirstName, LastName and Age property. When I serialize the Person class, I might end up with a string like this: "AaronBallman??" With all of this information, I should be able to unserialze that string back into an actual instance of a Person class.

The deep-vs-shallow copy problem stems from the fact that Person could hold references to other classes. For instance, they might hold onto a Car class that describes vehicular information for the Person. A deep copy will serialize the Car reference, but a shallow copy will not. The difficult part is that there's no way to generically know which type of copy a user might want to have. Deep copies are exhaustive and expensive. Shallow copies are cheap, but might miss information.

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.