C#:utilizzo di attributi personalizzati

C#:utilizzo di attributi personalizzati

In questo articolo mostrerò come utilizzare gli attributi personalizzati in C#. Ad esempio, sto creando un'app Console che codifica a colori l'output in base a uno stato.

Ecco come appare:

Cos'è un attributo?

Innanzitutto, cos'è un attributo?

Gli attributi sono un modo pulito per aggiungere ulteriori informazioni sulle cose (classi, metodi, proprietà, valori enum). Queste informazioni possono essere utilizzate in fase di esecuzione per modificare il comportamento del programma.

Ad esempio, quando esegui il test unitario, i tuoi metodi di test hanno il seguente aspetto:

[TestMethod()]
public void TestSum_Given1And1_Returns2()
Code language: C# (cs)

Qui TestMethod è un attributo che dice al framework di unit test che questo metodo è uno unit test e che dovrebbe eseguirlo.

Nota:in Java gli attributi sono chiamati annotazioni.

Fase 1:crea l'attributo personalizzato

using System;

namespace UsingCustomAttributes
{
    public class BackgroundColorAttribute : Attribute
    {
        public ConsoleColor ConsoleColor { get; }
        public BackgroundColorAttribute(ConsoleColor consoleColor)
        {
            ConsoleColor = consoleColor;
        }
    }
}
Code language: C# (cs)

Fondamentalmente devi sottoclassare la classe Attribute.

NOTA:i parametri del costruttore devono essere valori costanti. Questo è il motivo per cui sto usando l'enumerazione ConsoleColor qui.

Fase 2 – Assegna l'attributo

using System;

namespace UsingCustomAttributes
{
    public enum DeviceStatus
    {
        [BackgroundColor(ConsoleColor.Green)]
        Registered,
        [BackgroundColor(ConsoleColor.Red)]
        PingFailed,
        [BackgroundColor(ConsoleColor.Yellow)]
        PortNotOpen,
        [BackgroundColor(ConsoleColor.Yellow)]
        RegistrationFailed,
        [BackgroundColor(ConsoleColor.Green)]
        FoundAndRegistered
    }
}

Code language: C# (cs)

Qui sto assegnando l'attributo a ciascun valore enum, specificando il colore appropriato. La sintassi qui è fondamentalmente [AttributeName (parametro al costruttore)]. Si noti che la parola "Attributo" è esclusa.

Fase 3:ottieni il valore dell'attributo in fase di esecuzione

Primo:aggiungi un metodo per estrarre il valore dell'attributo

Sfortunatamente dobbiamo usare la riflessione per ottenere il valore dell'attributo. Questo porta a un codice dall'aspetto piuttosto complicato.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace UsingCustomAttributes
{
    public static class DeviceStatusExtensions
    {
        public static ConsoleColor GetBgColor(this DeviceStatus status)
        {
            Type deviceStatusType = typeof(DeviceStatus);
            string statusName = Enum.GetName(deviceStatusType, status);
            MemberInfo[] memberInfo = deviceStatusType.GetMember(statusName);

            if(memberInfo.Length != 1)
            {
                throw new ArgumentException($"DeviceStatus of {status} should only have one memberInfo");
            }

            IEnumerable<BackgroundColorAttribute> customAttributes = memberInfo[0].GetCustomAttributes<BackgroundColorAttribute>();
            BackgroundColorAttribute colorAttribute = customAttributes.FirstOrDefault();

            if(colorAttribute == null)
            {
                throw new InvalidOperationException($"DeviceStatus of {status} has no BackgroundColorAttribute");
            }

            return colorAttribute.ConsoleColor;
        }
    }
}

Code language: C# (cs)

Nota:ottenere il valore dell'attributo può essere ridotto a una riga, ma per chiarezza l'ho scritto a lungo. Se sei interessato, ecco la versione con il one-liner:

public static class DeviceStatusExtensions
{
	private static T GetAttribute<T>(this DeviceStatus status) 
		where T : System.Attribute
	{
		return (status.GetType().GetMember(Enum.GetName(status.GetType(), status))[0].GetCustomAttributes(typeof(T), inherit: false)[0] as T);
	}
	public static ConsoleColor GetBgColor(this DeviceStatus status)
	{
		return status.GetAttribute<BackgroundColorAttribute>().ConsoleColor;
	}
}
Code language: C# (cs)

Quindi – usa il valore dell'attributo

using System;
using System.Collections.Generic;
namespace UsingCustomAttributes
{
    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine("Fetching devices");
            List<Device> devices = LoadDevices();
            Console.WriteLine("Outputting current status of all devices...");

            Console.ForegroundColor = ConsoleColor.Black;
            foreach(var d in devices)
            {

                Console.BackgroundColor = d.Status.GetBgColor();
                Console.WriteLine($"Device {d.IPAddress} Status={d.Status}");
            }
            Console.ResetColor();

            Console.ReadKey();
        }

        private static List<Device> LoadDevices()
        {
            return new List<Device>()
            {
                new Device()
                {
                    IPAddress="10.1.187.10",
                    Status = DeviceStatus.Registered
                },
                new Device()
                {
                    IPAddress="10.1.187.12",
                    Status = DeviceStatus.PingFailed
                },
                new Device()
                {
                    IPAddress="10.1.187.23",
                    Status = DeviceStatus.PortNotOpen
                },
                new Device()
                {
                    IPAddress="10.1.187.57",
                    Status = DeviceStatus.RegistrationFailed
                },
            };
        }
    }
}

Code language: C# (cs)