Purpose of Common Predefined Attributes in C#
Apr 8, 2020 • 8 Minute Read
Introduction
In this guide we are going to take a look at one of the most commonly used features of C#. Attributes allow developers to associate metadata or declarative information in their projects and their code. One of the ways I personally like to use them is to mark classes or functions obsolete as app development continues. First we are going to take a look at what attributes are and see some examples. Then we are going to check out the most common attributes and see how we can define our own custom attributes.
Attributes
There are many places in your code where you can define attributes like assemblies, methods, and types, and the list goes on. Once you associate the attribute with a selected part of the code you'll have the option to query it with a technique called Reflection. In the .NET framework every type and component, like assemblies and classes, have their own metadata defined by default, which can be extended by custom metadata information as you please. When you allow metadata information, you have the option to choose the scope; you can apply it to entire assemblies or just specific parts of your code.
Finally, the most special thing about metadata is that your own app can inspect its own metadata and another program's metadata. This allows you to build in compatibility checking and other nice features.
There is a skeleton code which can be used as a template when you work with attributes:
attribute ( positional_parameters
The default attributes follow a naming convention that says you need to suffix your custom attribute with Attribute. In practice, this means that when you have an attribute called DllImport it actually points to the DllImportAttribute in the .NET Library. If you define your own custom attributes you should follow this concept.
Let's say you have the following code, which represents a server machine. It has two functions: one for restarting the machine and one for installing the latest updates.
public class Server
{
private string _name;
public string Name { get => _name; set => _name = value; }
public Server(string name) { Name = name; }
public void UpdateServer() { Console.WriteLine($"Downloading and installing updates on server: {Name}!"); }
public void RestatServer() { Console.WriteLine($"Gracefully restarting the server: {Name}"; }
}
As the project evolves, you may want to add an extra function that is going to deprecate the UpdateServer() and handle both updating installation and restarting tasks with some extra steps. How do you let other developers know that a new function will be introduced that covers these functionalities?
You adjust the code like this:
public class Server
{
private string _name;
public string Name { get => _name; set => _name = value; }
public Server(string name) { Name = name; }
[Obsolete("Do not use the UpdateServer() method, use the method UpdateAndRestart()", false)]
public void UpdateServer() { Console.WriteLine($"Downloading and installing updates on server: {Name}!"); }
public void RestatServer() { Console.WriteLine($"Gracefully restarting the server: {Name}"); }
public void UpdateAndRestart() { Console.WriteLine($"Installing latest updates on machine: {Name} and restarting"); }
}
The key point here is the following line:
Obsolete("Do not use the UpdateServer() method
It needs to have accurate function names, otherwise the compiler will throw an error stating that the method we are referring to cannot be found.
The second argument is either true or false. When you set it to false, the compiler will throw a warning but compilation will go through. If it is set to true, an error exception will be thrown and compilation will not happen. This behavior is common in most of the predefined attributes.
Attribute-targets point to the entity the attribute is targeted to. In this case, the UpdateServer() function is the target.
Predefined Attributes
Predefined attributes are a set of attributes that are built into the .NET library by Microsoft. There is a base class for attributes called [System.Attribute]. Every custom or predefined attribute inherits from this base class.
The most common predefined attributes are:
- Serialization
- NonSerialization
- Obsolete
- DllImport
- WebMethod
The type of app you are developing and the market area you develop it for will determine which common predefined attributes you use.
Custom Attributes
Anyone using the .NET framework can define their custom attributes for private use or packaged into a public library or app, but there are some rules:
- Your custom attribute must be derived from the System.Attribute base class.
- You must suffix your attribute name with Attribute.
- You should set the probable targets with the AttributeUsage parameter.
- You need to implement a class constructor and write-accessible properties, which means your properties cannot be read-only.
Create Your First Custom Attribute
The custom attribute created in this example will add metadata for the server class in the form of Name and Location. The name is the name of the company, the location is the country.
using System;
using System.Reflection;
namespace Pluralsight
{
[AttributeUsage(AttributeTargets.Class)]
public class CompanyAttribute : Attribute{
private string _name;
private string _location;
public string Name { get => _name; set => _name = value; }
public string Location { get => _location; set => _location = value; }
public CompanyAttribute(string company, string location) { Name = company; Location = location; }
}
[Company("Microsoft","USA")]
public class Server
{
private string _name;
public string Name { get => _name; set => _name = value; }
public Server(string name) { Name = name; }
[Obsolete("Do not use the UpdateServer() method, use the method UpdateAndRestart()", false)]
public void UpdateServer() { Console.WriteLine($"Downloading and installing updates on server: {Name}!"); }
public void RestatServer() { Console.WriteLine($"Gracefully restarting the server: {Name}"); }
public void UpdateAndRestart() { Console.WriteLine($"Installing latest updates on machine: {Name} and restarting"); }
}
public class ComPredefAttr
{
public static void Main()
{
Server a = new Server("Domain Controller");
MemberInfo info = typeof(Server);
object[] attrib = info.GetCustomAttributes(typeof(CompanyAttribute), false);
foreach (Object attribute in attrib)
{
CompanyAttribute b = (CompanyAttribute)attribute;
Console.WriteLine($"{b.Name}, {b.Location}");
}
Console.ReadKey();
}
}
}
You have already defined some parts of the code--what's new here is the CompanyAttribute class. It follows the custom attribute standards, as it has the right name, and the publicly writeable attributes. Notice how the reference changes when you want to apply this attribute to the Server class.
Upon executing the code the following output becomes visible:
Microsoft, USA
In order to pull out the metadata from the attributes, the System.Reflection namespace needs to be imported. Then in the Main() function it will become clear how to access the data. With this data, custom logic can be implemented in the app, and you will be able to optimize the execution.
Conclusion
In this guide you have learned the concept of attributes and why they are important to C# and the .NET library. You have learned about custom attributes and some best practices for using them in your app. Finally, you have defined your first custom attribute. I hope this guide has been informative to you and I would like to thank you for reading it!