All notes
ProgrammingConcepts

Attributes

Attributes provide a powerful method of associating metadata, or declarative information, with code (assemblies, types, methods, properties, and so forth).

After an attribute is associated with a program entity, the attribute can be queried at run time by using a technique called reflection.

When you compile your code for the runtime, it is converted into Microsoft intermediate language (MSIL) and placed inside a portable executable (PE) file along with metadata generated by the compiler. See MSDN: extending metadata.

Attributes have the following properties:

Using Attributes

Attributes can be placed on most any declaration, though a specific attribute might restrict the types of declarations on which it is valid.

Examples:


[System.Serializable]
public class SampleClass
{
    // Objects of this type can be serialized.
}

// Attribute on a method:
using System.Runtime.InteropServices;
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();

// More than one attribute placed on parameters:
void MethodA([In][Out] ref double x) { }
void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }

// Multi-use attribute - specified more than once for a given entity:
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
    // ...
}

Attribute Name

By convention, all attribute names end with the word "Attribute" to distinguish them from other items.

However, you do not need to specify the attribute suffix when using attributes in code. For example, [DllImport] is equivalent to [DllImportAttribute].

Attribute Parameters

Many attributes have parameters, which can be positional, unnamed, or named.


// The following are the same:
// The first parameter is positional/unnamed, the other two are named.
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]

Attribute Targets

By default, an attribute applies to the element that it precedes. But you can also explicitly identify, for example, whether an attribute is applied to a method, or to its parameter, or to its return value.


// applies to assembly and module:
using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]

// default: applies to method
[SomeAttr]
int Method1() { return 0; }

// applies to method
[method: SomeAttr]
int Method2() { return 0; }

// applies to return value
[return: SomeAttr]
int Method3() { return 0; }

Common Uses

Writing Attributes

The class must derive directly or indirectly from System.Attribute.

The constructor's parameters are the custom attribute's positional parameters.

Any public read-write fields or properties are named parameters.

If your attribute class contains a property, that property must be read-write.

The following example shows codes to tag types with the name of the programmer who wrote the type.


// Make the attribute valid only on class and struct declarations:
[System.AttributeUsage(System.AttributeTargets.Class |
                       System.AttributeTargets.Struct,
                       AllowMultiple = true)  // multiuse attribute
]
public class Author : System.Attribute
public class Author : System.Attribute
{
    private string name;
    public double version;

    public Author(string name)
    {
        this.name = name;
        version = 1.0; // Default.
    }
}

[Author("P. Ackerman", version = 1.1)]
[Author("R. Koch", version = 1.2)]
class SampleClass
{
    // P. Ackerman's code goes here...
    // R. Koch's code goes here...
}

Accessing Attributes, by reflection

The key method is GetCustomAttributes, which returns an array of objects that are the run-time equivalents of the source code attributes.


[Author("P. Ackerman", version = 1.1)]
class SampleClass

is conceptually equivalent to this:


Author anonymousAuthorObject = new Author("P. Ackerman");
anonymousAuthorObject.version = 1.1;

The code is not executed until SampleClass is queried for attributes. Calling GetCustomAttributes on SampleClass causes an Author object to be constructed and initialized as above.

GetCustomAttributes returns all the attribute objects in an array.

An example:


private static void PrintAuthorInfo(System.Type t)
{
    System.Console.WriteLine("Author information for {0}", t);

    // Using reflection.
    System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t);

    // Displaying output.
    foreach (System.Attribute attr in attrs)
    {
        if (attr is Author)
        {
            Author a = (Author)attr;
            System.Console.WriteLine("   {0}, version {1:f}", a.GetName(), a.version);
        }
    }
}

// Call this function by:
PrintAuthorInfo(typeof(MyClass));

Reflection

Wikipedia: reflection.

Reflection can be used for observing and modifying program execution at runtime (dynamically assigning program code at runtime).

A language supporting reflection provides a number of features available at runtime:

Reflection History

The earliest computers were programmed in their native assembly language, which were inherently reflective as these original architectures could be programmed by defining instructions as data and using self-modifying code. As programming moved to higher-level languages such as C, this reflective ability disappeared (outside of malware).

Common Attributes

Global Attributes

They apply to an entire assembly or module.

Global attributes appear in the source code after any top-level using directives and before any type, module, or namespace declarations.

In C# projects, global attributes are put in the AssemblyInfo.cs file.

Assembly Identity Attributes

Three attributes (with a strong name, if applicable) determine the identity of an assembly: name, version, and culture.

You can set an assembly's version and culture using attributes. However, the name value is set by the compiler, the Visual Studio IDE in the Assembly Information Dialog Box, or the Assembly Linker (Al.exe) when the assembly is created, based on the file that contains the assembly manifest.

The AssemblyFlagsAttribute attribute specifies whether multiple copies of the assembly can coexist.

AssemblyName 
    Fully describes the identity of an assembly.

AssemblyVersionAttribute
    Specifies the version of an assembly.

AssemblyCultureAttribute
    Specifies which culture the assembly supports.

AssemblyFlagsAttribute
    Specifies whether an assembly supports side-by-side execution on the same computer, in the same process, or in the same application domain.   

Informational Attributes

You can use informational attributes to provide additional company or product information for an assembly.

Assembly Manifest Attributes

It includes title, description, default alias, and configuration.

Obsolete Attribute

Each use of an entity marked obsolete will subsequently generate a warning or an error, depending on how the attribute is configured.


[System.Obsolete("use class B")]
class A
{
    public void Method() { }
}
class B
{
    // Second argument "True" will cause error instead of warning.
    [System.Obsolete("use NewMethod", true)]
    public void OldMethod() { }
    public void NewMethod() { }
}

// Generates 2 warnings:
// Why 2? One for the declaration of the class reference, and one for the class constructor.
// A a = new A();

// Generate no errors or warnings:
B b = new B();
b.NewMethod();

// Generates an error, terminating compilation:
// b.OldMethod();

Conditional Attribute

It makes the execution of a method dependent on a preprocessing identifier.

A conditional method must be a method in a class or struct declaration and must not have a return value.

Conditional Attribute on a method

Using Conditional is a cleaner, more elegant, and less error-prone alternative to enclosing methods inside #if…#endif blocks.


[Conditional("DEBUG")]
static void DebugMethod()
{
}

#if DEBUG
    void ConditionalMethod()
    {
    }
#endif

Multi-use is OR:


[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

// To achieve AND, use serial conditional methods:

[Conditional("A")]
static void DoIfA()
{
    DoIfAandB()
}

[Conditional("B")]
static void DoIfAorB()
{
    // ...
}

Conditional Attribute on an Attribute class

The Conditional attribute can also be applied to an attribute class definition.


[Conditional("DEBUG")]
public class Documentation : System.Attribute
{
    string text;

    public Documentation(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Caller Info Attributes

It contains information about the caller to a method: the file path of the source code, the line number in the source code, and the member name of the caller.