Custom Attributes In C#
Custom Attributes In C#
Written by Mike James   
Tuesday, 23 June 2015
Article Index
Custom Attributes In C#
Attribute Constructors
Practical attributes
An Example

csharp

An Example

Consider for a moment the problem of assigning a format to the fields of a struct using a new attribute - Formattable.

Implementing a rough sketch of such a facility reveals quite a lot about the difficulties inherent in doing a “good job” using attributes. The idea is simple, the implementation quickly becomes complicated.

First we need a new attribute:

[AttributeUsage(AttributeTargets.Field,
                AllowMultiple = false,
                Inherited = false)]
public class Formattable : Attribute
{
 public string format;
 
 public Formatable(string _format)
 {
  format = _format;
 }
}

 

There is nothing new here but it is worth saying that it would be better to implement the format string as a get/set property.

This attribute can now be applied to any field in any struct or class. Notice that you can restrict its application to say a field in a struct rather than a class.

Now that we have an attribute we can apply it:

public struct MyData
{
 public int notacost;
 [Formatable("Money")]
 public int cost;
}

Here we have a simple struct with two int fields one of which, cost, will hold a currency value and hence is tagged with the Formattable attribute set to Money. Immediately we have a problem in that the user can enter any string they care to into the new attribute – not just the few we want to allow.

One way of restricting the input to an attribute is to define an enum. For example:

public enum FormatType{Money,Percentage};

With this enum the attribute definition changes to:

[AttributeUsage(AttributeTargets.Field,
                AllowMultiple = false,
                Inherited = false)]
public class Formattable : Attribute
{
 public FormatType  format;
 public Formatable(FormatType _format)
 {
  format = _format ;
 }
}

and the struct becomes:

public struct MyData
{
 public int notacost;
 [Formatable(FormatType.Money)]
 public int cost;
}

Now the user really can only enter values that you provide.

The next task is to create some machinery to process the attribute. In this case we can assume that some sort of method has to be provided that displays the struct, taking notice of the attributes. 

We are going to write a display method that accepts a struct and formats each of its fields depending on the annotation. 

The simplest way of providing this machinery is to introduce a static class – after all why should we need to create instances of this machinery?

public static class Formatter
{

The first problem is what to pass to the method that does the formatting?

 

Banner

 

In principle we could pass any struct or class to the method and there really isn’t a type-safe way of doing this without using generics and this would complicate the example.

However as long as we actually test the type passed to the method this should at least be runtime type-safe. If you want to restrict what can be passed to structs you can, using:

static public void display(ValueType a)

Unfortunately this also allows simple types such as int and long to be passed so you need to add another test.

The problem is that struct isn’t a type, it’s a keyword that creates a struct which inherits directly from ValueType as do all simple types. This means you can’t separate all structs from the rest of the ValueTypes. You can’t even simply test to see if what has been passed in has fields to determine if it is a struct because simple types like int also have fields!

Moving on to provide the mechanism for inspecting and processing the attributes, the first step is to get all the fields:

System.Reflection.FieldInfo[]
             fields= a.GetType().GetFields();

Next we step through the array and process each field in turn:

foreach (System.Reflection.FieldInfo
                          field in fields)
{

We need the array of Formattable attributes that have been applied and in this case we also need the value of the field:

Formatable[] formats=(Formatable[])field.
       GetCustomAttributes(typeof(Formatable),
                                        false);
int temp = (int)field.GetValue(a);

Notice that we should also check that the type of the field is suitable for the formatting about to be applied but this has been omitted for simplicity.

Now we can use the Formattable attribute object to determine what format string to store in format:

string format="";
if (formats.Length!=0)
{
 if (formats[0].format==FormatType.Money)
 {
  format="C";
 }
}

Notice there can only be one Formattable object because of the:

AllowMultiple = false

Finally we can display the result and close the foreach loop, method and class:

  }
  MessageBox.Show(temp.ToString(format));
 }
}

Of course in a real application we wouldn't just display the resulting format it would be use in some way.

Now you can write:

MyData SomeData = new MyData();
SomeData.cost = 123;
SomeData.notacost = 456;
Formatter.display(SomeData);

The cost field, tagged as money format, will display with a currency symbol and the notacost field will display without. Unfortunately as it stands the display method also accepts simple types so:

Formatter.display(1);

displays the two fields associated with a boxed int. There seems to be no elegant way of stopping this from happening.

One solution would be to require the Formattable attribute to be applied to the entire struct before any of its fields are processed. That is, change the attribute to

[AttributeUsage(AttributeTargets.Field|
                AttributeTargets.Struct,
                AllowMultiple = false,
                Inherited = false)]
public class Formattable : Attribute

add a constructor that takes no parameters and add a test that the passed-in parameter did indeed have a Formattable attribute before processing it.

The static method that implements the machinery would be better associated with the struct or, in general, the types to which it applies i.e. it should be a method of the type is is going to display the fields of. The extension method facility can be used to retrofit methods to classes that already exist. You can even add new methods to build in types such as int or string.

Unfortunately the problem is that structs inherit directly from ValueType makes it impossible to add an extension method to all structs and nothing else. You can easily add an extension method to a single named struct but why bother… you might as well just add the method to the struct directly. To add the display method to all value types you simple change its definition to:

static public void display( 
                this System.ValueType a)

That is, add the modifier “this” to the first parameter. Now you can call the display method using:

SomeData.display();

In other words, the display method has been added to every struct you create – powerful isn’t it!

Unfortunately it has actually been added to every value type so you can also write:

int i = 10;
i.display();

which is perhaps not what you intended. In this case it will display the two fields supported by a boxed int i.e. the maximum and minimum values.

Conclusion

Attributes are something that you probably won’t use everyday, but now that you know exactly how they work you can spot when the approach might be useful.

 

Related Articles

In search of default properties

 

To be informed about new articles on I Programmer, install the I Programmer Toolbar, subscribe to the RSS feed, follow us on, Twitter, FacebookGoogle+ or Linkedin,  or sign up for our weekly newsletter.

 

Banner


The Perils of the C# Parallel For

Making parallel code easier to use is an important development, but making it easier also means you can use it without realising what you are getting into. Unless we can find a way of making parallel  [ ... ]



Linq and XML

XML, which is all about tree structured data, and Linq, which is all about querying collections, might not seem to fit together, but they work together just fine.


Other Articles

 

blog comments powered by Disqus

<ASIN:1449380344>

<ASIN:0123745144>

<ASIN:0321658701>

<ASIN:0321741765>

<ASIN:0470495995>

<ASIN:0672330792>

<ASIN:1430229799>

<ASIN:0262201755>

<ASIN:0596800959>

<ASIN:047043452X>

<ASIN:193435645X>

<ASIN:0596007124>

<ASIN:0521114292>



Last Updated ( Tuesday, 23 June 2015 )
 
 
Banner

   
RSS feed of all content
I Programmer - full contents
Copyright © 2015 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.