Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1908,6 +1908,18 @@ In many situations, logging is disabled or set to a log level that results in an
|CodeFix|True|
---

## [CA1876](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876): Use 'ReadOnlySpan\<T>' or 'ReadOnlyMemory\<T>' instead of 'Span\<T>' or 'Memory\<T>'

Using 'ReadOnlySpan\<T>' or 'ReadOnlyMemory\<T>' instead of 'Span\<T>' or 'Memory\<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.

|Item|Value|
|-|-|
|Category|Performance|
|Enabled|True|
|Severity|Info|
|CodeFix|True|
---

## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope

If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3507,6 +3507,26 @@
]
}
},
"CA1876": {
"id": "CA1876",
"shortDescription": "Use 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>'",
"fullDescription": "Using 'ReadOnlySpan<T>' or 'ReadOnlyMemory<T>' instead of 'Span<T>' or 'Memory<T>' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.",
"defaultLevel": "note",
"helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876",
"properties": {
"category": "Performance",
"isEnabledByDefault": true,
"typeName": "PreferReadOnlySpanOverSpanAnalyzer",
"languages": [
"C#",
"Visual Basic"
],
"tags": [
"Telemetry",
"EnabledRuleInAggressiveMode"
]
}
},
"CA2000": {
"id": "CA2000",
"shortDescription": "Dispose objects before losing scope",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Rule ID | Category | Severity | Notes
CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873)
CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874)
CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875)
CA1876 | Performance | Info | PreferReadOnlySpanOverSpanAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1876)
CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023)
CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024)
CA2025 | Reliability | Disabled | DoNotPassDisposablesIntoUnawaitedTasksAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2025)
Original file line number Diff line number Diff line change
Expand Up @@ -2216,4 +2216,16 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="DoNotUseThreadVolatileReadWriteCodeFixTitle" xml:space="preserve">
<value>Replace obsolete call</value>
</data>
<data name="PreferReadOnlySpanOverSpanTitle" xml:space="preserve">
<value>Use 'ReadOnlySpan&lt;T&gt;' or 'ReadOnlyMemory&lt;T&gt;' instead of 'Span&lt;T&gt;' or 'Memory&lt;T&gt;'</value>
</data>
<data name="PreferReadOnlySpanOverSpanMessage" xml:space="preserve">
<value>Parameter '{0}' can be declared as '{1}' instead of as '{2}'</value>
</data>
<data name="PreferReadOnlySpanOverSpanDescription" xml:space="preserve">
<value>Using 'ReadOnlySpan&lt;T&gt;' or 'ReadOnlyMemory&lt;T&gt;' instead of 'Span&lt;T&gt;' or 'Memory&lt;T&gt;' for parameters that are not written to can prevent errors, convey intent more clearly, and may improve performance.</value>
</data>
<data name="PreferReadOnlySpanOverSpanCodeFixTitle" xml:space="preserve">
<value>Change to '{0}'</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;

namespace Microsoft.NetCore.Analyzers.Performance
{
/// <summary>
/// CA1876: Use ReadOnlySpan&lt;T&gt; or ReadOnlyMemory&lt;T&gt; instead of Span&lt;T&gt; or Memory&lt;T&gt;
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PreferReadOnlySpanOverSpanFixer))]
[Shared]
public sealed class PreferReadOnlySpanOverSpanFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } =
ImmutableArray.Create(PreferReadOnlySpanOverSpanAnalyzer.RuleId);

public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span, getInnermostNodeForTie: true);
var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

var diagnostic = context.Diagnostics[0];

if (semanticModel.GetDeclaredSymbol(node, context.CancellationToken) is not IParameterSymbol parameterSymbol ||
GetReadOnlyTypeName(parameterSymbol.Type) is not { } targetTypeName)
{
return;
}

var title = string.Format(MicrosoftNetCoreAnalyzersResources.PreferReadOnlySpanOverSpanCodeFixTitle, targetTypeName);

context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: c => ChangeParameterTypeAsync(context.Document, node, c),
equivalenceKey: title),
diagnostic);
}

private static string? GetReadOnlyTypeName(ITypeSymbol typeSymbol)
{
return typeSymbol is INamedTypeSymbol namedType && namedType.OriginalDefinition.Name is "Span" or "Memory" ?
$"ReadOnly{typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)}" :
null;
}

private static async Task<Document> ChangeParameterTypeAsync(
Document document,
SyntaxNode node,
CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

// Get the parameter symbol to construct the correct type
var parameterSymbol = semanticModel.GetDeclaredSymbol(node, cancellationToken) as IParameterSymbol;
if (parameterSymbol?.Type is not INamedTypeSymbol namedType || namedType.TypeArguments.Length != 1)
{
return document;
}

// Get the compilation to find the readonly types
var compilation = semanticModel.Compilation;
var typeName = namedType.OriginalDefinition.Name;
INamedTypeSymbol? readOnlyType = null;

if (typeName == "Span")
{
readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1);
}
else if (typeName == "Memory")
{
readOnlyType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlyMemory1);
}

if (readOnlyType == null)
{
return document;
}

// Construct the generic type with the same type argument
var newType = readOnlyType.Construct(namedType.TypeArguments[0]);
var newTypeNode = generator.TypeExpression(newType);

// Replace the parameter's type
editor.ReplaceNode(node, (currentNode, gen) =>
{
return gen.WithType(currentNode, newTypeNode);
});

return editor.GetChangedDocument();
}
}
}
Loading
Loading