Niestandardowe ostrzeżenia kompilatora

115

Kiedy używasz ObsoleteAtribute w .Net daje ci ostrzeżenia kompilatora, informujące cię, że obiekt / metoda / właściwość jest przestarzała i należy użyć czegoś innego. Obecnie pracuję nad projektem, który wymaga wielu zmian w kodzie byłych pracowników. Chcę napisać atrybut niestandardowy, którego mogę użyć do oznaczania metod lub właściwości, które będą generować ostrzeżenia kompilatora, które będą wyświetlać wiadomości, które piszę. Coś takiego

[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

Chcę, aby to wygenerowało ostrzeżenie kompilatora, które mówi: „Ten kod sux i należy mu się przyjrzeć”. Wiem, jak utworzyć atrybut niestandardowy, pytanie brzmi, jak spowodować, aby generował ostrzeżenia kompilatora w Visual Studio.

Micheasza
źródło
Czy to jest C #? Zamierzam przypuszczalnie ponownie oznaczyć to jako C # (nie C), zakładając, że to właśnie chciał wybrać oryginalny plakat.
Onorio Catenacci,
13
To nie jest poprawne VB lub C # ... więc co to jest ...?!
ljs
5
Stare pytanie, ale teraz możesz zdefiniować niestandardowe ostrzeżenia kompilatora przy użyciu Roslyn.
RJ Cuthbertson
4
@jrummell In Roslyn speak, code analysers: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson
2
@RJCuthbertson Przeniosłem Twój komentarz do zaakceptowanej odpowiedzi, aby poświęcić mu uwagę, na jaką zasługuje.
jpaugh

Odpowiedzi:

27

Aktualizacja

Jest to teraz możliwe dzięki Roslyn (Visual Studio 2015). Można budować na analizator kodu do sprawdzenia atrybutu niestandardowego


Nie wierzę, że to możliwe. ObsoleteAttribute jest traktowany specjalnie przez kompilator i jest zdefiniowany w standardzie C #. Dlaczego do diabła nie można zaakceptować ObsoleteAttribute? Wydaje mi się, że jest to właśnie sytuacja, do której został zaprojektowany, i zapewnia dokładnie to, czego potrzebujesz!

Należy również pamiętać, że program Visual Studio odbiera ostrzeżenia generowane przez ObsoleteAttribute również w locie, co jest bardzo przydatne.

Nie chcę być pomocny, po prostu zastanawiasz się, dlaczego nie chcesz go używać ...

Niestety ObsoleteAttribute jest zapieczętowany (prawdopodobnie częściowo z powodu specjalnego traktowania), dlatego nie możesz podklasować swojego własnego atrybutu z tego.

Ze standardu C #: -

Atrybut Przestarzałe służy do oznaczania typów i elementów członkowskich typów, które nie powinny już być używane.

Jeśli program używa typu lub elementu członkowskiego, który jest ozdobiony atrybutem przestarzałym, kompilator wyświetla ostrzeżenie lub błąd. W szczególności kompilator generuje ostrzeżenie, jeśli nie podano parametru błędu lub jeśli podano parametr błędu i ma wartość false. Kompilator zgłasza błąd, jeśli parametr błędu jest określony i ma wartość true.

Czy to nie podsumowuje twoich potrzeb? ... nie zamierzasz zrobić nic lepszego niż to, nie sądzę.

ljs
źródło
14
Szukam tego samego. Przestarzały „działa”, ale kod nie jest tak naprawdę przestarzały, a raczej niekompletny z powodu refaktoryzacji.
g.
11
Zgadzam się z @g i prawdopodobnie pierwotnym autorem. Przestarzały oznacza przestarzały, nie używaj. Chcę oznaczyć coś jako „hej, to kompiluje, ale naprawdę musimy albo a) dokończyć funkcjonalność, albo b) refaktoryzować”. Byłby to raczej atrybut czasu rozwoju. Działają również zadania, np. // TODO :, ale ja ich nie używam, jak domyślam się, że wiele osób nie, ale regularnie przeglądam ostrzeżenia kompilatora.
MikeJansen
8
Innym powodem, dla którego nie należy używać [Obsolete]tagu, jest to, że może powodować problemy, jeśli trzeba wykonać XmlSerialization z właściwością. Dodanie [Obsolete]tagu dodaje również [XmlIgnore]atrybut za kulisami.
spalony toast
6
Przestarzałe jest inne. Przestarzałe wyświetli ostrzeżenie o każdym wierszu kodu, który wywołuje tę metodę. Nie sądzę, że tego chce plakat (przynajmniej nie tego chcę, kiedy szukałem i znalazłem to pytanie). Pomyślałem, że to pytanie dotyczyło ostrzeżenia, które pojawiło się przy definicji funkcji, a nie miejsca, w którym jest używana.
Nick
Nie najlepsza odpowiedź. -1 za myślenie, że niezdolność do wymyślenia powodu jej nieużywania zasługuje na krytykę. Taka postawa zniechęca do autentyczności.
Mike Socha III
96

Warto spróbować.

Nie możesz rozszerzyć Obsolete, ponieważ jest ostateczny, ale może możesz stworzyć własny atrybut i oznaczyć tę klasę jako przestarzałą w następujący sposób:

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

Gdy oznaczysz metody atrybutem „MustRefactor”, pojawią się ostrzeżenia dotyczące kompilacji. Generuje ostrzeżenie o czasie kompilacji, ale komunikat o błędzie wygląda śmiesznie, powinieneś to zobaczyć i wybrać. To jest bardzo bliskie temu, co chciałeś osiągnąć.

UPDATE: Za pomocą tego kodu generuje ostrzeżenie (niezbyt ładne, ale nie sądzę, aby było coś lepszego).

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
{

}
Pablo Fernandez
źródło
Czy możesz wkleić to, co generuje? Jestem ciekawy.
Micah
1
Ostrzeżenie kompilacji jest wyzwalane, nawet jeśli właściwość / metoda nie jest wywoływana.
Rolf Kristensen
1
Dobre sugestie tutaj. Chciałem zrobić to samo, ale skończyło się na tym, że wyrzuciłem NotImplementedExceptions. Nie jest to najlepsze rozwiązanie, ponieważ nie pojawiają się w czasie kompilacji, tylko w czasie wykonywania, jeśli kod zostanie wykonany. Sam spróbuję.
MonkeyWrench,
1
Czy nie byłoby wspaniale, gdyby ObsolteAttribute mógł obsługiwać wyrażenia, tak jak DebuggerDisplayAttribute, wtedy moglibyśmy zrobić naprawdę fajne rzeczy. visualstudio.uservoice.com/forums/121579-visual-studio/ ...
jpierson
Jeśli zaimplementujesz IDisposablete przestarzałe klasy, oznacza to, że możesz opakować swój podejrzany kod testowy w usingblok. Tak: using(new MustRefactor()){DodgyCode();}. Następnie możesz znaleźć wszystkie zastosowania, kiedy skończysz. Używam tego teraz do Sleepwątku wewnątrz pętli for, którą muszę sztucznie spowolnić w celu debugowania.
Iain Fraser
48

W niektórych kompilatorach możesz użyć #warning, aby wygenerować ostrzeżenie:

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

W kompilatorach firmy Microsoft zazwyczaj można użyć pragmy komunikatu:

#pragma message ( "text" )

Wspomniałeś o .Net, ale nie sprecyzowałeś, czy programujesz w C / C ++ czy C #. Jeśli programujesz w C #, powinieneś wiedzieć, że C # obsługuje format #warning .

Douglas Mayle
źródło
1
#warning lub #pragma są dyrektywami preprocesora i dlatego będą działać niezależnie od obecności jakiegokolwiek kodu byłych kolegów Micaha i w ogóle nie wchodzą w interakcje z atrybutem. Całkiem pewne Przestarzałe to jedyny sposób na osiągnięcie tego ...
ljs
39

Obecnie jesteśmy w trakcie wielu refaktoryzacji, podczas których nie mogliśmy od razu wszystkiego naprawić. Po prostu używamy polecenia #warning preproc, w którym musimy cofnąć się i spojrzeć na kod. Pojawia się w danych wyjściowych kompilatora. Nie sądzę, aby można było przypisać to do metody, ale można by było umieścić je tylko wewnątrz metody, a nadal łatwo jest znaleźć.

public void DoEverything() {
   #warning "This code sucks"
}
Ted Elliott
źródło
7

W VS 2008 (+ sp1) # ostrzeżenia nie wyświetlają się poprawnie na liście błędów po Clean Soultion & Rebuild Solution, nie wszystkie z nich. Niektóre ostrzeżenia są wyświetlane na liście błędów dopiero po otwarciu określonego pliku klasy. Więc byłem zmuszony użyć atrybutu niestandardowego:

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

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

    public MappingToDo()
    {}
}

Więc kiedy oflaguję nim jakiś kod

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

Generuje takie ostrzeżenia:

Namespace.MappingToDo jest przestarzałe: „Mapping ToDo”.

Nie mogę zmienić treści ostrzeżenia, nie wyświetla się „Jakiś komentarz”. Lista błędów. Ale przeskoczy na właściwe miejsce w pliku. Jeśli więc chcesz zmieniać takie komunikaty ostrzegawcze, utwórz różne atrybuty.

Tomasz Modelski
źródło
6

To, co próbujesz zrobić, jest nadużyciem atrybutów. Zamiast tego użyj listy zadań programu Visual Studio. Możesz wpisać komentarz do swojego kodu w następujący sposób:

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

Następnie otwórz Widok / Lista zadań z menu. Lista zadań ma dwie kategorie, zadania użytkownika i komentarze. Przełącz się na Komentarze, a zobaczysz wszystkie swoje // Todo: 's. Dwukrotne kliknięcie TODO spowoduje przejście do komentarza w kodzie.

Glin

user4089256
źródło
1
uważam to za bardziej korzystne rozwiązanie
Samuel
1
co, jeśli chcesz oznaczyć funkcję jako „Nie należy wywoływać w kodzie produkcyjnym” lub podobnie. Więc chcesz, aby był uruchamiany, jeśli funkcja lub klasa zostanie wywołana lub utworzona, ale nie jeśli zostanie właśnie skompilowana.
Jesse Pepper,
2

Myślę, że nie możesz. O ile wiem, obsługa ObsoleteAttribute jest zasadniczo zakodowana na stałe w kompilatorze C #; nie możesz zrobić niczego podobnego bezpośrednio.

To, co możesz zrobić, to użyć zadania MSBuild (lub zdarzenia po kompilacji), które wykonuje niestandardowe narzędzie dla właśnie skompilowanego zestawu. Narzędzie niestandardowe będzie odzwierciedlać wszystkie typy / metody w zestawie i zużywa niestandardowy atrybut, w którym to momencie może drukować do domyślnego lub błędu TextWriters System.Console.

technofil
źródło
2

Patrząc na źródło ObsoleteAttribute , nie wygląda na to, że robi coś specjalnego, aby wygenerować ostrzeżenie kompilatora, więc chciałbym iść z @ technophile i powiedzieć, że jest na stałe zakodowany w kompilatorze. Czy jest jakiś powód, dla którego nie chcesz używać ObsoleteAttribute do generowania komunikatów ostrzegawczych?

bdukes
źródło
Żaden konkretny powód, poza kodem, niekoniecznie jest przestarzały.
Micah
1
Jest określony w specyfikacji C # jako traktowany specjalnie przez kompilator, sprawdź moją odpowiedź :-). Micah - „Atrybut przestarzały służy do oznaczania typów i elementów składowych typów, których nie należy już używać”. ze specyfikacji. Czy to nie dotyczy? ...
ljs
Gdyby ktoś się zastanawiał, w kodzie źródłowym nie ma też kodu C #, który mógłby to zrobić. referenceource.microsoft.com/#mscorlib/system/…
Paweł Mach
1

Istnieje kilka komentarzy, które sugerują wstawienie ostrzeżeń lub pragmatów. Przestarzałe działa w zupełnie inny sposób! Oznaczając przestarzałą funkcję biblioteki L, przestarzały komunikat pojawia się, gdy program wywołuje funkcję, nawet jeśli programu wywołującego nie ma w bibliotece L. Ostrzeżenie powoduje wyświetlenie komunikatu TYLKO, gdy L jest kompilowane.

bubi
źródło
1

Oto implementacja Roslyn, dzięki czemu możesz tworzyć własne atrybuty, które w locie wyświetlają ostrzeżenia lub błędy.

Utworzyłem atrybut Type Called, IdeMessagektóry będzie atrybutem generującym ostrzeżenia:

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

    public IDEMessageAttribute(string message);
}

Aby to zrobić, musisz najpierw zainstalować Roslyn SDK i uruchomić nowy projekt VSIX z analizatorem. Pominąłem niektóre mniej istotne fragmenty, takie jak wiadomości, możesz dowiedzieć się, jak to zrobić. W swoim analizatorze robisz to

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);
}

W tym przypadku nie ma CodeFixProvider, możesz go usunąć z rozwiązania.

johnny 5
źródło