Creating and Using Attributes in C#

Creating and Using Attributes in C#

ยท

4 min read

Yello, so in today's article, I would be writing about Creating Attributes in C# and utilizing them.
You might have seen a code like this:

 public  class  Book
  {
    public  int  BookId  {  get;  set;  }
    Column("Description")]
    public  string  Title  {  get;  set;  }
  }

And from the code above you might be wondering what [Column("Description")] is. And how can the code you are writing use it?
[Column("Description")] is called an Attribute.
An attribute is used to decorate parts of your code with metadata or information that can be utilized during your code execution.
Attributes in C# can be used on various targets like classes, interface, methods and other types which you can find here. There are some inbuilt attributes in dotnet like:

  • AssemblyTitleAttribute
  • AssemblyDescriptionAttribute
  • AttributeUsageAttribute
  • CallerFilePathAttribute etc

Creating an Attribute

Attributes can be created by inheriting from the Attribute class. The example below shows an Attribute created with the property, MinAge.

class AgeAttribute : Attribute {
    public int MinAge;
    public AgeAttribute(int minAge = 5){
    MinAge = minAge
    }
}

The AgeAttribute can be called via its alias, Age or its full name AgeAttribute.
The [AttributeUsage(AttributeTargets.Property)] describes that the attribute should only be used for Properties.
To find out other restrictions that can be defined for attribute targets check here.
The AgeAttribute can be added to a property in various ways for instance:

  • [Age] : This would create an attribute by setting the property, MinAge to the default value (5).
  • [Age(20)] : This would create an attribute by setting the property, MinAge to 20.

The class below has the AgeAttribute added to one of its properties:

class Employee {
  public string Name;
  Age(30)]
  public int Age;
}

But then custom attributes are just bare and have no effect when being used to annotate.
To harness the power of custom attributes, you would need to read the attribute defined for a particular target and implement your logic based on the defined attribute.
For instance, you could prevent your data reader from logging out the age of Employees below the age of 30.

To access the defined attribute in the property of an instantiated object (eg. Employee object), you would need to do something like this:

 Employee person = new(){
    Name = "Sam",
    Age = 20
};
  PropertyInfo propertyInfo = typeof(Employee).GetProperty("Age");
  AgeAttribute  ageAttribute = (AgeAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(AgeAttribute)); 
  if(ageAttribute.MinAge < 30){
// Do something
}
  else {
// Do something else
}

The Attribute.GetCustomAttribute method takes in two arguments in the code above. But it also has other overloads for different cases.
The first argument is of type, PropertyInfo property and the second argument is of type Type
These two arguments allow the GetCustomAttribute method to retrieve the Attribute defined on the Age property. This is possible through what is called Reflection. To know more about reflection check this.

Creating a WeatherLogger using Custom Attributes.

The code below shows an example of creating a Weather logger that decides on which Temperature unit to print to the console based on the attribute attached to the Temperature property. I made use of a weather API. to obtain real-time weather data.
By the time you would be reading this. The API key exposed in the code might not be available.

using System.Reflection;
using System.Text.Json;

//call log method
WeatherLogger.Log("Lagos,Nigeria");


//Define Accepted Temperature Units
enum Units
{
    Celsius,
    Fahrenheit
}


// Create Attritube constraining it to only Properties
[AttributeUsage(AttributeTargets.Property)]
class UnitAttribute : Attribute
{
    public Units Unit { get; }
    public UnitAttribute(Units unit = Units.Celsius)
    {
        Unit = unit;
    }
}

//Create Weather Class
class Weather
{
    public string country { get; set; }
    public string region { get; set; }
    //Applying the Attribute to the Temperature Property
    [Unit(Units.Fahrenheit)]
    public double Temperature { get; set; }

}

class WeatherLogger
{
    public static string apiKey = "c59654e9053c4be586d212935223101";
    public static void Log(string city)
    {
        //Obtain the value Defined in the Unit Attribute  from the Temperature Property
        PropertyInfo propertyInfo = typeof(Weather).GetProperty(nameof(Weather.Temperature));
        UnitAttribute unitAttribute = (UnitAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(UnitAttribute));
        //if the Unit Attribute isn't defined, create a UnitAttribute object
        unitAttribute ??= new UnitAttribute();
        var client = new HttpClient();
        HttpResponseMessage response = client.GetAsync($"https://api.weatherapi.com/v1/current.json?q={city}&key={apiKey}").Result;
        var info = response.Content.ReadAsStringAsync().Result;
        var country = JsonDocument.Parse(info).RootElement.GetProperty("location").GetProperty("country").ToString();
        var region = JsonDocument.Parse(info).RootElement.GetProperty("location").GetProperty("region").ToString();
        var tempC = Convert.ToDouble(JsonDocument.Parse(info).RootElement.GetProperty("current").GetProperty("temp_c").ToString());
        var tempF = Convert.ToDouble(JsonDocument.Parse(info).RootElement.GetProperty("current").GetProperty("temp_f").ToString());

        var newWeather = JsonSerializer.Serialize<Weather>(new Weather()
        {
            region = region,
            country = country,
            //Decide which temperature unit to display based on the set unitattribute value
            Temperature = unitAttribute.Unit == Units.Celsius ? tempC : tempF,
        });
        Console.WriteLine(newWeather);

    }

}

For the code that uses Microsoft.Extensions.Configuration to retrieve secret key check this.

Thanks for reading through ๐Ÿ’ช