简体   繁体   English

是否可以在编译时(不是运行时)在 C# 中查询自定义属性

[英]Is it possible to query custom Attributes in C# during compile time ( not run-time )

In other words could it be possible to create assembly, which does not even compile (assuming the checking code is not removed ) if each one of the Classes does not have ( "must have" ) custom attributes ( for example Author and Version ) ?换句话说,如果每个类都没有(“必须有”)自定义属性(例如 Author 和 Version ),是否有可能创建程序集,它甚至不会编译(假设检查代码没有被删除)?

Here is the code I have used for querying during run time :这是我在运行时用于查询的代码:

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 { }

//}

Edit: Just to justify my choice for answer.编辑:只是为了证明我选择的答案是合理的。 I think casperOne provided the correct answer of the question.我认为 casperOne 提供了问题的正确答案。

However the reasons for asking the question seemed to be weak .不过问这个问题的理由似乎很薄弱 Probably I should start to use some external tool such as : FinalBuilder or create unit tests checking for this "requirement", using Pex , Nunit or other unit testing frameworks ...可能我应该开始使用一些外部工具,例如: FinalBuilder或创建单元测试来检查这个“要求”,使用 Pex、Nunit 或其他单元测试框架......

EDIT I added a small code snippet of a console program at the end of the answers that performs the check ... feel free to comment, criticize or suggest improvements编辑我在执行检查的答案的末尾添加了一个控制台程序的小代码片段......随时发表评论、批评或提出改进建议
Once more I realized that this "requirement" should be implemented as part of the unit testing just before the "check in"我再次意识到这个“要求”应该在“签入”之前作为单元测试的一部分来实现

No, it is not possible to hook into the compilation of the assembly and check if it exists.不,不可能挂钩程序集的编译并检查它是否存在。

However you can hook into the build process, which is made up of more than just running the compiler.但是,您可以连接到构建过程,它不仅仅由运行编译器组成。 You could create a custom MSBUILD task (or NAnt, if you are using that) which checks the assembly through reflection after it is built and then fail the build if it doesn't have the required attributes.您可以创建一个自定义 MSBUILD 任务(或 NAnt,如果您正在使用它),它会在构建后通过反射检查程序集,如果它没有所需的属性,则构建失败。

Of course, you should probably still verify this in code as well.当然,您可能仍然应该在代码中验证这一点。 What you are trying to do is not a good substitute for a proper runtime check.您尝试做的不是适当的运行时检查的良好替代品。

You can run a post-build step that reflects on the DLL to do what you want.您可以运行反映 DLL 的构建后步骤来执行您想要的操作。

You will have to write a command-line app that loads the DLL and reflects on the types.您必须编写一个命令行应用程序来加载 DLL 并反映类型。 You then run that command-line app as a post-build step.然后,您可以在构建后步骤中运行该命令行应用程序。 I have done this in the past.我过去曾这样做过。 It is not terribly difficult to do, assuming you understand the reflection API.假设您了解反射 API,这并不难。

PostSharp does this to achieve aspect oriented programming. PostSharp这样做是为了实现面向方面的编程。 Pretty cool, actually.实际上很酷。

Attributes are run time only.属性只是运行时。 However :然而 :

It would be possible to create a rule in FXCop (static analysis) that will fail if the attribute is not defined, and your build/checkin process could check that rule and fail appropriately.如果未定义属性,则可以在 FXCop(静态分析)中创建一个规则,该规则将失败,并且您的构建/签入过程可以检查该规则并适当地失败。

I'm not aware of any way to hook into the C# compilation process, but you may take a different approach and create a custom tool launched on the post build event which could load your assembly and reflects on that.我不知道有什么方法可以连接到 C# 编译过程,但是您可以采用不同的方法并创建一个在构建后事件上启动的自定义工具,该工具可以加载您的程序集并对其进行反映。 Depending on what the tool returns the whole build process will result in a success or a failure, so you may just return an error with your tool and make the build fail, while providing more details about the failure writing to console.根据工具返回的内容,整个构建过程将导致成功或失败,因此您可能只返回工具的错误并使构建失败,同时向控制台提供有关失败写入的更多详细信息。

To me this seems more like a testing problem than a compilation problem.对我来说,这更像是一个测试问题而不是编译问题。 That is, you're asking "how do I know that my code is written correctly?"也就是说,您要问“我怎么知道我的代码编写正确?” where "written correctly" has (among other things) the connotation that all classes are decorated with a particular attribute.其中“正确编写”具有(除其他外)所有类都装饰有特定属性的含义。 I would consider writing unit tests that verify that your attribute inclusion rules are, in fact, followed.我会考虑编写单元测试来验证您的属性包含规则实际上得到了遵守。 You could have your build (and/or checkin) process run this particular set of tests after the build (before the checkin) as a condition of a successful build (checkin).你可以让你的构建(和/或签入)过程在构建之后(签入之前)运行这组特定的测试,作为成功构建(签入)的条件。 It won't break the compile, since that needs to complete in order for the tests to run, but it will break the build, so to speak.它不会破坏编译,因为它需要完成才能运行测试,但它会破坏构建,可以这么说。

This is now fully possible using by writing a Roslyn analyzer.现在完全可以通过编写 Roslyn 分析器来使用。 You could use either the syntax tree or the semantic model.您可以使用语法树或语义模型。 (The latter is recommended, due to the complexities of how attribute names can be referenced, for example using using aliasing). (建议使用后者,因为如何引用属性名称的复杂性,例如using别名)。

//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 namespace } //eof 命名空间

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM