Export (0) Print
Expand All

Extending Metadata with Custom Attributes

Custom attributes are a simple way to extend the metadata of any given managed element. They are available in all languages that target the .NET Framework.

This topic walks through how to use custom attributes from Managed Extensions for C++.

The purpose of this feature is to allow for metadata to be user extensible. For example, in Microsoft Transaction Server (MTS) 1.0, behavior with respect to transactions, synchronization, load balancing, and so on was specified through custom GUIDs inserted into the type library by using the ODL custom attribute. Hence, a client of an MTS server could determine its characteristics by walking the type library. In the .NET Framework, the analog of the type library is metadata, and the analog of the ODL custom attribute is custom attributes. Also, walking the type library is analogous to using reflection on the types.

The following example illustrates some important features of custom attributes:

#using <mscorlib.dll>
using namespace System;
using namespace System::Reflection;

public __value enum Access { Read, Write, Execute };

/* Defining the Job attribute:
   The special "attribute" attribute automatically makes a __gc class a 
   custom attribute (class implicitly inherits from System::Attribute); 
   arguments explained below 
*/
[attribute( AttributeTargets::Class, AllowMultiple=true )]
public __gc class Job 
{ 
public:
   __property void set_Priority( int value ) { m_Priority = value; }
   __property int get_Priority( ) { return m_Priority; }
   /* You can overload constructors to specify Job attribute in 
      different ways */
   Job( ) { m_Access = Access::Read; }
   Job( Access a ) { m_Access = a; }
   Access m_Access;

protected:
   int m_Priority;
};

__gc __interface IService 
{
   void Run( );
};

/* Using the Job attribute:
   Here we specify that QueryService is to be read only with a priority of 2.
   To prevent namespace collisions, all custom attributes implicitly 
   end with "Attribute". Job could have been specified as
   public __gc class JobAttribute : public Attribute 
   In other words, Job and JobAttribute are equivalent.
*/
[JobAttribute( Access::Read, Priority=2 )]
__gc struct QueryService : public IService {
   void Run( ) 
   {
      //...
   }
};

/* Because we said AllowMultiple=true, we can add multiple attributes 
   where it makes sense 
*/
[JobAttribute(Access::Read, Priority=1)]
[JobAttribute(Access::Write, Priority=3)]
__gc struct StatsGenerator : public IService {
   void Run( )
   {
      //...
   }
};

int main( ) 
{
   IService* pIS;
   QueryService* pQS = new QueryService;
   StatsGenerator* pSG = new StatsGenerator;

   Console::WriteLine(S"Please enter the service to run");
   Console::Write(S"QueryService or StatsGenerator> ");

   if(Console::Read( ) == L'Q')
      pIS = __try_cast<IService*>( pQS );
   else
      pIS = __try_cast<IService*>( pSG );

   // Reflection
   MemberInfo* pMI = pIS->GetType( );
   Object* pObjs __gc[] = pMI->GetCustomAttributes(false);
   
   /* We can now quickly and easily view custom attributes for an 
      Object through Reflection */
   for( int i=0; i < pObjs->Length; i++ ) 
   {
      Console::Write(S" Service Priority = ");
      Console::WriteLine(static_cast<Job*>(pObjs[i])->Priority);
      Console::Write(S" Service Access = ");
      Console::WriteLine(static_cast<Job*>(pObjs[i])->m_Access);
   }
}

This example shows a common usage of the custom attributes: instantiating a server that can fully describe itself to clients.

A custom attribute definition specifies where it is syntactically allowed. The above Job attribute is allowed on classes, as indicated by the argument:

[ attribute( AttributeTargets::Class ) ] ...

Actually,

[ attribute( Class ) ]  ...

is sufficient if there are no name collisions. The definition of AttributeTargets:

__value enum AttributeTargets {
    Assembly = 0x1,
    Module = 0x2,
    Class = 0x4,
    Struct = 0x8,
    Enum = 0x10,
    Constructor = 0x20,
    Method = 0x40,
    Property = 0x80,
    Field = 0x100,
    Event = 0x200,
    Interface = 0x400,
    Parameter = 0x800,
    Delegate = 0x1000,
    All = 0x1fff,
    ClassMembers = 0x17fc,
};

shows the possible syntactic elements on which a custom attribute can be used. Combinations of these values (using logical OR) may be used .

Two other named arguments that the "attribute" attribute takes are:

bool AllowMultiple; // default: false
bool Inherited;     // default: true

AllowMultiple was previously demonstrated. If TRUE, then the custom attribute can be specified multiple times on any valid targets.

The Inherited named argument specifies whether a custom attribute applied on a base class will show up on reflection of a derived class. The following example illustrates this:

#using<mscorlib.dll>
using namespace System;
using namespace System::Reflection;

[attribute( AttributeTargets::Method, Inherited=false )]
__gc class BaseOnlyAttribute { };

[attribute( AttributeTargets::Method, Inherited=true )]
__gc class DerivedTooAttribute { };

__gc struct IBase {    // base class
public:
   [BaseOnly, DerivedToo]
   virtual void meth( ) {}
};

__gc class Derived : public IBase { // derived class
public:
   void meth( ) {}
};

int main( ) 
{

   IBase* pIB = new Derived;

   MemberInfo* pMI = pIB->GetType( )->GetMethod( S"meth" );
   Object* pObjs __gc[] = pMI->GetCustomAttributes( true );

   Console::WriteLine( pObjs->Length ) ;
}

should print:

1

Reflection on Derived::meth will show DerivedTooAttribute but not BaseOnlyAttribute.

One last but very important note about creating custom attributes is that the public part of the custom attribute class must be serializable. This is one of the restrictions imposed by the runtime. What this means when authoring custom attributes is that named arguments of your custom attribute are limited to compile-time constants. Think of it as a sequence of bits appended to your class layout in the metadata.

Example 1

__gc struct Deadly {};

[attribute( All )]
__gc struct A 
{
   A( System::Type* ) {}
   A( String*)  {}
   A( int ) {}
};

[A( __typeof( Deadly ) )] //typeof operator only allowed in custom attribute blocks
__gc struct B {};

Example 2

[attribute( Class )]
__gc class A 
{
public:
   A( Object* ) {} //error: illegal type for a custom attribute
private:
   Object* m_Obj; //OK member is private
};

Declarative security is basically an extension of custom attributes. Security attributes are part of the .NET Framework. The definition and implication of security attributes in your code are described in the Security specs provided by the frameworks team

See Also

Attributes Walkthroughs | attribute

Show:
© 2014 Microsoft