Однократная инициализация атрибута у класса C#

В моем приложении имеются сообщения, которые должны отправляться/приниматься по сети. Для идентификации этих сообщений используется хэш от его структуры данных. Структура данных постоянная и представлена в виде строки. Хэш этой строки служит идентификатором этого сообщения.

Появилась идея прописывать структуру данных в атрибуте класса и в результате получился вот такой код:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class FooAttribute : Attribute
{
    public byte[] Hash { get; }
    public FooAttribute(string value)
    {
        Hash = SHA256.HashData(Encoding.ASCII.GetBytes(value));
        Console.WriteLine($"Init attribute with param '{value}'");
    }
}

[Foo(nameof(TestClass))]
public class TestClass
{
    public byte[] Hash => GetType().GetCustomAttribute<FooAttribute>()!.Hash;
}

У меня было представление, что эти атрибуты создаются однократно и будут храниться в типе (класс Type) в виде синглтон объектов, но оказалось это не так. При каждой попытке получить доступ к атрибуту, создается его новый экземпляр. На практике результат работы приложения выглядит вот так:

TestClass testClass = new();
Type typeOfTestClass = typeof(TestClass);

Console.WriteLine($"Hash of class is {testClass.Hash}");
Console.WriteLine($"Hash of class is {testClass.Hash}");
Console.WriteLine($"Hash of class is {typeOfTestClass.GetCustomAttribute<FooAttribute>()!.Hash}");
Console.WriteLine($"Hash of class is {typeOfTestClass.GetCustomAttribute<FooAttribute>()!.Hash}");

// Init attribute with param 'TestClass'
// Hash of class is System.Byte[]
// Init attribute with param 'TestClass'
// Hash of class is System.Byte[]
// Init attribute with param 'TestClass'
// Hash of class is System.Byte[]
// Init attribute with param 'TestClass'
// Hash of class is System.Byte[]

Идея этого решения заключалась в том, чтобы высчитывать хэш для класса один раз при его первичной инициализации и не тратить на это ресурсы при последующих.

Возможно ли добиться такого поведения атрибутов? Или такое невозможно и придется прибегать к статическим свойствам класса?


Ответы (1 шт):

Автор решения: rotabor

Поскольку проблема с атрибутами вторична, я предлагаю решение для первичной проблемы автора - сделать перманентный хэш названия класса.

Вариант с использованием CRTP:

public class Item<TSelf> where TSelf : Item<TSelf> {
    public static readonly byte[] Hash;
    static Item() {
        Console.WriteLine(typeof(TSelf).FullName);
        Hash = SHA256.HashData(Encoding.ASCII.GetBytes(typeof(TSelf).FullName));
    }
}

public class TestClass1 : Item<TestClass1> { }

public class TestClass2 : Item<TestClass2> { }

internal static class Program {
    static string Ba2String(this byte[] ba) => String.Join("; ", ba);
    static void Main(string[] args) {
        Console.WriteLine(TestClass1.Hash.Ba2String());
        Console.WriteLine(TestClass2.Hash.Ba2String());
        Console.WriteLine(TestClass1.Hash.Ba2String());
        Console.WriteLine(TestClass2.Hash.Ba2String());
    }
}

-->

ConsoleApp1.TestClass1
191; 234; 69; 62; 236; 166; 234; 250; 228; 18; 48; 95; 52; 40; 188; 98; 181; 23; 84; 252; 138; 60; 54; 106; 194; 15; 254; 197; 223; 69; 250; 251
ConsoleApp1.TestClass2
109; 197; 218; 171; 226; 154; 4; 64; 126; 185; 184; 121; 163; 138; 9; 224; 238; 147; 44; 163; 91; 63; 225; 24; 109; 244; 156; 80; 132; 28; 23; 159
191; 234; 69; 62; 236; 166; 234; 250; 228; 18; 48; 95; 52; 40; 188; 98; 181; 23; 84; 252; 138; 60; 54; 106; 194; 15; 254; 197; 223; 69; 250; 251
109; 197; 218; 171; 226; 154; 4; 64; 126; 185; 184; 121; 163; 138; 9; 224; 238; 147; 44; 163; 91; 63; 225; 24; 109; 244; 156; 80; 132; 28; 23; 159
→ Ссылка