Передача типа в качестве параметра атрибуту

#c# #generics #custom-attributes

#c# #общие положения #пользовательские атрибуты

Вопрос:

Я написал несколько общий механизм десериализации, который позволяет мне создавать объекты из двоичного формата файла, используемого приложением C .

Чтобы все было чисто и легко изменялось, я создал Field класс, который расширяется Attribute , создается с Field(int offset, string type, int length, int padding) помощью и применяется к атрибутам класса, которые я хочу десериализовать. Вот как это выглядит :

 [Field(0x04, "int")]
public int ID = 0;

[Field(0x08, "string", 0x48)]
public string Name = "0";

[Field(0x6C, "byte", 3)]
public byte[] Color = { 0, 0, 0 };

[Field(0x70, "int")]
public int BackgroundSoundEffect = 0;

[Field(0x74, "byte", 3)]
public byte[] BackgroundColor = { 0, 0, 0 };

[Field(0x78, "byte", 3)]
public byte[] BackgroundLightPower = { 0, 0, 0 };

[Field(0x7C, "float", 3)]
public float[] BackgroundLightAngle = { 0.0f, 0.0f, 0.0f };
  

Затем вызов myClass.Decompile(pathToBinaryFile) извлекает данные из файла, считывая соответствующие типы и размеры с соответствующими смещениями.

Однако я нахожу, что передавать имя типа в виде строки некрасиво.

Возможно ли передать тип более элегантным, но коротким способом, и как?

Спасибо.

Комментарии:

1. Тип такой же, как у отмеченного поля, не так ли? Почему бы не получить тип из поля, вместо того, чтобы отмечать его в конструкторе атрибута?

Ответ №1:

Используйте typeof оператор (возвращает экземпляр Type ):

 [Field(0x7C, typeof(float), 3)]
  

Комментарии:

1. @Heandel: Безопасность типов и поддержка рефакторинга в каждом атрибуте по сравнению с шестью дополнительными символами в каждом атрибуте == Я бы нахмурился 🙂

2. @Heandel, это единственный чистый способ сделать это… Это немного длиннее, но в 100 раз лучше, чем использование строки

3. Я просто делю удивительность typeof на удивительность string… как вы можете видеть, это очень точная оценка 😉

Ответ №2:

Да: пусть атрибут принимает a Type в качестве параметра, а затем передает, например typeof(int) .

Ответ №3:

Да, параметр должен иметь тип Type , и тогда вы можете передать тип следующим образом:

 [Field(0x7C, typeof(float), 3)]
public float[] BackgroundLightAngle = { 0.0f, 0.0f, 0.0f };
  

Ответ №4:

Я не думаю, что вам нужно указывать тип в конструкторе атрибута, вы можете получить его из поля. Смотрите пример:

 public class FieldAttribute : Attribute { }

class Data
{
    [Field]
    public int Num;

    [Field]
    public string Name;

    public decimal NonField;
}


class Deserializer
{
    public static void Deserialize(object data)
    {
        var fields = data.GetType().GetFields();
        foreach (var field in fields)
        {
            Type t = field.FieldType;
            FieldAttribute attr = field.GetCustomAttributes(false)
                                     .Where(x => x is FieldAttribute)
                                     .FirstOrDefault() as FieldAttribute;
            if (attr == null) return;
            //now you have the type and the attribute
            //and even the field with its value
        }
    }
}
  

Комментарии:

1. @Heandel: Возможно, я неправильно понял вашу идею. Но правда в том, что атрибут не может получить информацию о своем «вешателе». Другими словами, у вас есть экземпляр некоторого атрибута с именем a , вы не можете определить, какое поле / свойство / класс a помечается.

Ответ №5:

В C # 10 появилась новая функция (в предварительном просмотре на момент публикации этого поста), которая позволяет создавать универсальные атрибуты!

Используя эту новую функцию, вы можете создать вместо этого универсальный атрибут:

 public class GenericAttribute<T> : Attribute { }
  

Затем укажите параметр типа для использования атрибута:

 [GenericAttribute<string>()]
public string Method() => default;
  

Комментарии:

1. Это C # 11 и все еще в предварительном просмотре: learn.microsoft.com/en-us/dotnet/csharp/whats-new /…