2009-04-15 13 views
17

Mit anderen Worten könnte es möglich sein, Assembly zu erstellen, die nicht einmal kompiliert (vorausgesetzt, der Prüfcode wird nicht entfernt) Die Klassen haben keine benutzerdefinierten Attribute (zum Beispiel "Autor" und "Version").Ist es möglich, benutzerdefinierte Attribute in C# während der Kompilierzeit abzufragen (keine Laufzeit)

Hier ist der Code, den ich während der Laufzeit verwendet haben, für die Abfrage: meine Wahl zu rechtfertigen, Just for Antwort:

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

//} 

bearbeiten. Ich denke, CasperOne lieferte die richtige Antwort auf die Frage.

Die Gründe für das Stellen der Frage schienen jedoch weak zu sein. Wahrscheinlich sollte ich beginnen, einige externe Tool zu verwenden, wie zum Beispiel: FinalBuilder oder Unit-Tests schaffen für diese „Anforderung“ Überprüfung, mit Pex, Nunit oder eine andere Einheit Test-Frameworks ...

EDIT Ich habe eine kleine code snippet eines Konsolenprogramms am Ende der Antworten, die die Prüfung durchführt ... fühlen Sie sich frei, zu kommentieren, zu kritisieren oder Verbesserungen vorzuschlagen
Noch einmal wurde mir klar, dass diese "Anforderung" als Teil der Unit-Tests kurz vor der Implementierung implementiert werden sollte "Einchecken"

Antwort

7

Nein, es ist nicht möglich, in t einzuhaken Er erstellt die Baugruppe und prüft, ob sie existiert.

Allerdings können Sie in den Build-Prozess, die aus mehr als nur den Compiler ausgeführt wird. Sie könnten eine benutzerdefinierte MSBUILD-Task (oder NAnt, wenn Sie diese verwenden) erstellen, die die Assembly durch Reflektion überprüft, nachdem sie erstellt wurde, und dann den Build fehlschlagen, wenn sie nicht die erforderlichen Attribute aufweist.

Natürlich sollten Sie wahrscheinlich immer noch überprüfen Sie dies auch in Code. Was Sie versuchen, ist kein guter Ersatz für eine ordnungsgemäße Laufzeitprüfung.

+0

Thaks für die Beantwortung! Was meinen Sie genauer mit "Was Sie zu tun versuchen, ist kein guter Ersatz für eine ordnungsgemäße Laufzeitprüfung."? –

+0

@YordanGeorgiev: Selbst wenn Sie einen Build-Prozess haben, der sicherstellt, dass das Attribut angewendet wird, sollten Sie noch immer Ihren Laufzeitcode einchecken, um sicherzustellen, dass das Attribut angewendet wird. Sie können nicht aufhören, dort nachzusehen, weil Sie denken, Sie hätten es zur Kompilierzeit abgefangen. – casperOne

+0

Also die Anforderung für mindestens 4 "Muss haben" Attribute wird die Leistung beeinflussen, wegen der Reflexion ... Scheint, die Durchführung der Prüfungen in Unit Tests wird als bessere Idee sein?! –

1

Attribute sind nur Laufzeit. Jedoch:

Es wäre möglich, eine Regel in FXCop (statische Analyse) zu erstellen, die fehlschlägt, wenn das Attribut nicht definiert ist, und Ihr Build/Checkin-Prozess könnte diese Regel überprüfen und entsprechend fehlschlagen.

+0

Nicht 100% richtig. "Obsolete" und "Conditional" sind beide Kompilierzeitattribute. –

1

Ich bin mir keiner Möglichkeit bewusst, in den C# -Kompilierungsprozess einzubinden, aber Sie können einen anderen Ansatz wählen und ein benutzerdefiniertes Tool erstellen, das beim Postbuildereignis gestartet wird und Ihre Baugruppe laden kann. Je nachdem, was das Tool zurückgibt, führt der gesamte Build-Prozess zu einem Erfolg oder einem Fehler. Daher können Sie einfach einen Fehler mit Ihrem Tool zurückgeben und den Build fehlschlagen lassen, während Sie weitere Details zum Fehler beim Schreiben in die Konsole bereitstellen.

+0

Ich habe einen kleinen Entwurf dieser Art eines Werkzeugs unter –

4

Sie können einen Post-Build-Schritt ausführen, der sich auf die DLL bezieht, um das zu tun, was Sie möchten.

Sie müssen eine Befehlszeilenanwendung schreiben, die die DLL lädt und die Typen reflektiert. Anschließend führen Sie diese Befehlszeilenanwendung als Post-Build-Schritt aus. Ich habe das in der Vergangenheit gemacht. Es ist nicht sehr schwierig, dies zu tun, vorausgesetzt, Sie verstehen die Reflektions-API.

PostSharp bewirkt dies eine aspektorientierte Programmierung. Ziemlich cool, eigentlich.

+0

Brian, danke hinzugefügt! Sah vielversprechend aus Ich werde es morgen überprüfen ... Hier in Finnland ist jetzt 10:43 Uhr. Wenn ich etwas Sinnvolles herausfinden werde ich den Code ... –

+0

Arggh ... riecht, dass es zu einer geschlossenen Lösung umgewandelt wird ... –

1

Für mich scheint dies eher ein Testproblem als ein Kompilierungsproblem. Das heißt, Sie fragen: "Woher weiß ich, dass mein Code richtig geschrieben ist?" wo "richtig geschrieben" hat (unter anderem) die Konnotation, dass alle Klassen mit einem bestimmten Attribut versehen sind. Ich würde erwägen, Komponententests zu schreiben, die bestätigen, dass die Regeln für die Aufnahme von Attributen tatsächlich eingehalten werden. Sie könnten Ihren Build (und/oder Check-in) Prozess diese bestimmte Reihe von Tests nach dem Build (vor dem Einchecken) als Bedingung für einen erfolgreichen Build (Einchecken) ausführen lassen. Es wird den Kompiliervorgang nicht unterbrechen, da dieser abgeschlossen werden muss, damit die Tests ausgeführt werden können, aber es wird sozusagen den Build unterbrechen.

+0

Meine Angst ist, dass Unit-Tests nicht immer 100% für den gesamten Build geeignet sind. Ich habe auch zu Unit-Tests in gewissem Maße zu betrügen gesehen ... Im Zusammenhang mit der Frage, ob das ganze denken würde nicht einmal kompilieren und die erzwungenen benutzerdefinierten Attribute sind minimal (zum Beispiel Autor, Version) –

+0

würde ich wahrscheinlich schreiben a Einzeleinheitstest pro Attribut. Es würde alle Klassen in der Assembly durchlaufen (oder Methoden in Klassen) und die Existenz des Attributs überprüfen. Sie müssten für jede Klasse/Methode keinen separaten Test schreiben. – tvanfosson

+0

Danke für die Antwort tvanfosson! Sie waren richtig, darauf hinzuweisen, dass diese Überprüfung als Teil der Einheit Testprozess durchgeführt werden sollte Ich fügte ein Code-Snippet eines Beispiels, wie es getan werden könnte (aber nicht als Teil der bestimmten Unit-Test, da man NUnit verwenden könnte , PEX oder was auch immer und ich kenne nicht alle Varianten ... Deine Antwort erinnert mich daran, dass man immer zuerst auf das große Bild schauen und später mit dem Codieren beginnen sollte ... –

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 

} // EOF Namespace