คำเตือนคอมไพเลอร์ที่กำหนดเอง


115

เมื่อใช้ ObsoleteAtribute ใน. Net จะให้คำเตือนคอมไพเลอร์ที่บอกคุณว่าอ็อบเจ็กต์ / วิธีการ / คุณสมบัติล้าสมัยและควรใช้อย่างอื่น ฉันกำลังทำโปรเจ็กต์ที่ต้องรีแฟคโค้ดอดีตพนักงานเป็นจำนวนมาก ฉันต้องการเขียนแอตทริบิวต์ที่กำหนดเองที่ฉันสามารถใช้เพื่อทำเครื่องหมายวิธีการหรือคุณสมบัติที่จะสร้างคำเตือนของคอมไพเลอร์ที่ให้ข้อความที่ฉันเขียน อะไรทำนองนี้

[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

ฉันต้องการให้สิ่งนี้สร้างคำเตือนคอมไพเลอร์ที่ระบุว่า "รหัสนี้ sux และควรได้รับการตรวจสอบ" ฉันรู้วิธีสร้างแอตทริบิวต์ที่กำหนดเองคำถามคือฉันจะทำให้มันสร้างคำเตือนคอมไพเลอร์ในสตูดิโอภาพได้อย่างไร


นี่คือ C #? ฉันจะคาดเดาสิ่งนี้ใหม่เป็น C # (ไม่ใช่ C) โดยสันนิษฐานว่าเป็นสิ่งที่ผู้โพสต์ดั้งเดิมหมายถึงการเลือก
Onorio Catenacci

13
นั่นไม่ใช่ VB หรือ C # ที่ถูกต้อง ... มันคืออะไร ... ?!
ljs

5
คำถามเก่า แต่คุณสามารถกำหนดคำเตือนของคอมไพเลอร์ที่กำหนดเองโดยใช้ Roslyn ได้แล้ว
RJ Cuthbertson

4
@jrummell ใน Roslyn พูดตัววิเคราะห์โค้ด: johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson

2
@RJCuthbertson ฉันย้ายความคิดเห็นของคุณไปเป็นคำตอบที่ได้รับการยอมรับเพื่อให้ความสนใจนั้นสมควรได้รับ
jpaugh

คำตอบ:


27

ปรับปรุง

ตอนนี้สามารถทำได้ด้วย Roslyn (Visual Studio 2015) คุณสามารถสร้างตัววิเคราะห์โค้ดเพื่อตรวจสอบแอตทริบิวต์ที่กำหนดเอง


ฉันไม่เชื่อว่ามันจะเป็นไปได้ ObsoleteAttribute ได้รับการปฏิบัติเป็นพิเศษโดยคอมไพเลอร์และกำหนดไว้ในมาตรฐาน C # ทำไม ObsoleteAttribute ถึงไม่ยอมรับบนโลก สำหรับฉันแล้วดูเหมือนว่านี่เป็นสถานการณ์ที่ถูกออกแบบมาสำหรับและบรรลุสิ่งที่คุณต้องการอย่างแม่นยำ!

โปรดทราบว่า Visual Studio รับคำเตือนที่สร้างโดย ObsoleteAttribute ทันทีซึ่งมีประโยชน์มาก

อย่าคิดว่าจะไม่มีประโยชน์แค่สงสัยว่าทำไมคุณถึงไม่กระตือรือร้นที่จะใช้มัน ...

น่าเสียดายที่ ObsoleteAttribute ถูกปิดผนึก (ส่วนหนึ่งอาจเกิดจากการดูแลพิเศษ) ดังนั้นคุณจึงไม่สามารถ subclass แอตทริบิวต์ของคุณเองจากมันได้

จากมาตรฐาน C #: -

แอตทริบิวต์ล้าสมัยใช้เพื่อทำเครื่องหมายประเภทและสมาชิกของประเภทที่ไม่ควรใช้อีกต่อไป

หากโปรแกรมใช้ชนิดหรือสมาชิกที่ตกแต่งด้วยแอ็ตทริบิวต์เลิกใช้คอมไพเลอร์จะออกคำเตือนหรือข้อผิดพลาด โดยเฉพาะอย่างยิ่งคอมไพลเลอร์ออกคำเตือนหากไม่มีพารามิเตอร์ข้อผิดพลาดหรือหากระบุพารามิเตอร์ข้อผิดพลาดและมีค่าเป็นเท็จ คอมไพเลอร์แสดงข้อผิดพลาดหากระบุพารามิเตอร์ข้อผิดพลาดและมีค่าเป็นจริง

นั่นสรุปความต้องการของคุณไม่ใช่เหรอ ... คุณจะไม่ทำได้ดีไปกว่าที่ฉันไม่คิด


14
ฉันกำลังมองหาสิ่งเดียวกัน 'ใช้งานได้' ล้าสมัย แต่โค้ดไม่ล้าสมัยมากนักเนื่องจากไม่สมบูรณ์เนื่องจากการปรับโครงสร้างใหม่
ก.

11
ฉันเห็นด้วยกับ @g และอาจเป็นผู้เขียนต้นฉบับ ล้าสมัยหมายความว่าล้าสมัยอย่าใช้ ฉันต้องการตั้งค่าสถานะบางอย่างว่า "เฮ้นี่คอมไพล์ แต่เราต้องการ a) ทำฟังก์ชันให้สมบูรณ์หรือ b) refactor" มันจะเป็นแอตทริบิวต์เวลาในการพัฒนามากกว่า นอกจากนี้งานยังทำงานได้เช่น // TODO: แต่ฉันไม่ได้ใช้สิ่งเหล่านี้เนื่องจากฉันเดาว่าหลายคนไม่ได้ทำ แต่ตรวจสอบคำเตือนของคอมไพเลอร์เป็นประจำ
MikeJansen

8
อีกเหตุผลหนึ่งที่ไม่ใช้[Obsolete]แท็กก็คืออาจทำให้เกิดปัญหาหากคุณจำเป็นต้องทำ XmlSerialization กับคุณสมบัติ การเพิ่ม[Obsolete]แท็กยังเพิ่ม[XmlIgnore]แอตทริบิวต์เบื้องหลัง
burnttoast11

6
ล้าสมัยแตกต่างกัน ล้าสมัยจะแจ้งเตือนคุณเกี่ยวกับโค้ดทุกบรรทัดที่เรียกใช้เมธอดนั้น ฉันไม่คิดว่านั่นคือสิ่งที่ผู้โพสต์ต้องการ (อย่างน้อยนั่นก็ไม่ใช่สิ่งที่ฉันต้องการเมื่อฉันค้นหาและพบคำถามนี้) ฉันคิดว่าสิ่งที่คำถามกำลังมองหาคือคำเตือนเพื่อแสดงคำจำกัดความของฟังก์ชันไม่ใช่สถานที่ที่มีการใช้งาน
นิค

ไม่ใช่คำตอบที่ยิ่งใหญ่ที่สุด -1 สำหรับการคิดว่าคุณไม่สามารถหาเหตุผลที่จะไม่ใช้มันเป็นการวิจารณ์ ทัศนคตินี้ไม่สนับสนุนความถูกต้อง
Mike Socha III

96

นี่น่าลอง

คุณไม่สามารถขยายความล้าสมัยได้เนื่องจากเป็นขั้นสุดท้าย แต่บางทีคุณอาจสร้างแอตทริบิวต์ของคุณเองและทำเครื่องหมายว่าคลาสนั้นล้าสมัยเช่นนี้:

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

จากนั้นเมื่อคุณทำเครื่องหมายวิธีการของคุณด้วยแอตทริบิวต์ "MustRefactor" คำเตือนการคอมไพล์จะแสดงขึ้น สร้างคำเตือนเวลาคอมไพล์ แต่ข้อความแสดงข้อผิดพลาดดูตลกคุณควรดูด้วยตัวคุณเองและเลือก สิ่งนี้ใกล้เคียงกับสิ่งที่คุณต้องการบรรลุมาก

อัปเดต: ด้วยรหัสนี้จะสร้างคำเตือน (ไม่ค่อยดีนัก แต่ฉันไม่คิดว่าจะมีอะไรดีกว่านี้)

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
{

}

คุณสามารถวางสิ่งที่สร้างขึ้นได้หรือไม่? ฉันอยากรู้.
คาห์

1
คำเตือนการคอมไพล์ถูกทริกเกอร์แม้ว่าคุณสมบัติ / เมธอดจะไม่ถูกเรียก
Rolf Kristensen

1
คำแนะนำดีๆที่นี่ ฉันต้องการทำสิ่งเดียวกันและลงเอยด้วยการโยน NotImplementedExceptions ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุดเนื่องจากไม่ปรากฏในเวลาคอมไพล์เฉพาะที่รันไทม์เท่านั้นหากโค้ดถูกเรียกใช้ ฉันจะลองเอง
MonkeyWrench

1
จะดีหรือไม่ถ้า ObsolteAttribute สามารถรองรับนิพจน์เหมือนกับ DebuggerDisplayAttribute เราก็สามารถทำสิ่งดีๆได้ visualstudio.uservoice.com/forums/121579-visual-studio/…
jpierson

หากคุณนำไปใช้IDisposableกับคลาสที่ล้าสมัยเหล่านั้นหมายความว่าคุณสามารถรวมโค้ดทดสอบการหลบในusingบล็อกได้ เช่นนี้: using(new MustRefactor()){DodgyCode();}. จากนั้นคุณจะพบการใช้งานทั้งหมดเมื่อคุณทำเสร็จแล้ว ตอนนี้ฉันใช้สิ่งนี้กับSleepเธรดภายใน for loop ฉันจำเป็นต้องชะลอตัวลงเพื่อวัตถุประสงค์ในการดีบัก
Iain Fraser

48

ในคอมไพเลอร์บางตัวคุณสามารถใช้ # คำเตือนเพื่อออกคำเตือน:

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

ในคอมไพเลอร์ของ Microsoft โดยทั่วไปคุณสามารถใช้ข้อความ pragma:

#pragma message ( "text" )

คุณพูดถึง. Net แต่ไม่ได้ระบุว่าคุณกำลังเขียนโปรแกรมด้วย C / C ++ หรือ C # หากคุณกำลังเขียนโปรแกรมใน C # กว่าที่คุณควรรู้ว่าC # สนับสนุนรูปแบบ


1
# คำเตือนหรือ #pragma เป็นคำสั่งก่อนตัวประมวลผลดังนั้นจะทำงานไม่ว่าจะมีโค้ดของอดีตเพื่อนร่วมงานของ micah อยู่หรือไม่และจะไม่โต้ตอบกับแอตทริบิวต์เลย ค่อนข้างล้าสมัยเป็นวิธีเดียวในการบรรลุเป้าหมายนี้ ...
ljs

39

ขณะนี้เรากำลังอยู่ระหว่างการปรับโครงสร้างใหม่จำนวนมากซึ่งเราไม่สามารถแก้ไขทุกอย่างได้ในทันที เราเพียงใช้คำสั่ง #warning preproc ซึ่งเราต้องย้อนกลับไปดูโค้ด จะปรากฏในเอาต์พุตของคอมไพเลอร์ ฉันไม่คิดว่าคุณจะใส่มันลงในวิธีการ แต่คุณสามารถใส่มันเข้าไปในวิธีการนั้นได้และมันก็ยังหาได้ง่าย

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

7

ใน VS 2008 (+ sp1) # คำเตือนไม่แสดงอย่างถูกต้องในรายการข้อผิดพลาดหลังจาก Clean Soultion & Rebuild Solution ไม่ใช่ทั้งหมด คำเตือนบางอย่างจะแสดงในรายการข้อผิดพลาดหลังจากที่ฉันเปิดไฟล์คลาสเฉพาะเท่านั้น ดังนั้นฉันจึงถูกบังคับให้ใช้แอตทริบิวต์ที่กำหนดเอง:

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

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

    public MappingToDo()
    {}
}

ดังนั้นเมื่อฉันตั้งค่าสถานะบางรหัสด้วย

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

มันสร้างคำเตือนเช่นนี้:

Namespace.MappingToDo ล้าสมัย: 'การแมป ToDo'

ฉันไม่สามารถเปลี่ยนข้อความของคำเตือนได้, 'ความคิดเห็นบางส่วน' ไม่แสดงในรายการข้อผิดพลาด แต่จะข้ามไปยังตำแหน่งที่เหมาะสมในไฟล์ ดังนั้นหากคุณต้องการเปลี่ยนแปลงข้อความเตือนดังกล่าวให้สร้างแอตทริบิวต์ต่างๆ


6

สิ่งที่คุณพยายามทำคือการใช้คุณลักษณะในทางที่ผิด ใช้รายการงาน Visual Studio แทน คุณสามารถป้อนความคิดเห็นในรหัสของคุณดังนี้:

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

จากนั้นเปิด View / Task List จากเมนู รายการงานมีสองประเภทงานของผู้ใช้และความคิดเห็น เปลี่ยนเป็นความคิดเห็นแล้วคุณจะเห็น // สิ่งที่ต้องทำทั้งหมดของคุณที่นั่น ดับเบิลคลิกที่สิ่งที่ต้องทำจะข้ามไปที่ความคิดเห็นในโค้ดของคุณ

อัล


1
ฉันพบว่านี่เป็นทางออกที่ดีกว่า
ซามูเอล

1
จะเกิดอะไรขึ้นหากคุณต้องการทำเครื่องหมายฟังก์ชันเป็น "ห้ามเรียกในรหัสการผลิต" หรือคล้ายกัน ดังนั้นคุณจึงต้องการให้มันเริ่มทำงานหากมีการเรียกใช้ฟังก์ชันหรือคลาสหรือสร้างอินสแตนซ์ แต่ไม่ใช่หากเพิ่งคอมไพล์
Jesse Pepper

2

ฉันไม่คิดว่าคุณจะทำได้ เท่าที่ฉันรู้ว่าการสนับสนุน ObsoleteAttribute นั้นเข้ารหัสในคอมไพเลอร์ C # เป็นหลัก คุณไม่สามารถทำอะไรที่คล้ายกันได้โดยตรง

สิ่งที่คุณอาจทำได้คือใช้งาน MSBuild (หรือเหตุการณ์หลังการสร้าง) ที่เรียกใช้เครื่องมือแบบกำหนดเองกับแอสเซมบลีที่คอมไพล์ เครื่องมือที่กำหนดเองจะแสดงถึงประเภท / วิธีการทั้งหมดในแอสเซมบลีและใช้แอตทริบิวต์ที่กำหนดเองของคุณซึ่งจะสามารถพิมพ์ไปยัง System.Console เริ่มต้นหรือ TextWriters ข้อผิดพลาด


2

เมื่อมองไปที่ซอร์สของObsoleteAttributeดูเหมือนว่าจะไม่ได้ทำอะไรเป็นพิเศษในการสร้างคำเตือนของคอมไพเลอร์ดังนั้นฉันมักจะใช้ @ technophileและบอกว่าฮาร์ดโค้ดในคอมไพเลอร์ มีเหตุผลที่คุณไม่ต้องการเพียงแค่ใช้ObsoleteAttributeเพื่อสร้างข้อความเตือนของคุณหรือไม่?


ไม่มีเหตุผลอื่นใดนอกจากรหัสไม่จำเป็นต้องล้าสมัย
คาห์

1
มันระบุไว้ในข้อกำหนด C # ว่าได้รับการปฏิบัติโดยคอมไพเลอร์เป็นพิเศษโปรดดูคำตอบของฉัน :-) Micah - 'แอตทริบิวต์ล้าสมัยใช้เพื่อทำเครื่องหมายประเภทและสมาชิกของประเภทที่ไม่ควรใช้อีกต่อไป' จากข้อกำหนด ใช้ไม่ได้เหรอ ...
ljs

ถ้ามีใครสงสัยก็ไม่มีรหัส C # ในซอร์สโค้ดที่จะทำเช่นนี้ referencesource.microsoft.com/#mscorlib/system/…
Paweł Mach

1

มีหลายความคิดเห็นที่แนะนำให้ใส่คำเตือนหรือ pragma ล้าสมัยทำงานในลักษณะที่แตกต่างกันมาก! การทำเครื่องหมายฟังก์ชันของไลบรารี L ล้าสมัยข้อความล้าสมัยจะเพิ่มขึ้นเมื่อโปรแกรมเรียกใช้ฟังก์ชันแม้ว่าโปรแกรมผู้โทรจะไม่ได้อยู่ในไลบรารี L คำเตือนจะเพิ่มข้อความเฉพาะเมื่อคอมไพล์ L


1

นี่คือการนำ Roslyn ไปใช้เพื่อให้คุณสามารถสร้างแอตทริบิวต์ของคุณเองที่ให้คำเตือนหรือข้อผิดพลาดได้ทันที

ฉันได้สร้างแอตทริบิวต์ Type called IdeMessageซึ่งจะเป็นแอตทริบิวต์ที่สร้างคำเตือน:

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

    public IDEMessageAttribute(string message);
}

ในการดำเนินการนี้คุณต้องติดตั้ง Roslyn SDK ก่อนและเริ่มโครงการ VSIX ใหม่ด้วยตัววิเคราะห์ ฉันได้ละเว้นบางส่วนที่เกี่ยวข้องน้อยกว่าเช่นข้อความคุณสามารถหาวิธีทำได้ ในเครื่องวิเคราะห์ของคุณคุณทำสิ่งนี้

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

ไม่มี CodeFixProvider สำหรับสิ่งนี้คุณสามารถลบออกจากโซลูชันได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.