Avvisi del compilatore personalizzato


115

Quando si utilizza ObsoleteAtribute in .Net, vengono visualizzati avvisi del compilatore che indicano che l'oggetto / metodo / proprietà è obsoleto e che è necessario utilizzare qualcos'altro. Attualmente sto lavorando a un progetto che richiede molto refactoring di un codice ex dipendenti. Voglio scrivere un attributo personalizzato che posso utilizzare per contrassegnare metodi o proprietà che genereranno avvisi del compilatore che forniranno messaggi che scrivo. Qualcosa come questo

[MyAttribute("This code sux and should be looked at")]
public void DoEverything()
{
}
<MyAttribute("This code sux and should be looked at")>
Public Sub DoEverything()
End Sub

Voglio che questo generi un avviso del compilatore che dice: "Questo codice sux e dovrebbe essere guardato". So come creare un attributo personalizzato, la domanda è come faccio a generare avvisi del compilatore in Visual Studio.


È questo C #? Presumibilmente lo ricodificherò come C # (non C) presumendo che sia ciò che il poster originale intendeva scegliere.
Onorio Catenacci

13
Non è VB o C # valido ... quindi cos'è ...?!
ljs

5
Vecchia domanda, ma ora puoi definire avvisi del compilatore personalizzati utilizzando Roslyn.
RJ Cuthbertson

4
@jrummell In Roslyn parla, analizzatori di codici: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson

2
@RJCuthbertson Ho spostato il tuo commento nella risposta accettata per dargli l'attenzione che merita.
jpaugh

Risposte:


27

Aggiornare

Questo è ora possibile con Roslyn (Visual Studio 2015). Puoi creare un analizzatore di codice per verificare la presenza di un attributo personalizzato


Non credo sia possibile. ObsoleteAttribute viene trattato in modo speciale dal compilatore ed è definito nello standard C #. Perché diavolo è ObsoleteAttribute non accettabile? Mi sembra che questa sia esattamente la situazione per cui è stato progettato e raggiunge esattamente ciò di cui hai bisogno!

Si noti inoltre che Visual Studio raccoglie al volo anche gli avvisi generati da ObsoleteAttribute, il che è molto utile.

Non intendo essere inutile, chiedendomi solo perché non ti piace usarlo ...

Sfortunatamente ObsoleteAttribute è sigillato (probabilmente in parte a causa del trattamento speciale), quindi non puoi sottoclassare il tuo attributo da esso.

Dallo standard C #: -

L'attributo Obsoleto viene utilizzato per contrassegnare tipi e membri di tipi che non dovrebbero più essere utilizzati.

Se un programma utilizza un tipo o un membro decorato con l'attributo Obsoleto, il compilatore emette un avviso o un errore. In particolare, il compilatore emette un avviso se non viene fornito alcun parametro di errore o se viene fornito il parametro di errore e ha il valore false. Il compilatore genera un errore se il parametro error è specificato e ha il valore true.

Non riassume le tue esigenze? ... non farai meglio di così, non credo.


14
Sto cercando la stessa cosa. "Funziona" obsoleto, ma il codice non è tanto obsoleto quanto incompleto a causa del refactoring.
g.

11
Sono d'accordo con @g, e probabilmente l'autore originale. Obsoleto significa obsoleto, non usare. Voglio contrassegnare qualcosa come "hey questo compila ma abbiamo davvero bisogno di a) completare la funzionalità o b) refactoring". Sarebbe più un attributo del tempo di sviluppo. Anche le attività funzionano, ad esempio // TODO :, ma non le uso, poiché immagino che molte persone non lo facciano, ma rivedo regolarmente gli avvisi del compilatore.
MikeJansen

8
Un altro motivo per non utilizzare il [Obsolete]tag è che potrebbe causare problemi se è necessario eseguire XmlSerialization con la proprietà. L'aggiunta del [Obsolete]tag aggiunge anche l' [XmlIgnore]attributo dietro le quinte.
Burnttoast11

6
Obsoleto è diverso. Obsoleto ti darà un avviso su ogni riga di codice che chiama quel metodo. Non penso che sia quello che vuole il poster (almeno non è quello che voglio quando ho fatto una ricerca e ho trovato questa domanda). Ho pensato che quello che la domanda stava cercando fosse che un avvertimento comparisse sulla definizione della funzione, non mai dove è stata utilizzata.
Nick

Non è la risposta migliore. -1 per pensare che la tua incapacità di trovare un motivo per non usarlo meriti una critica. Questo atteggiamento scoraggia l'autenticità.
Mike Socha III

96

Vale la pena provare.

Non puoi estendere Obsoleto, perché è definitivo, ma forse puoi creare il tuo attributo e contrassegnare quella classe come obsoleta in questo modo:

[Obsolete("Should be refactored")]
public class MustRefactor: System.Attribute{}

Quindi, quando contrassegni i tuoi metodi con l'attributo "MustRefactor", verranno visualizzati gli avvisi di compilazione. Genera un avviso in fase di compilazione, ma il messaggio di errore sembra divertente, dovresti vederlo da solo e scegliere. Questo è molto vicino a ciò che volevi ottenere.

AGGIORNAMENTO: Con questo codice genera un avviso (non molto carino, ma non credo ci sia di meglio).

public class User
{
    private String userName;

    [TooManyArgs] // Will show warning: Try removing some arguments
    public User(String userName)
    {
        this.userName = userName;   
    }

    public String UserName
    {
        get { return userName; }
    }
    [MustRefactor] // will show warning: Refactor is needed Here
    public override string ToString()
    {
        return "User: " + userName;
    }
}
[Obsolete("Refactor is needed Here")]
public class MustRefactor : System.Attribute
{

}
[Obsolete("Try removing some arguments")]
public class TooManyArgs : System.Attribute
{

}

Puoi incollare ciò che genera? Sono curioso.
Michea

1
L'avviso di compilazione viene attivato anche se la proprietà / metodo non viene chiamato.
Rolf Kristensen

1
Buoni suggerimenti qui. Stavo cercando di fare la stessa cosa e ho finito per lanciare NotImplementedExceptions. Non è la soluzione migliore poiché non vengono visualizzati in fase di compilazione, ma solo in fase di esecuzione se il codice viene eseguito. Proverò io stesso.
MonkeyWrench

1
Non sarebbe fantastico se ObsolteAttribute potesse supportare espressioni come DebuggerDisplayAttribute, allora potremmo davvero fare cose interessanti. visualstudio.uservoice.com/forums/121579-visual-studio/…
jpierson

Se implementi IDisposablesu quelle classi obsolete, significa che puoi racchiudere il tuo codice di test ingannevole in un usingblocco. Come questo: using(new MustRefactor()){DodgyCode();}. Quindi puoi trovare tutti gli usi quando hai finito. Lo sto usando in questo momento per Sleepil thread all'interno di un ciclo for che devo rallentare artificialmente per scopi di debug.
Iain Fraser

48

In alcuni compilatori puoi usare #warning per emettere un avviso:

#warning "Do not use ABC, which is deprecated. Use XYZ instead."

Nei compilatori Microsoft, in genere è possibile utilizzare il pragma del messaggio:

#pragma message ( "text" )

Hai menzionato .Net, ma non hai specificato se stavi programmando con C / C ++ o C #. Se stai programmando in C #, dovresti sapere che C # supporta il formato #warning .


1
#warning o #pragma sono direttive del pre-processore e quindi verranno eseguite indipendentemente dalla presenza di qualsiasi codice di ex colleghi di Michea e non interagiscono affatto con l'attributo. Abbastanza certo Obsoleto è l'unico mezzo per raggiungere questo obiettivo ...
ljs

39

Siamo attualmente nel bel mezzo di un sacco di refactoring in cui non siamo riusciti a riparare tutto immediatamente. Usiamo solo il comando #warning preproc in cui dobbiamo tornare indietro e guardare il codice. Viene visualizzato nell'output del compilatore. Non penso che tu possa metterlo su un metodo, ma potresti inserirlo solo all'interno del metodo, ed è ancora facile da trovare.

public void DoEverything() {
   #warning "This code sucks"
}

7

In VS 2008 (+ sp1) gli #avvisi non vengono visualizzati correttamente nell'elenco degli errori dopo Clean Soultion & Rebuild Solution, no tutti. Alcuni avvisi vengono visualizzati nell'elenco degli errori solo dopo aver aperto un particolare file di classe. Quindi sono stato costretto a utilizzare l'attributo personalizzato:

[Obsolete("Mapping ToDo")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class MappingToDo : System.Attribute
{
    public string Comment = "";

    public MappingToDo(string comment)
    {
        Comment = comment;
    }

    public MappingToDo()
    {}
}

Quindi, quando contrassegno un codice con esso

[MappingToDo("Some comment")]
public class MembershipHour : Entity
{
    // .....
}

Produce avvisi come questo:

Namespace.MappingToDo è obsoleto: "Mapping ToDo".

Non riesco a modificare il testo dell'avviso, "Alcuni commenti" non viene visualizzato Elenco errori. Ma salterà nella posizione corretta nel file. Quindi, se è necessario variare tali messaggi di avviso, creare vari attributi.


6

Quello che stai cercando di fare è un uso improprio degli attributi. Utilizzare invece l'elenco delle attività di Visual Studio. Puoi inserire un commento nel tuo codice in questo modo:

//TODO:  This code sux and should be looked at
public class SuckyClass(){
  //TODO:  Do something really sucky here!
}

Quindi apri Visualizza / Elenco attività dal menu. L'elenco delle attività ha due categorie, attività utente e commenti. Passa a Commenti e vedrai tutte le tue // Cose da fare: lì. Facendo doppio clic su un TODO si salterà al commento nel codice.

Al


1
Trovo che questa sia una soluzione più preferibile
Samuel

1
cosa succede se si desidera contrassegnare una funzione come "Non essere chiamata nel codice di produzione" o simile. Quindi vuoi che si attivi se una funzione o una classe viene chiamata o istanziata, ma non se è solo compilata.
Jesse Pepper

2

Non credo che tu possa. Per quanto ne so il supporto per ObsoleteAttribute è essenzialmente hardcoded nel compilatore C #; non puoi fare nulla di simile direttamente.

Quello che potresti essere in grado di fare è usare un'attività di MSBuild (o un evento di post-compilazione) che esegue uno strumento personalizzato sull'assembly appena compilato. Lo strumento personalizzato si rifletterebbe su tutti i tipi / metodi nell'assembly e consumerebbe l'attributo personalizzato, a quel punto potrebbe stampare su TextWriters predefinito o di errore di System.Console.


2

Guardando il sorgente per ObsoleteAttribute , non sembra che stia facendo qualcosa di speciale per generare un avviso del compilatore, quindi tenderei ad andare con @ technophile e dire che è hard-coded nel compilatore. C'è un motivo per cui non vuoi usare ObsoleteAttribute per generare i tuoi messaggi di avviso?


Nessun motivo particolare diverso dal codice non è necessariamente obsoleto.
Michea

1
È specificato nella specifica C # come trattato in modo speciale dal compilatore, controlla la mia risposta :-). Michea - "L'attributo Obsoleto viene utilizzato per contrassegnare tipi e membri di tipi che non dovrebbero più essere utilizzati." dalla specifica. Non è applicabile? ...
ljs

Solo se qualcuno si chiedesse, non esiste nemmeno un codice C # nel codice sorgente per farlo. Referencesource.microsoft.com/#mscorlib/system/…
Paweł Mach

1

Ci sono diversi commenti che suggeriscono di inserire avvertimenti o pragma. Obsolete funziona in un modo molto diverso! Contrassegnando come obsoleta una funzione di una libreria L, il messaggio obsoleto viene generato quando un programma chiama la funzione anche se il programma chiamante non è nella libreria L. Warning solleva il messaggio SOLO quando L viene compilato.


1

Ecco l'implementazione di Roslyn, quindi puoi creare i tuoi attributi che forniscono avvisi o errori al volo.

Ho creato un attributo Type Called IdeMessageche sarà l'attributo che genera avvisi:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class IDEMessageAttribute : Attribute
{
    public string Message;

    public IDEMessageAttribute(string message);
}

Per fare ciò, è necessario installare prima Roslyn SDK e avviare un nuovo progetto VSIX con l'analizzatore. Ho omesso alcuni dei pezzi meno rilevanti come i messaggi, puoi capire come farlo. Nel tuo analizzatore fai questo

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzerInvocation, SyntaxKind.InvocationExpression);
}

private static void AnalyzerInvocation(SyntaxNodeAnalysisContext context)
{
    var invocation = (InvocationExpressionSyntax)context.Node;

    var methodDeclaration = (context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken).Symbol as IMethodSymbol);

    //There are several reason why this may be null e.g invoking a delegate
    if (null == methodDeclaration)
    {
        return;
    }

    var methodAttributes = methodDeclaration.GetAttributes();
    var attributeData = methodAttributes.FirstOrDefault(attr => IsIDEMessageAttribute(context.SemanticModel, attr, typeof(IDEMessageAttribute)));
    if(null == attributeData)
    {
        return;
    }

    var message = GetMessage(attributeData); 
    var diagnostic = Diagnostic.Create(Rule, invocation.GetLocation(), methodDeclaration.Name, message);
    context.ReportDiagnostic(diagnostic);
}

static bool IsIDEMessageAttribute(SemanticModel semanticModel, AttributeData attribute, Type desiredAttributeType)
{
    var desiredTypeNamedSymbol = semanticModel.Compilation.GetTypeByMetadataName(desiredAttributeType.FullName);

    var result = attribute.AttributeClass.Equals(desiredTypeNamedSymbol);
    return result;
}

static string GetMessage(AttributeData attribute)
{
    if (attribute.ConstructorArguments.Length < 1)
    {
        return "This method is obsolete";
    }

    return (attribute.ConstructorArguments[0].Value as string);
}

Non ci sono CodeFixProvider per questo, puoi rimuoverlo dalla soluzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.