Advertencias del compilador personalizado

115

Cuando usa ObsoleteAtribute en .Net, le da advertencias del compilador que le dicen que el objeto / método / propiedad es obsoleto y que se debe usar algo más. Actualmente estoy trabajando en un proyecto que requiere mucha refactorización de un código de ex empleados. Quiero escribir un atributo personalizado que pueda usar para marcar métodos o propiedades que generarán advertencias del compilador que dan los mensajes que escribo. Algo como esto

[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

Quiero que esto genere una advertencia del compilador que diga, "Este código sux y debería ser revisado". Sé cómo crear un atributo personalizado, la pregunta es cómo puedo hacer que genere advertencias del compilador en Visual Studio.

Miqueas
fuente
¿Es esto C #? Presuntamente voy a volver a etiquetar esto como C # (no C) con la presunción de que eso es lo que el póster original quería elegir.
Onorio Catenacci
13
Eso no es VB o C # válido ... entonces, ¿qué es ...?!
ljs
5
Pregunta anterior, pero ahora puede definir advertencias de compilador personalizadas usando Roslyn.
RJ Cuthbertson
4
@jrummell En Roslyn habla, analizadores de código: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson
2
@RJCuthbertson Moví su comentario a la respuesta aceptada para darle la atención que merece.
jpaugh

Respuestas:

27

Actualizar

Esto ahora es posible con Roslyn (Visual Studio 2015). Puede crear un analizador de código para verificar un atributo personalizado


No creo que sea posible. ObsoleteAttribute es tratado especialmente por el compilador y está definido en el estándar C #. ¿Por qué diablos no es aceptable ObsoleteAttribute? Me parece que esta es precisamente la situación para la que fue diseñada, ¡y logra exactamente lo que necesita!

También tenga en cuenta que Visual Studio también recoge las advertencias generadas por ObsoleteAttribute sobre la marcha, lo cual es muy útil.

No quiero ser inútil, solo me pregunto por qué no está interesado en usarlo ...

Desafortunadamente, ObsoleteAttribute está sellado (probablemente en parte debido al tratamiento especial), por lo tanto, no puede subclasificar su propio atributo.

Desde el estándar C #: -

El atributo Obsoleto se usa para marcar tipos y miembros de tipos que ya no se deben usar.

Si un programa utiliza un tipo o miembro que está decorado con el atributo Obsolete, el compilador emite una advertencia o un error. Específicamente, el compilador emite una advertencia si no se proporciona ningún parámetro de error o si se proporciona el parámetro de error y tiene el valor falso. El compilador emite un error si se especifica el parámetro de error y tiene el valor verdadero.

¿No resume eso tus necesidades? ... No creo que te vaya a ir mejor que eso.

ljs
fuente
14
Busco lo mismo. Obsoleto 'funciona' pero el código no es realmente obsoleto sino incompleto debido a la refactorización.
g.
11
Estoy de acuerdo con @g, y probablemente con el autor original. Obsoleto significa obsoleto, no lo use. Quiero marcar algo como "oye, esto se compila pero realmente necesitamos a) completar la funcionalidad ob) refactorizar". Sería más un atributo de tiempo de desarrollo. También funcionan las tareas, por ejemplo, // TODO :, pero yo no las uso, como supongo que mucha gente no lo hace, pero revisa las advertencias del compilador con regularidad.
MikeJansen
8
Otra razón para no usar la [Obsolete]etiqueta es que podría causar problemas si necesita hacer XmlSerialization con la propiedad. Agregar la [Obsolete]etiqueta también agrega [XmlIgnore]atributos detrás de escena.
burnttoast11
6
Lo obsoleto es diferente. Obsolete le dará una advertencia en cada línea de código que llame a ese método. No creo que eso sea lo que quiere el cartel (al menos eso no es lo que quiero cuando hice una búsqueda y encontré esta pregunta). Pensé que lo que buscaba la pregunta era que apareciera una advertencia en la definición de la función, nunca en el lugar donde se está utilizando.
Nick
No es la mejor respuesta. -1 por pensar que tu incapacidad para encontrar una razón para no usarlo merece una crítica. Esta actitud desalienta la autenticidad.
Mike Socha III
96

Vale la pena intentarlo.

No puede extender Obsolete, porque es final, pero tal vez pueda crear su propio atributo y marcar esa clase como obsoleta de esta manera:

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

Luego, cuando marque sus métodos con el atributo "MustRefactor", se mostrarán las advertencias de compilación. Genera una advertencia de tiempo de compilación, pero el mensaje de error parece extraño, debería verlo usted mismo y elegir. Esto está muy cerca de lo que quería lograr.

ACTUALIZACIÓN: Con este código genera una advertencia (no muy agradable, pero no creo que haya algo mejor).

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
fuente
¿Puedes pegar lo que genera? Soy curioso.
Micah
1
La advertencia de compilación se activa incluso si no se llama a la propiedad / método.
Rolf Kristensen
1
Buenas sugerencias aquí. Estaba buscando hacer lo mismo y terminé lanzando NotImplementedExceptions. No es la mejor solución ya que no aparecen en tiempo de compilación, solo en tiempo de ejecución si el código se ejecuta. Yo mismo lo intentaré.
MonkeyWrench
1
¿No sería genial si ObsolteAttribute pudiera admitir expresiones como DebuggerDisplayAttribute, entonces realmente podríamos hacer algunas cosas interesantes? visualstudio.uservoice.com/forums/121579-visual-studio/...
jpierson
Si implementa IDisposableen esas clases obsoletas, significa que puede envolver su código de prueba poco fiable en un usingbloque. De esta manera: using(new MustRefactor()){DodgyCode();}. Luego, puede encontrar todos los usos cuando haya terminado. Estoy usando esto ahora mismo para Sleepel hilo dentro de un bucle for que necesito ralentizar artificialmente para fines de depuración.
Iain Fraser
48

En algunos compiladores puede usar #warning para emitir una advertencia:

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

En los compiladores de Microsoft, normalmente puede utilizar el pragma de mensajes:

#pragma message ( "text" )

Mencionó .Net, pero no especificó si estaba programando con C / C ++ o C #. Si está programando en C #, debe saber que C # admite el formato de advertencia .

Douglas Mayle
fuente
1
#warning o #pragma son directivas de preprocesador y, por lo tanto, se ejecutarán independientemente de la presencia de cualquier código de ex colegas de micah y no interactúa con el atributo en absoluto. Bastante seguro Obsolete es el único medio de lograr esto ...
ljs
39

Actualmente estamos en medio de una gran refactorización en la que no pudimos arreglar todo de inmediato. Solo usamos el comando #warning preproc donde tenemos que volver y mirar el código. Aparece en la salida del compilador. No creo que puedas ponerlo en un método, pero podrías ponerlo dentro del método y aún es fácil de encontrar.

public void DoEverything() {
   #warning "This code sucks"
}
Ted Elliott
fuente
7

En VS 2008 (+ sp1), las advertencias no se muestran correctamente en la Lista de errores después de Clean Soultion & Rebuild Solution, no todas. Algunas advertencias se muestran en la Lista de errores solo después de que abro un archivo de clase en particular. Entonces me vi obligado a usar un atributo personalizado:

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

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

    public MappingToDo()
    {}
}

Entonces, cuando señalo un código con él

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

Produce advertencias como esta:

Namespace.MappingToDo está obsoleto: 'Mapping ToDo'.

No puedo cambiar el texto de la advertencia, "Algún comentario" no se muestra. Lista de errores. Pero saltará al lugar adecuado en el archivo. Entonces, si necesita variar dichos mensajes de advertencia, cree varios atributos.

Tomasz Modelski
fuente
6

Lo que está intentando hacer es un mal uso de atributos. En su lugar, utilice la lista de tareas de Visual Studio. Puede ingresar un comentario en su código como este:

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

Luego abra Ver / Lista de tareas desde el menú. La lista de tareas tiene dos categorías, tareas de usuario y comentarios. Cambie a Comentarios y verá todos sus // Todo: allí. Al hacer doble clic en un TODO, saltará al comentario en su código.

Alabama

user4089256
fuente
1
Encuentro que esta es una solución más preferible
Samuel
1
¿Qué sucede si desea marcar una función como "No se llamará en el código de producción" o similar? Por lo tanto, desea que se active si se llama o instancia una función o clase, pero no si se acaba de compilar.
Jesse Pepper
2

No creo que puedas. Hasta donde yo sé, el soporte para ObsoleteAttribute está esencialmente codificado en el compilador de C #; no puedes hacer nada similar directamente.

Lo que podría hacer es usar una tarea de MSBuild (o un evento posterior a la compilación) que ejecute una herramienta personalizada en el ensamblado recién compilado. La herramienta personalizada se reflejaría en todos los tipos / métodos en el ensamblaje y consumiría su atributo personalizado, en cuyo punto podría imprimir en los TextWriters predeterminados o de error de System.Console.

tecnófilo
fuente
2

Mirando la fuente de ObsoleteAttribute , no parece que esté haciendo nada especial para generar una advertencia del compilador, por lo que tendería a ir con @ technophile y decir que está codificado en el compilador. ¿Hay alguna razón por la que no desee usar ObsoleteAttribute para generar sus mensajes de advertencia?

bdukes
fuente
Ninguna otra razón en particular que no sea el código no es necesariamente obsoleta.
Micah
1
Está especificado en la especificación C # como tratado especialmente por el compilador, mira mi respuesta :-). Micah - 'El atributo Obsolete se usa para marcar tipos y miembros de tipos que ya no deben usarse'. de la especificación. ¿No es aplicable? ...
ljs
Solo si alguien se pregunta, tampoco hay código C # en el código fuente para hacer esto. referenciasource.microsoft.com/#mscorlib/system/…
Paweł Mach
1

Hay varios comentarios que sugieren insertar advertencias o pragma. ¡Obsolete funciona de una manera muy diferente! Marcando como obsoleta una función de una biblioteca L, el mensaje obsoleto aparece cuando un programa llama a la función incluso si el programa que llama no está en la biblioteca L. Advertencia genera el mensaje SÓLO cuando L está compilado.

bubi
fuente
1

Aquí está la implementación de Roslyn, por lo que puede crear sus propios atributos que dan advertencias o errores sobre la marcha.

He creado un tipo de atributo llamado IdeMessageque será el atributo que genera advertencias:

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

    public IDEMessageAttribute(string message);
}

Para hacer esto, primero debe instalar Roslyn SDK y comenzar un nuevo proyecto VSIX con el analizador. He omitido algunas de las piezas menos relevantes como los mensajes, puedes descubrir cómo hacerlo. En tu analizador haces esto

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

No hay CodeFixProvider para esto, puede eliminarlo de la solución.

johnny 5
fuente