Возможно ли запрашивать пользовательские атрибуты в C # во время компиляции (не время выполнения)

18

Другими словами, можно создать сборку, которая даже не компилируется (предполагается, что проверяющий код не удаляется), если у каждого из классов нет («должны иметь») пользовательские атрибуты (например, Author и Версия)?

Вот код, который я использовал для запросов во время выполнения:

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


namespace ForceMetaAttributes
{

    [System.AttributeUsage ( System.AttributeTargets.Method, AllowMultiple = true )]
    class TodoAttribute : System.Attribute
    {
        public TodoAttribute ( string message )
        {
            Message = message;
        }
        public readonly string Message;

    }

    [System.AttributeUsage ( System.AttributeTargets.Class |
        System.AttributeTargets.Struct, AllowMultiple = true )]
    public class AttributeClass : System.Attribute
    {
        public string Description { get; set; }
        public string MusHaveVersion { get; set; }


        public AttributeClass ( string description, string mustHaveVersion ) 
        {
            Description = description; 
            MusHaveVersion = mustHaveVersion ; 
        }

    } //eof class 


    [AttributeClass("AuthorName" , "1.0.0")]
    class ClassToDescribe
    {
        [Todo ( " A todo message " )]
        static void Method ()
        { }
    } //eof class 

    //how to get this one to fail on compile 
    class AnotherClassToDescribe
    { 

    } //eof class 

class QueryApp
{
        public static void Main()
        {

                Type type = typeof(ClassToDescribe);
                AttributeClass objAttributeClass;


                //Querying Class Attributes

                foreach (Attribute attr in type.GetCustomAttributes(true))
                {
                        objAttributeClass = attr as AttributeClass;
                        if (null != objAttributeClass)
                        {
                                Console.WriteLine("Description of AnyClass:\n{0}", 
                                                                    objAttributeClass.Description);
                        }
                }



                //Querying Class-Method Attributes  

                foreach(MethodInfo method in type.GetMethods())
                {
                        foreach (Attribute attr in method.GetCustomAttributes(true))
                        {
                                objAttributeClass = attr as AttributeClass;
                                if (null != objAttributeClass)
                                {
                                        Console.WriteLine("Description of {0}:\n{1}", 
                                                                            method.Name, 
                                                                            objAttributeClass.Description);
                                }
                        }
                }
                //Querying Class-Field (only public) Attributes

                foreach(FieldInfo field in type.GetFields())
                {
                        foreach (Attribute attr in field.GetCustomAttributes(true))
                        {
                                objAttributeClass= attr as AttributeClass;
                                if (null != objAttributeClass)
                                {
                                        Console.WriteLine("Description of {0}:\n{1}",
                                                                            field.Name,objAttributeClass.Description);
                                }
                        }
                }
                Console.WriteLine ( "hit Enter to exit " );
                Console.ReadLine ();
        } //eof Main 
} //eof class 

} //eof namespace 


//uncomment to check whether it works with external namespace 
//namespace TestNamespace {

//  class Class1 { }
//  class Class2 { }

//}

Изменить: просто чтобы оправдать мой выбор для ответа. Я думаю, что casperOne дал правильный ответ на вопрос.

Однако причины для запроса вопроса были слабая . Вероятно, я должен начать использовать внешний инструмент, например: FinalBuilder или создать единичные тесты, проверяющие это «требование», используя платформы Pex, Nunit или другие модульные системы тестирования ...

ИЗМЕНИТЬ Я добавил небольшой фрагмент кода консольной программы в конце ответов, который выполняет проверку ... не стесняйтесь комментировать, критиковать или предлагать улучшения
Еще раз я понял, что это «требование» должно быть реализовано как часть модульного тестирования непосредственно перед «проверкой»

    
задан Yordan Georgiev 23.05.2017 в 12:29
источник

6 ответов

7

Нет, невозможно подключить компиляцию сборки и проверить, существует ли она.

Однако вы можете подключиться к процессу сборки, который состоит не только из-за запуска компилятора. Вы можете создать пользовательскую задачу MSBUILD (или NAnt, если вы ее используете), которая проверяет сборку через отражение после ее создания, а затем отказывает сборку, если у нее нет необходимых атрибутов.

Конечно, вы должны, вероятно, еще проверить это и в коде. То, что вы пытаетесь сделать, не является хорошей заменой для правильной проверки времени выполнения.

    
ответ дан casperOne 15.04.2009 в 21:12
  • Thaks для ответа! Что вы имеете в виду более конкретно: «То, что вы пытаетесь сделать, не является хорошей заменой для правильной проверки времени выполнения».? –  Yordan Georgiev 15.04.2009 в 21:40
  • @YordanGeorgiev: Даже если у вас есть процесс сборки, который гарантирует, что атрибут применяется, вы должны STILL проверить свой код времени выполнения, чтобы убедиться, что этот атрибут применяется. Вы не можете остановить проверку там, потому что думаете, что поймали ее во время компиляции. –  casperOne 15.04.2009 в 21:44
  • Таким образом, требование получения атрибутов не менее 4 "должно иметь" влияет на производительность, из-за отражения ... Кажется, что выполнение проверок в модульных тестах будет чем лучшая идея?! –  Yordan Georgiev 16.04.2009 в 21:09
  • Меня беспокоило также то, что MSBUILD не предназначен для веб-проектов ... и NAnt кажется слишком тяжелым для такого «простого» требования (или может быть «простым» из-за моей неопытности ...) –  Yordan Georgiev 16.04.2009 в 21:12
  • @casperOne: Я до сих пор не понимаю, почему, по вашему мнению, необходимы проверки времени выполнения, по крайней мере, если ваши сборки имеют сильные имена. –  Anton Tykhyy 18.04.2009 в 08:32
4

Вы можете запустить шаг после сборки, который отображает в DLL, чтобы делать то, что вы хотите.

Вам нужно будет написать приложение командной строки, которое загружает DLL и отражает типы. Затем вы запускаете это приложение командной строки как шаг после сборки. Я сделал это в прошлом. Это не так сложно сделать, если вы понимаете API отражения.

PostSharp делает это для достижения аспектно-ориентированного программирования. На самом деле довольно круто.

    
ответ дан Brian Genisio 15.04.2009 в 21:17
  • Брайан, спасибо! Выглядел многообещающе, я проверю это завтра ... Здесь в Финляндии сейчас 10:43. Если я выясню что-то значимое, я отправлю код ... –  Yordan Georgiev 15.04.2009 в 21:43
  • Arggh ... запахи, что он будет преобразован в закрытое решение ... –  Yordan Georgiev 16.04.2009 в 21:06
1

Атрибуты - это только время выполнения. Однако:

Было бы возможно создать правило в FXCop (статический анализ), которое будет терпеть неудачу, если атрибут не определен, и ваш процесс сборки / проверки может проверить это правило и сбой соответствующим образом.

    
ответ дан Jason Coyne 15.04.2009 в 21:13
  • Не 100% истинно. «Устаревшие» и «Условные» являются атрибутами времени компиляции. –  Michael Meadows 15.04.2009 в 21:18
1

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

    
ответ дан em70 15.04.2009 в 21:14
  • Я добавил небольшую тягу этого типа инструмента ниже –  Yordan Georgiev 18.04.2009 в 11:24
1

Для меня это больше похоже на проблему тестирования, чем на проблему компиляции. То есть вы спрашиваете: «Откуда я знаю, что мой код написан правильно?» где «написано правильно» имеет (среди прочего) коннотацию, что все классы украшены определенным атрибутом. Я бы подумал о написании модульных тестов, которые подтверждают, что на самом деле соблюдаются ваши правила включения атрибутов. Вы можете запустить процесс сборки (и / или checkin) с помощью этого набора тестов после сборки (до проверки) в качестве условия успешной сборки (checkin). Он не сломает компиляцию, так как это необходимо для завершения тестов, но это сломает сборку, так сказать.

    
ответ дан tvanfosson 15.04.2009 в 21:22
  • Я боюсь, что модульные тесты не всегда на 100% подходят для всей сборки. В какой-то степени я видел обман на модульных тестах ... В контексте вопроса, если бы все мысли даже не компилировались, а принудительные пользовательские атрибуты минимальны (например, Author, Version) –  Yordan Georgiev 15.04.2009 в 21:37
  • Я бы написал, вероятно, один единичный тест для каждого атрибута. Он будет проходить через все классы в сборке (или методы в классах) и проверять наличие атрибута. Вам не пришлось бы писать отдельный тест для каждого класса / метода. –  tvanfosson 15.04.2009 в 21:50
  • Спасибо за ответ tvanfosson! Вы, где правильно указать, что эта проверка должна выполняться как часть процесса тестирования устройства, я добавил фрагмент кода примера того, как это можно сделать (но не как часть конкретного модульного теста, так как можно использовать NUnit, PEX или что-то еще, и я не знаю всех вариаций ... Ваш ответ напомнил мне, что всегда нужно смотреть сначала на общую картину, а затем на начало кодирования ... –  Yordan Georgiev 18.04.2009 в 08:19
0
//PLEASE COMMENT IF YOU FIND BUGS OR SUGGEST IMPROVEMENTS


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

namespace MustHaveAttributes
{
 [AttributeClass ( "Yordan Georgiev", "1.0.0" )] 
 class Program
 {


 static void Main ( string [] args )
 {
  bool flagFoundCustomAttrOfTypeAttributeClass = false; 
  Console.WriteLine ( " START " );

  // what is in the assembly
  Assembly a = Assembly.Load ( "MustHaveAttributes" );
  Type[] types = a.GetTypes ();
  foreach (Type t in types)
  {
   object[] arrCustomAttributes = t.GetCustomAttributes ( true );


   if (arrCustomAttributes == null || arrCustomAttributes.GetLength ( 0 ) == 0)
   {
    //DO NOT CHECK IN
    ExitProgram ( t, "Found class without CustomAttributes" );
   }


   foreach (object objCustomAttribute in arrCustomAttributes)
   {
    Console.WriteLine ( "CustomAttribute for type  is {0}", t );
    if (objCustomAttribute is AttributeClass)
     flagFoundCustomAttrOfTypeAttributeClass = true; 
   }

   if (flagFoundCustomAttrOfTypeAttributeClass == false)
   { //DO NOT CHECK IN 
    ExitProgram ( t, "Did not found custom attribute of type AttributeClass" );
   }
   Console.WriteLine ( "Type is {0}", t );
  }
  Console.WriteLine ("{0} types found", types.Length );

  //NOW REQUIREMENTS IS PASSED CHECK IN
  Console.WriteLine ( " HIT A KEY TO EXIT " );
  Console.ReadLine ();
  Console.WriteLine ( " END " );
 }



 static void ExitProgram ( Type t, string strExitMsg  )
 {

  Console.WriteLine ( strExitMsg );
  Console.WriteLine ( "Type is {0}", t );
  Console.WriteLine ( " HIT A KEY TO EXIT " );
  Console.ReadLine ();

  System.Environment.Exit ( 1 );

 }
} //eof Program


//This will fail even to compile since the constructor requires two params
//[AttributeClass("OnlyAuthor")]  
//class ClassOne
//{ 

//} //eof class 


////this will not check in since this class does not have required custom
////attribute
//class ClassWithoutAttrbute
//{ }



[AttributeClass("another author name " , "another version")]
class ClassTwo
{ 

} //eof class


[System.AttributeUsage ( System.AttributeTargets.Class |
 System.AttributeTargets.Struct, AllowMultiple = true )]
public class AttributeClass : System.Attribute
{

 public string MustHaveDescription { get; set; }
 public string MusHaveVersion { get; set; }


 public AttributeClass ( string mustHaveDescription, string mustHaveVersion )
 {
  MustHaveDescription = mustHaveDescription;
  MusHaveVersion = mustHaveVersion;
 }

} //eof class 

} // пространство имен

    
ответ дан YordanGeorgiev 01.05.2009 в 20:13