diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs
new file mode 100644
index 0000000000..ae87caec60
--- /dev/null
+++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpConstantExpectedAnalyzer.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.NetCore.Analyzers.Performance;
+
+namespace Microsoft.NetCore.CSharp.Analyzers.Performance
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ public sealed class CSharpConstantExpectedAnalyzer : ConstantExpectedAnalyzer
+ {
+ private static readonly CSharpDiagnosticHelper s_diagnosticHelper = new();
+ private static readonly IdentifierNameSyntax s_constantExpectedIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpected);
+ private static readonly IdentifierNameSyntax s_constantExpectedAttributeIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedAttribute);
+
+ protected override DiagnosticHelper Helper => s_diagnosticHelper;
+
+ protected override void RegisterAttributeSyntax(CompilationStartAnalysisContext context, ConstantExpectedContext constantExpectedContext)
+ {
+ context.RegisterSyntaxNodeAction(context => OnAttributeNode(context, constantExpectedContext), SyntaxKind.Attribute);
+ }
+
+ private void OnAttributeNode(SyntaxNodeAnalysisContext context, ConstantExpectedContext constantExpectedContext)
+ {
+ var attributeSyntax = (AttributeSyntax)context.Node;
+ var attributeName = attributeSyntax.Name;
+ if (!attributeName.IsEquivalentTo(s_constantExpectedIdentifier) && !attributeName.IsEquivalentTo(s_constantExpectedAttributeIdentifier))
+ {
+ return;
+ }
+
+ if (attributeSyntax.Parent.Parent is ParameterSyntax parameter)
+ {
+ var parameterSymbol = context.SemanticModel.GetDeclaredSymbol(parameter);
+ OnParameterWithConstantExpectedAttribute(parameterSymbol, constantExpectedContext, context.ReportDiagnostic);
+ }
+ }
+
+ private sealed class CSharpDiagnosticHelper : DiagnosticHelper
+ {
+ private readonly IdentifierNameSyntax _constantExpectedMinIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedMin);
+ private readonly IdentifierNameSyntax _constantExpectedMaxIdentifier = (IdentifierNameSyntax)SyntaxFactory.ParseName(ConstantExpectedMax);
+
+ public override Location? GetMaxLocation(SyntaxNode attributeNode) => GetArgumentLocation(attributeNode, _constantExpectedMaxIdentifier);
+
+ public override Location? GetMinLocation(SyntaxNode attributeNode) => GetArgumentLocation(attributeNode, _constantExpectedMinIdentifier);
+
+ private static Location? GetArgumentLocation(SyntaxNode attributeNode, IdentifierNameSyntax targetNameSyntax)
+ {
+ var attributeSyntax = (AttributeSyntax)attributeNode;
+ if (attributeSyntax.ArgumentList is null)
+ {
+ return null;
+ }
+ var targetArg = attributeSyntax.ArgumentList.Arguments.FirstOrDefault(arg => arg.NameEquals.Name.IsEquivalentTo(targetNameSyntax, true));
+ return targetArg?.GetLocation();
+ }
+ }
+ }
+}
+;
\ No newline at end of file
diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
index cdf4f1397e..4cb7d58e49 100644
--- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
+++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
@@ -1 +1,8 @@
; Please do not edit this file manually, it should only be updated through code fix application.
+
+### New Rules
+
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+CA1856 | Performance | Error | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1856)
+CA1857 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1857)
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
index 62e7fe1249..bd0da36e43 100644
--- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
@@ -1864,6 +1864,42 @@
'{0}' uses the preview type '{1}' and needs to opt into preview features. See {2} for more information.
+
+ Incorrect usage of ConstantExpected attribute
+
+
+ ConstantExpected attribute is not applied correctly on the parameter.
+
+
+ A constant is expected for the parameter
+
+
+ The parameter expects a constant for optimal performance.
+
+
+ The '{0}' type is not supported for ConstantExpected attribute
+
+
+ The '{0}' value is not compatible with parameter type of '{1}'
+
+
+ The '{0}' value does not fit within the parameter value bounds of '{1}' to '{2}'
+
+
+ The Min and Max values are inverted
+
+
+ The constant does not fit within the value bounds of '{0}' to '{1}'
+
+
+ The constant is not of the same '{0}' type as the parameter
+
+
+ The argument should be a constant for optimal performance
+
+
+ The ConstantExpected attribute is required for the parameter due to the parent method annotation
+
Specify a culture or use an invariant version
diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs
new file mode 100644
index 0000000000..5060578fd6
--- /dev/null
+++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/ConstantExpectedAnalyzer.UnmanagedHelper.cs
@@ -0,0 +1,340 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Operations;
+
+namespace Microsoft.NetCore.Analyzers.Performance
+{
+ public abstract partial class ConstantExpectedAnalyzer
+ {
+ private sealed class UnmanagedHelper where T : unmanaged
+ {
+ private static readonly ConstantExpectedParameterFactory? _instance;
+ private static ConstantExpectedParameterFactory Instance => _instance ?? throw new InvalidOperationException("unsupported type");
+
+ static UnmanagedHelper()
+ {
+ if (typeof(T) == typeof(long))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformInt64);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ else if (typeof(T) == typeof(ulong))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformUInt64);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ else if (typeof(T) == typeof(float))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformSingle);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ else if (typeof(T) == typeof(double))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformDouble);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ else if (typeof(T) == typeof(char))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformChar);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ else if (typeof(T) == typeof(bool))
+ {
+ var helper = new UnmanagedHelper.TransformHelper(TryTransformBoolean);
+ _instance = new ConstantExpectedParameterFactory((TransformHelper)(object)helper);
+ }
+ }
+
+ public static bool TryCreate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, [NotNullWhen(true)] out ConstantExpectedParameter? parameter)
+ => Instance.TryCreate(parameterSymbol, attributeData, typeMin, typeMax, out parameter);
+ public static bool Validate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, DiagnosticHelper diagnosticHelper, out ImmutableArray diagnostics)
+ => Instance.Validate(parameterSymbol, attributeData, typeMin, typeMax, diagnosticHelper, out diagnostics);
+
+ public delegate bool TryTransform(object constant, out T value, out bool isInvalid);
+ public sealed class TransformHelper
+ {
+ private readonly TryTransform _tryTransform;
+
+ public TransformHelper(TryTransform tryTransform)
+ {
+ _tryTransform = tryTransform;
+ }
+
+ public bool IsLessThan(T operand1, T operand2) => Comparer.Default.Compare(operand1, operand2) < 0;
+ public bool TryTransformMin(object constant, out T value, ref ErrorKind errorFlags)
+ {
+ if (_tryTransform(constant, out value, out bool isInvalid))
+ {
+ return true;
+ }
+
+ errorFlags |= isInvalid ? ErrorKind.MinIsIncompatible : ErrorKind.MinIsOutOfRange;
+ return false;
+ }
+
+ public bool TryTransformMax(object constant, out T value, ref ErrorKind errorFlags)
+ {
+ if (_tryTransform(constant, out value, out bool isInvalid))
+ {
+ return true;
+ }
+
+ errorFlags |= isInvalid ? ErrorKind.MaxIsIncompatible : ErrorKind.MaxIsOutOfRange;
+ return false;
+ }
+ public bool TryConvert(object val, out T value) => _tryTransform(val, out value, out _);
+ }
+
+ public sealed class ConstantExpectedParameterFactory
+ {
+ private readonly TransformHelper _helper;
+
+ public ConstantExpectedParameterFactory(TransformHelper helper)
+ {
+ _helper = helper;
+ }
+ public bool Validate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, DiagnosticHelper diagnosticHelper, out ImmutableArray diagnostics)
+ {
+ if (!IsValidMinMax(attributeData, typeMin, typeMax, out _, out _, out ErrorKind errorFlags))
+ {
+ diagnostics = diagnosticHelper.GetError(errorFlags, parameterSymbol, attributeData.ApplicationSyntaxReference.GetSyntax(), typeMin.ToString(), typeMax.ToString());
+ return false;
+ }
+
+ diagnostics = ImmutableArray.Empty;
+ return true;
+ }
+
+ public bool TryCreate(IParameterSymbol parameterSymbol, AttributeData attributeData, T typeMin, T typeMax, [NotNullWhen(true)] out ConstantExpectedParameter? parameter)
+ {
+ if (!IsValidMinMax(attributeData, typeMin, typeMax, out T minValue, out T maxValue, out _))
+ {
+ parameter = null;
+ return false;
+ }
+
+ parameter = new UnmanagedConstantExpectedParameter(parameterSymbol, minValue, maxValue, _helper);
+ return true;
+ }
+
+ private bool IsValidMinMax(AttributeData attributeData, T typeMin, T typeMax, out T minValue, out T maxValue, out ErrorKind errorFlags)
+ {
+ minValue = typeMin;
+ maxValue = typeMax;
+ var ac = AttributeConstant.Get(attributeData);
+ errorFlags = ErrorKind.None;
+ if (ac.Min is not null && _helper.TryTransformMin(ac.Min, out minValue, ref errorFlags))
+ {
+ if (_helper.IsLessThan(minValue, typeMin) || _helper.IsLessThan(typeMax, minValue))
+ {
+ errorFlags |= ErrorKind.MinIsOutOfRange;
+ }
+ }
+
+ if (ac.Max is not null && _helper.TryTransformMax(ac.Max, out maxValue, ref errorFlags))
+ {
+ if (_helper.IsLessThan(maxValue, typeMin) || _helper.IsLessThan(typeMax, maxValue))
+ {
+ errorFlags |= ErrorKind.MaxIsOutOfRange;
+ }
+ }
+
+ if (errorFlags != ErrorKind.None)
+ {
+ return false;
+ }
+
+ if (_helper.IsLessThan(maxValue, minValue))
+ {
+ errorFlags = ErrorKind.MinMaxInverted;
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public sealed class UnmanagedConstantExpectedParameter : ConstantExpectedParameter
+ {
+ private readonly TransformHelper _helper;
+ public UnmanagedConstantExpectedParameter(IParameterSymbol parameter, T min, T max, TransformHelper helper) : base(parameter)
+ {
+ Min = min;
+ Max = max;
+ _helper = helper;
+ }
+
+ public T Min { get; }
+ public T Max { get; }
+
+ public override bool ValidateParameterIsWithinRange(ConstantExpectedParameter subsetCandidate, IArgumentOperation argument, [NotNullWhen(false)] out Diagnostic? validationDiagnostics)
+ {
+ if (Parameter.Type.SpecialType != subsetCandidate.Parameter.Type.SpecialType ||
+ subsetCandidate is not UnmanagedConstantExpectedParameter subsetCandidateTParameter)
+ {
+ validationDiagnostics = CreateConstantInvalidConstantRuleDiagnostic(argument);
+ return false;
+ }
+
+ if (!_helper.IsLessThan(subsetCandidateTParameter.Min, Min) && !_helper.IsLessThan(Max, subsetCandidateTParameter.Max))
+ {
+ //within range
+ validationDiagnostics = null;
+ return true;
+ }
+ validationDiagnostics = CreateConstantOutOfBoundsRuleDiagnostic(argument, Min.ToString(), Max.ToString());
+ return false;
+ }
+
+ public override bool ValidateValue(IArgumentOperation argument, Optional