C# 如果未调用基本方法,则visual studio发出警告

C# 如果未调用基本方法,则visual studio发出警告,c#,.net,visual-studio,C#,.net,Visual Studio,我正在研究一种方法,如果我重写基类中的特定方法,但忘记在被重写的基类中调用基类方法,那么VisualStudio会发出警告。例如: class Foo { [SomeAttributeToMarkTheMethodToFireTheWarning] public virtual void A() { ... } } class Bar : Foo { public override void A() { // base.A(); // warning if

我正在研究一种方法,如果我重写基类中的特定方法,但忘记在被重写的基类中调用基类方法,那么VisualStudio会发出警告。例如:

class Foo
{
   [SomeAttributeToMarkTheMethodToFireTheWarning]
   public virtual void A() { ... }
}

class Bar : Foo
{
   public override void A()
   {
      // base.A(); // warning if base.A() is not called
      // ...
   }
}
到目前为止,我还没有找到一种方法,可能也不可能让编译器直接发出这样的警告。哪怕是第三方工具或使用新的Roslyn.NET编译器平台上的API,你有什么想法可以实现这一点吗

更新:
例如,在AndroidStudio(IntelliJ)中,如果在任何活动中重写
onCreate()
,但忘记调用基本方法
super.onCreate()
,则会收到警告。这是我在VS.中需要的行为。

如果您想确保某些代码运行,那么您应该更改您的设计:

abstract class Foo
{
   protected abstract void PostA();  

   public void A() { 
      ... 
      PostA();
   }
}


class Bar : Foo
{
   protected override void PostA()
   {

   }
}

//method signature remains the same:
Bar.A();
这样,
A()
总是在重写方法之前激发

要具有多个继承性并确保调用了(),还必须将bar设置为抽象:

abstract class Bar : Foo
{
   //no need to override now
}

class Baz:Bar
{
   protected override void PostA()
   {

   }
}
在C#中没有办法准确地做你想做的事。这不是VisualStudio的问题。这就是C#的工作原理

可以重写或不重写虚拟方法签名,也可以在基中调用或不调用。您有两个选择虚拟或抽象。您使用的是
virtual
,我给了您一个
abstract
soltuion。这取决于你选择你想用哪一个


我能想到的最接近你想要的是一个
#警告
。看见但这只会在输出窗口中产生警告,而不会在intellisense中产生警告。基本上。

我终于有时间用Roslyn做实验了,看起来我用分析仪找到了解决方案。这是我的解决方案

标记子类中需要重写的方法的属性:

[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public sealed class RequireBaseMethodCallAttribute : Attribute
{
    public RequireBaseMethodCallAttribute() { }
}
分析仪:

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RequiredBaseMethodCallAnalyzer : DiagnosticAnalyzer
{
    public const string DiagnosticId = "RequireBaseMethodCall";

    // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat.
    // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md for more on localization
    private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources));
    private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources));
    private const string Category = "Usage";

    private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

    public override void Initialize(AnalysisContext context)
    {
        context.RegisterCompilationStartAction(AnalyzeMethodForBaseCall);
    }

    private static void AnalyzeMethodForBaseCall(CompilationStartAnalysisContext compilationStartContext)
    {
        compilationStartContext.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration);
    }

    private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
    {
        var mds = context.Node as MethodDeclarationSyntax;
        if (mds == null)
        {
            return;
        }

        IMethodSymbol symbol = context.SemanticModel.GetDeclaredSymbol(mds) as IMethodSymbol;
        if (symbol == null)
        {
            return;
        }

        if (!symbol.IsOverride)
        {
            return;
        }

        if (symbol.OverriddenMethod == null)
        {
            return;
        }

        var overridenMethod = symbol.OverriddenMethod;
        var attrs = overridenMethod.GetAttributes();
        if (!attrs.Any(ad => ad.AttributeClass.MetadataName.ToUpperInvariant() 
                            == typeof(RequireBaseMethodCallAttribute).Name.ToUpperInvariant()))
        {
            return;
        }

        var overridenMethodName = overridenMethod.Name.ToString();
        string methodName = overridenMethodName;

        var invocations = mds.DescendantNodes().OfType<MemberAccessExpressionSyntax>().ToList();
        foreach (var inv in invocations)
        {
            var expr = inv.Expression;
            if ((SyntaxKind)expr.RawKind == SyntaxKind.BaseExpression)
            {
                var memberAccessExpr = expr.Parent as MemberAccessExpressionSyntax;
                if (memberAccessExpr == null)
                {
                    continue;
                }

                // compare exprSymbol and overridenMethod
                var exprMethodName = memberAccessExpr.Name.ToString();

                if (exprMethodName != overridenMethodName)
                {
                    continue;
                }

                var invokationExpr = memberAccessExpr.Parent as InvocationExpressionSyntax;
                if (invokationExpr == null)
                {
                    continue;
                }
                var exprMethodArgs = invokationExpr.ArgumentList.Arguments.ToList();
                var ovrMethodParams = overridenMethod.Parameters.ToList();

                if (exprMethodArgs.Count != ovrMethodParams.Count)
                {
                    continue;
                }

                var paramMismatch = false;
                for (int i = 0; i < exprMethodArgs.Count; i++)
                {
                    var arg = exprMethodArgs[i];
                    var argType = context.SemanticModel.GetTypeInfo(arg.Expression);

                    var param = arg.NameColon != null ? 
                                ovrMethodParams.FirstOrDefault(p => p.Name.ToString() == arg.NameColon.Name.ToString()) : 
                                ovrMethodParams[i];

                    if (param == null || argType.Type != param.Type)
                    {
                        paramMismatch = true;
                        break;
                    }

                    exprMethodArgs.Remove(arg);
                    ovrMethodParams.Remove(param);
                    i--;
                }

                // If there are any parameters left without default value
                // then it is not the base method overload we are looking for
                if (ovrMethodParams.Any(p => p.HasExplicitDefaultValue))
                {
                    continue;
                }

                if (!paramMismatch)
                {
                    // If the actual arguments match with the method params
                    // then the base method invokation was found
                    // and there is no need to continue the search
                    return;
                }
            }
        }

        var diag = Diagnostic.Create(Rule, mds.GetLocation(), methodName);
        context.ReportDiagnostic(diag);
    }
}
[DiagnosticanAnalyzer(LanguageNames.CSharp)]
公共类RequiredBaseMethodCallAnalyzer:DiagnosticanAnalyzer
{
public const string DiagnosticId=“requirebemethodcall”;
//您可以在Resources.resx文件中更改这些字符串。如果您不希望您的分析器能够本地化,可以使用常规字符串作为标题和消息格式。
//看https://github.com/dotnet/roslyn/blob/master/docs/analyzers/Localizing%20Analyzers.md 有关本地化的更多信息
private static readonly LocalizableString Title=新的LocalizableResourceString(nameof(Resources.AnalyzerTitle)、Resources.ResourceManager、typeof(Resources));
private static readonly LocalizableString MessageFormat=新的LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat)、Resources.ResourceManager、typeof(Resources));
private static readonly LocalizableString Description=新的LocalizableResourceString(nameof(Resources.AnalyzerDescription)、Resources.ResourceManager、typeof(Resources));
private const string Category=“用法”;
私有静态DiagnosticDescriptor规则=新的DiagnosticDescriptor(DiagnosticId、标题、消息格式、类别、DiagnosticSeverity.Warning、IsEnabled默认为true,描述为description);
公共覆盖ImmutableArray支持的诊断{get{return ImmutableArray.Create(Rule);}
公共覆盖无效初始化(AnalysisContext上下文)
{
RegisterCompilationStartAction(AnalyzeMethodForBaseCall);
}
专用静态无效分析方法BaseCall(编译StartAnalysisContext编译StartContext)
{
编译StartContext.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration,SyntaxKind.MethodDeclaration);
}
私有静态void分析方法声明(SyntaxNodeAnalysisContext上下文)
{
var mds=context.Node as MethodDeclarationSyntax;
如果(mds==null)
{
返回;
}
IMethodSymbol symbol=context.SemanticModel.GetDeclaredSymbol(mds)作为IMethodSymbol;
如果(符号==null)
{
返回;
}
如果(!symbol.IsOverride)
{
返回;
}
if(symbol.overridedenMethod==null)
{
返回;
}
var OverrideMethod=symbol.OverrideMethod;
var attrs=overrideMethod.GetAttributes();
如果(!attrs.Any(ad=>ad.AttributeClass.MetadataName.ToUpperInvariant())
==typeof(RequireBeamMethodCallAttribute).Name.ToUpperInvariant())
{
返回;
}
var OverrideMethodName=OverrideMethod.Name.ToString();
string methodName=重写的methodName;
var invocations=mds.degenantnodes().OfType().ToList();
foreach(调用中的var inv)
{
变量表达式=变量表达式;
if((SyntaxKind)expr.RawKind==SyntaxKind.BaseExpression)
{
var memberAccessExpr=expr.Parent作为MemberAccessExpressionSyntax;
if(memberAccessExpr==null)
{
继续;
}
//比较exprSymbol和OverrideMethod
var exprMethodName=memberAccessExpr.Name.ToString();
if(exprMethodName!=重写方法名)
{
继续;
}
var invokationExpr=memberAccessExpr.Parent作为InvokationExpressionSyntax;
if(invokationExpr==null)
{
继续;
}
var exprMethodArgs=invokationExpr.ArgumentList.Arguments.ToList();
var ovrMethodParams=OverrideMethod.Parameters.ToList();
if(exprMethodArgs.Count!=ovrMethodParams.Count)
{
继续;
}
var参数不匹配=假;
for(int i=0;i[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(BaseMethodCallCodeFixProvider)), Shared]
public class BaseMethodCallCodeFixProvider : CodeFixProvider
{
    private const string title = "Add base method invocation";

    public sealed override ImmutableArray<string> FixableDiagnosticIds
    {
        get { return ImmutableArray.Create(RequiredBaseMethodCallAnalyzer.DiagnosticId); }
    }

    public sealed override FixAllProvider GetFixAllProvider()
    {
        // See https://github.com/dotnet/roslyn/blob/master/docs/analyzers/FixAllProvider.md for more information on Fix All Providers
        return WellKnownFixAllProviders.BatchFixer;
    }

    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;

        // Register a code action that will invoke the fix.
        context.RegisterCodeFix(
            CodeAction.Create(
                title: title,
                createChangedDocument: c => AddBaseMethodCallAsync(context.Document, diagnosticSpan, c),
                equivalenceKey: title),
            diagnostic);
    }

    private async Task<Document> AddBaseMethodCallAsync(Document document, TextSpan diagnosticSpan, CancellationToken cancellationToken)
    {
        var root = await document.GetSyntaxRootAsync(cancellationToken);
        var node = root.FindNode(diagnosticSpan) as MethodDeclarationSyntax;

        var args = new List<ArgumentSyntax>();
        foreach (var param in node.ParameterList.Parameters)
        {
            args.Add(SyntaxFactory.Argument(SyntaxFactory.ParseExpression(param.Identifier.ValueText)));
        }

        var argsList = SyntaxFactory.SeparatedList(args);

        var exprStatement = SyntaxFactory.ExpressionStatement(
            SyntaxFactory.InvocationExpression(
                SyntaxFactory.MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    SyntaxFactory.BaseExpression(),
                    SyntaxFactory.Token(SyntaxKind.DotToken),
                    SyntaxFactory.IdentifierName(node.Identifier.ToString())
                ),
                SyntaxFactory.ArgumentList(argsList)
            ),
            SyntaxFactory.Token(SyntaxKind.SemicolonToken)
        );

        var newBodyStatements = SyntaxFactory.Block(node.Body.Statements.Insert(0, exprStatement));
        var newRoot = root.ReplaceNode(node.Body, newBodyStatements).WithAdditionalAnnotations(Simplifier.Annotation);

        return document.WithSyntaxRoot(newRoot);
    }
}
/// <summary>
/// Base class for making sure that descendants always invoke overridable
/// methods of base.
/// </summary>
public abstract class Overridable
{
    private sealed class InvocationGuard : IDisposable
    {
        private readonly Overridable overridable;
        public readonly  string      MethodName;
        public           bool        Invoked;

        public InvocationGuard( Overridable overridable, string methodName )
        {
            this.overridable = overridable;
            MethodName       = methodName;
        }

        public void Dispose()
        {
            Assert( ReferenceEquals( overridable.invocationStack.Peek(), this ) );
            Assert( Invoked );
            overridable.invocationStack.Pop();
        }
    }

    private readonly Stack<InvocationGuard> invocationStack = new Stack<InvocationGuard>();

    public IDisposable NewOverridableGuard( string methodName )
    {
        Assert( ReflectionHelpers.MethodExistsAssertion( GetType(), methodName ) );
        var invocationGuard = new InvocationGuard( this, methodName );
        invocationStack.Push( invocationGuard );
        return invocationGuard;
    }

    public void OverridableWasInvoked( [CanBeNull][CallerMemberName] string methodName = null )
    {
        Assert( methodName != null );
        Assert( ReflectionHelpers.MethodExistsAssertion( GetType(), methodName ) );
        InvocationGuard invocationGuard = invocationStack.Peek();
        Assert( invocationGuard.MethodName == methodName );
        Assert( !invocationGuard.Invoked );
        invocationGuard.Invoked = true;
    }
}
protected internal void RaisePropertyChanged( [CallerMemberName] string propertyName = null )
{
    using( NewOverridableGuard( nameof(OnPropertyChanged) ) ) //Add this to your existing code
        OnPropertyChanged( propertyName );
}

protected virtual void OnPropertyChanged( string propertyName )
{
    OverridableWasInvoked(); //Add this to your existing code
    PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}