Full Lectures Set
- C#Lectures - Lecture 1: Primitive Types
- C# Lectures - Lecture 2: Work with text in C#: char, string, StringBuilder, SecureString
- C# Lectures - Lecture 3 Designing Types in C#. Basics You Need to Know About Classes
- C# Lectures - Lecture 4: OOP basics: Abstraction, Encapsulation, Inheritance, Polymorphism by C# example
- C# Lectures - Lecture 5:Events, Delegates, Delegates Chain by C# example
- C# Lectures - Lecture 6: Attributes, Custom attributes in C#
- C# Lectures - Lecture 7: Reflection by C# example
- C# Lectures - Lecture 8: Disaster recovery. Exceptions and error handling by C# example
- C# Lectures - Lecture 9:Lambda expressions
- C# Lectures - Lecture 10: LINQ introduction, LINQ to 0bjects Part 1
Introduction
6th article from my series is about nice .NET technology that is called custom attributes. Custom attributes gives you the ability to add more content to source code and add more information almost to every record in metadata table for assembly. We can work with this metadata during runtime and it may impact the flow of application. Most famous technologies of .NET as WPF, WCF, etc. use attributes a lot. Every .NET engineer should be familiar with this technology to be efficient in his work with .NET and C#.
Attributes Areas
Attribute specifies declarative information about entities that are declared in your program. Everyone knows method or member modifiers as: private
, public
, protected
and internal
. These are attributes, standard attributes. Besides standard attributes, C# gives programmers an ability to create their own attributes. For example, you may add attribute ModelAttribute
to your classes and describe in it how your class is presented in software model and design. Later, you can generate some documentation where your model is present by ModelAttribute
to see if your model properly presented by classes and structures. Attributes information can be retrieved during runtime and this is very useful for specific scenarios. The main thing we should know about attributes is that it is metadata. Most of the attributes don't mean anything for the compiler and it ignores them and only stores them to metadata of assembly. .NET libraries have hundreds of attributes that you can use in your code. Few examples: DllImport
, Conditional
, Obsolete
, Serializable
, etc. these are attributes that you use in almost every project.
Attributes What Is Inside
First thing you should know and remember is that attribute is an object of a specific type. This type should directly or indirectly derive from System.Attribute abstract
class. C# supports only CLS compatible attributes and to achieve this, you should derive your attribute type from System.Attribute
. As attributes are objects of type, this type must have public
constructor. Applying attributes is the same as creation of objects by calling constructor. One more additional thing that can be used when you declare attribute is a defining of open fields or properties or class. Syntax for applying attribute is:
[attribute(positional_parameters, name_parameter = value, ...)]
Name of the attribute and its values are specified within the square brackets, before the element to which the attribute is applied. Positional parameters specify the essential information and the name parameters specify the optional information. Positional parameters are passed to attribute via constructor and named parameters are every non static public read-write property or field. See example below:
[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)]
You may apply more than one attribute for each element. You can put attributes in separate [] brackets, or specify one by one using comma in one brackets. Order of separate attributes and the one that go with comma is not relevant.
Attributes Example
In C#, names of custom attributes are stored in [] brackets. You should define attribute directly before name of the class, method, object, etc. Let's take a look at the code below, I explained in comments purpose of attributes that are used there:
[DebuggerDisplay("String status is={m_string_status}, second integer is: {m_int2}")]
internal class AttributesSampleClass
{
private int m_int1 = 1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private int m_int2 = 2;
private string m_string_status;
[Obsolete("This method is obsolete,
use ConsolseOutput2 instead")] public void ConsoleOutput1()
{
Console.WriteLine("Output 1");
}
[DebuggerStepThrough] public void ConsoleOutput2()
{
Console.WriteLine("Output 2");
}
public AttributesSampleClass()
{
}
[Flags]
public enum EnumFlags
{
Flag1 = 1,
Flag2 = 2,
Flag3 = 4,
Flag4 = 8,
Flag5 = 16,
Flag6 = 32
}
[DebuggerNonUserCode]
[DllImport("User32.dll")] public static extern int SetForegroundWindow(IntPtr point);
}
Console.WriteLine("------------------SAMPLE---------------------");
AttributesSampleClass sample = new AttributesSampleClass();
sample.ConsoleOutput1(); sample.ConsoleOutput2();
The screenshot below demonstrates how we hide value of integer member m_int2
from debugger, but showed it when moving mouse pointer to type. This was done using attributes for debugging:
<img alt="" src="1081990/1.png" style="width: 640px; height: 293px;" />
As you can see, attributes are very important and often used in day to day developer life. To be effective with .NET, you need first of all to know its attributes and their usage where it is required. Remember that C# allows you to apply attributes for everything that can be presented as metadata.
Predefined Attributes
The .NET Framework provides three pre-defined attributes:
AttributeUsage
Conditional
Obsolete
Creating Your Own Custom Attributes and Using Them
Now when we know about attributes, their usage and the fact that all of them derive from System.Attribute
, let's do step by step creation of our own attribute:
You can analyze the following: Assembly
, Module
, ParameterInfo
, MemberInfo
, Type
, MethodInfo
, ConstructorInfo
, FieldInfo
, EventInfo
, PropertyInfo
and related to them *Builder
. All of them have IsDefined
and GetCustomAttributes
.
- First of all, we need to define
public
class that is derived from System.Attribute
. In my case, this class name is MyOwnExcitingAttribute
. There is no requirement, but this is a recommendation from Microsoft to use suffix Attribute in your attribute type name.
- Second thing is that any
Attribute
class must have at least one open constructor. By adding parameters to constructor, you may specify there things that should provided by developer that use your attribute. Besides this, you can define non static open fields that developer may define or not.
- When you define your own
Attribute
class, you can use AttributeUsageAttribute
to define area of usage for your attribute and it will be applied only for specific types or members. Besides this, you may configure other behavioral things for your own attribute using AttributeUsageAttribute
:
- Attributes can be inherited if you use property
Inherited=true
in AttributeUsageAttribute
.
- Most attributes can't be applied to the same element several times, and actually there is no reason to do this. If you want to apply the same attribute for entity more than one time, use named parameter
AllowMultiple
with true
value.
- Defining attribute itself and applying it to some members is something useless. All you'll have finally is additional metadata in your assembly and there is no impact for your application workflow. This is only partially true. While runtime code is analyzed for attributes by mechanism called reflection. We will not dive into reflection in this article, but only review its usage. When you write your code that behaves differently if specific attributes are used, you should check for these attributes by yourself. There are several ways to check if attribute is defined. We will review two of them:
- You can call method
IsDefined
from type Type
to check if some attribute is connected with type
. If method IsDefined
returns true
, it means type
is connected with the requested attribute:
if (user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))
This is the code from my example. You can see the whole code in the attached archive with source. Please note that this method to check if attribute is defined, is applicable only for types.
- When we need to check if attribute is applied to method, assembly or module, we need to use another way than described above. For such purposes, we can use class
System.Reflection.CustomAttributeExtensions
. This class contains static
methods to get attributes. In general, this class has the following three methods that has many overloads:
GetCustomAttribute
- returns instance of requested attribute if it was applied for a specific module. This instance contains fields and values defined before compilation. If no attribute like this applied, it returns null
.
GetCustomAttributes
- returns array of instances of attributes of specific type. Same as previous each member has parameters that were assigned before compilation. Returns empty collection if nothing found.
IsDefined
- returns true
if some attributes of requested type were applied to a specific element. This is a very fast method as it does not reserialize anything and simply checks if attribute was applied or not.
- Recommendations for Attribute class:
- Try to use one constructor
- Try to avoid open members, rather properties
- Try to use simple types for attributes members and fields, don't use complex types or several dimensions arrays (you can read more about primitive types in my article here). This is a recommendation, you can use more complex members in your
attribute
class, but there is no guarantee it will be CLS compatible.
- Try not have methods in
Attribute
class - the idea for this class is to be simple and hold some state, try to avoid building complex logic inside Attribute
class.
- Define attributes as sealed. Once you put the class to
GetCustomAttributes
, it will look for specific attribute or attributes derived from it. It may not return the exact class that you look for and you always need to check type of returned attribute. To avoid this checking, use sealed
when you define attribute.
The code below demonstrates example for things described in this section:
[AttributeUsage(AttributeTargets.All,Inherited=false, AllowMultiple=false)] public sealed class MyOwnExcitingAttribute : System.Attribute
{
private string m_StringData;
private int m_IntegerData;
public MyOwnExcitingAttribute()
{
m_StringData = "default value";
}
public MyOwnExcitingAttribute(string s)
{
m_StringData = s;
}
public int IntegerData
{
get { return m_IntegerData; }
set { m_IntegerData = value; }
}
public string StringData
{
get { return m_StringData; } }
}
public class AttributeUser
{
[MyOwnExciting("custom value applied to function", IntegerData = 5)]
public void FunctionThatDoSomething()
{
}
[MyOwnExciting("custom value applied to member", IntegerData = 10)]
public int m_IntData;
[MyOwnExciting(IntegerData = 10)] public int m_IntData2;
}
public class NotAttributeUser
{
}
AttributeUser user = new AttributeUser();
if (user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))
{
Console.WriteLine("Attribute is defined");
}
NotAttributeUser not_user = new NotAttributeUser();
if (not_user.GetType().IsDefined(typeof(MyOwnExcitingAttribute), false))
{
}
else
{
Console.WriteLine("Attribute is not defined");
}
var members = from m in typeof(AttributesSampleClass).GetTypeInfo().DeclaredMembers select m;
foreach (MemberInfo m in members)
{
ShowAttributes(m);
}
var members2 = from m2 in typeof(AttributeUser).GetTypeInfo().DeclaredMembers select m2;
foreach (MemberInfo m2 in members2)
{
ShowAttributes(m2);
}
}
public static void ShowAttributes(MemberInfo member)
{
var attributes = member.GetCustomAttributes<Attribute>(); Console.WriteLine(member.Name + " attributes are:");
foreach (Attribute at in attributes)
{
Console.WriteLine("Attribute type is: " + at.GetType().ToString());
if (at is MyOwnExcitingAttribute)
{
Console.WriteLine("String data is: " + ((MyOwnExcitingAttribute)at).StringData);
Console.WriteLine("int data is: " + ((MyOwnExcitingAttribute)at).IntegerData);
}
}
}
Some Useful Standard Attributes
[DebuggerDisplay]
- Using it, you can display values of type members when moving mouse pointer to the type while debugging
[DebuggerBrowsable]
- Using it, you can play with the way how member or property is shown in debugger
[DebuggerTypeProxy]
- Used if you need to make significant changes to type under debugger but leaving original type unchanged
[DebuggerStepThrough]
- Used for avoiding stepping into method under debugger
[DebuggerNonUserCode]
- Indicates that type or member is not a user code of application
[Obsolete]
- Used to notify developer that he shouldn't use this version of the code instead of the one he used. There is compilation warning about it.
[Conditional]
- Is applied to method or class. It can be used to enable disable to display diagnostic information
[Flags]
- Applicable to enum
s indicates that numeration can be treated as a bit field
You can find the full list of standard attributes here.
Sources