MA0042: check if DisposeAsync is declared directly when runtime type is known#1118
Merged
Merged
Conversation
…is known When analyzing 'using' statements and declarations: - For direct object creation (new T()) or sealed types, only report a diagnostic if the type itself declares or overrides DisposeAsync. Inheriting a non-async default DisposeAsync (e.g. DbConnection's implementation that just calls Dispose()) is not a meaningful override, so 'await using' would bring no benefit. - For all other expressions (factory methods, non-sealed types), keep the existing full-hierarchy check since the runtime type may be a subclass that properly overrides DisposeAsync. Also removes the now-redundant MemoryStream special case; the general IObjectCreationOperation check already handles it correctly because MemoryStream does not declare DisposeAsync directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
For DbConnection subclasses, only warn about 'using' (instead of 'await using') when the exact type being instantiated has a meaningful DisposeAsync override. DbConnection.DisposeAsync itself just calls Dispose() synchronously, so inheriting it without overriding brings no benefit from 'await using'. - Restore MemoryStream special case (unchanged) - Add DbConnectionSymbol field for System.Data.Common.DbConnection - Add HasDisposeAsyncMethodDeclaredInDbConnectionSubclass: walks the type hierarchy from T up to (but not including) DbConnection and looks for a DisposeAsync declaration via GetMembers() - In CanBeAwaitUsing: for 'new T()' where T : DbConnection, use the new method; factory/method calls retain the old conservative behavior (warn, since the runtime type may be a deeper subclass) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of a special-cased MemoryStream exemption, apply the same DisposeAsync-override walk used for DbConnection to all Stream subclasses: when a Stream subclass is directly instantiated with 'new' and does not override DisposeAsync in the subclass chain (up to but not including Stream), do not report a diagnostic. - Replace MemoryStreamSymbol with StreamSymbol (System.IO.Stream) - Replace HasDisposeAsyncMethodDeclaredInDbConnectionSubclass with the generic HasDisposeAsyncMethodDeclaredInSubclass(symbol, baseTypeSymbol) - CanBeAwaitUsing now applies the check for both Stream and DbConnection - Add two new Stream-specific tests - Update MA0042.md to document Stream alongside DbConnection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced May 5, 2026
Closed
Merged
Merged
This was referenced May 11, 2026
Closed
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contributes to #1117
Problem
MA0042 was reporting false positives for types like
SqlConnectionorMemoryStreamthat inheritDisposeAsyncfrom a base class but don't actually override it. For example,DbConnection.DisposeAsyncis just a virtual method that callsDispose()synchronously — usingawait usingbrings no benefit when the runtime type is known to not override it.Solution
The fix distinguishes two cases based on whether the static type information tells us the exact runtime type:
new SqlConnection()SqlConnectiondoesn't declareDisposeAsyncCreateConnection()→SqlConnectionnew MyConn()whereMyConnoverridesDisposeAsyncsealed SealedConnwithout override, from factorysealed SealedConnwith override, from factorynew MemoryStream()MemoryStreamdoesn't declareDisposeAsync(replaces the hardcoded special case)Key changes
DoNotUseBlockingCallInAsyncContextAnalyzer.cs: AddedHasDisposeAsyncMethodDeclaredDirectly()which usesGetMembers()(direct only) instead ofGetAllMembers(). ModifiedCanBeAwaitUsing()to use it when the runtime type is statically known (direct object creation or sealed type). Removed the now-redundantMemoryStreamspecial case and its field.