【问题标题】:Is it possible to query custom Attributes in C# during compile time ( not run-time )是否可以在编译时(不是运行时)在 C# 中查询自定义属性
【发布时间】:2010-10-19 16:33:28
【问题描述】:

换句话说,如果每个类都没有(“必须”)自定义属性(例如 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 提供了该问题的正确答案。

但是问这个问题的原因似乎是weak。可能我应该开始使用一些外部工具,例如: FinalBuilder 或使用 Pex 、 Nunit 或其他单元测试框架创建单元测试检查此“要求”...

编辑 我在执行检查的答案末尾添加了一个控制台程序的小code snippet ...随时评论、批评或提出改进建议
我再次意识到,这个“要求”应该在“签入”之前作为单元测试的一部分来实现

【问题讨论】:

    标签: c# custom-attributes


    【解决方案1】:

    这现在完全可以通过编写 Roslyn 分析器来使用。您可以使用语法树或语义模型。 (推荐使用后者,因为引用属性名称的方式很复杂,例如使用 using 别名)。

    【讨论】:

      【解决方案2】:
      //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 
      

      } //eof 命名空间

      【讨论】:

        【解决方案3】:

        对我来说,这似乎更像是一个测试问题而不是编译问题。也就是说,您要问“我怎么知道我的代码编写正确?”其中“正确编写”具有(除其他外)所有类都用特定属性装饰的内涵。我会考虑编写单元测试来验证您的属性包含规则实际上是否得到遵守。您可以让您的构建(和/或签入)过程在构建之后(签入之前)运行这组特定的测试,作为成功构建(签入)的条件。它不会破坏编译,因为它需要完成才能运行测试,但它会破坏构建,可以这么说。

        【讨论】:

        • 我担心单元测试并不总是 100% 适合整个构建。我也看到在某种程度上在单元测试上作弊......在问题的上下文中,如果整个认为甚至不会编译并且强制自定义属性是最小的(例如 Author , Version )
        • 我可能会为每个属性编写一个单元测试。它将遍历程序集中的所有类(或类中的方法)并验证属性的存在。您不必为每个类/方法编写单独的测试。
        • 感谢 tvanfosson 的回答!您正确指出应该作为单元测试过程的一部分执行此检查, PEX 或其他任何东西,我不知道所有的变化......你的回答提醒我,一个人应该总是先看大局,然后再开始编码......
        【解决方案4】:

        您可以运行反映在 DLL 上的构建后步骤来执行您想要的操作。

        您必须编写一个命令行应用程序来加载 DLL 并反映类型。然后,您将该命令行应用程序作为构建后步骤运行。我过去曾这样做过。假设您了解反射 API,这并不难做到。

        PostSharp 这样做是为了实现面向方面的编程。其实很酷。

        【讨论】:

        • 布赖恩,谢谢!看起来很有希望我明天会检查它......芬兰现在是晚上 10:43。如果我想出一些有意义的东西,我会发布代码......
        【解决方案5】:

        我不知道有什么方法可以挂接到 C# 编译过程中,但您可以采用不同的方法并创建一个在构建后事件上启动的自定义工具,该工具可以加载您的程序集并对此进行反映。根据工具返回的内容,整个构建过程将导致成功或失败,因此您可能只是使用工具返回错误并导致构建失败,同时提供有关写入控制台的失败的更多详细信息。

        【讨论】:

        • 我在下面添加了一个此类工具的小草稿
        【解决方案6】:

        属性仅在运行时。但是:

        可以在 FXCop(静态分析)中创建一个规则,如果未定义该属性,该规则将失败,并且您的构建/签入过程可以检查该规则并相应地失败。

        【讨论】:

        • 不是 100% 正确的。 “过时”和“条件”都是编译时属性。
        【解决方案7】:

        不,不可能挂钩到程序集的编译并检查它是否存在。

        但是您可以挂钩到构建过程,该过程不仅仅是运行编译器。您可以创建一个自定义 MSBUILD 任务(或 NAnt,如果您正在使用它),该任务在构建后通过反射检查程序集,如果它没有所需的属性,则构建失败。

        当然,您可能仍然也应该在代码中验证这一点。您正在尝试做的事情并不能很好地替代正确的运行时检查。

        【讨论】:

        • 感谢您的回答!更具体地说,“您尝试做的事情不能很好地替代适当的运行时检查。”是什么意思?
        • @YordanGeorgiev:即使您有确保应用属性的构建过程,您仍应检查运行时代码以确保应用该属性。你不能停止在那里检查,因为你认为你在编译时发现了它。
        • 因此要求至少获得 4 个“必须具有”属性会影响性能,因为反射......似乎在单元测试中执行检查会比更好的主意?!
        • 令我困扰的是,MSBUILD 并不适用于 Web 项目......而且 NAnt 似乎对于这样一个“简单”的要求来说太重了(或者可能是“简单”是由于我的经验不足。 ..)
        • @casperOne:我仍然不明白您为什么认为需要运行时检查,至少在您的程序集具有强名称的情况下。
        猜你喜欢
        • 1970-01-01
        • 2016-12-25
        • 2013-05-26
        • 2020-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多