diff --git a/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.Fixer.cs b/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.Fixer.cs
new file mode 100644
index 00000000..001821e7
--- /dev/null
+++ b/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.Fixer.cs
@@ -0,0 +1,42 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Creedengo.Core.Analyzers;
+
+/// GCI6605 fixer: Replace Any with Exists on List<T>.
+[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExistsInsteadOfAnyFixer)), Shared]
+public sealed class UseExistsInsteadOfAnyFixer : CodeFixProvider
+{
+ ///
+ public override ImmutableArray FixableDiagnosticIds => _fixableDiagnosticIds;
+ private static readonly ImmutableArray _fixableDiagnosticIds = ImmutableArray.Create(UseExistsInsteadOfAny.Descriptor.Id);
+
+ ///
+ [ExcludeFromCodeCoverage]
+ public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
+
+ ///
+ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ if (context.Diagnostics.FirstOrDefault() is not { } diagnostic ||
+ await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root ||
+ root.FindNode(context.Span, getInnermostNodeForTie: true) is not SimpleNameSyntax { Identifier.Text: "Any" } anyName)
+ {
+ return;
+ }
+
+ context.RegisterCodeFix(CodeAction.Create(
+ "Use Exists instead of Any",
+ ct => ReplaceAnyWithExistsAsync(context.Document, anyName),
+ equivalenceKey: "Use Exists instead of Any"),
+ diagnostic);
+ }
+
+ private static async Task ReplaceAnyWithExistsAsync(Document document, SimpleNameSyntax anyName)
+ {
+ var existsIdentifier = SyntaxFactory.IdentifierName("Exists")
+ .WithLeadingTrivia(anyName.GetLeadingTrivia())
+ .WithTrailingTrivia(anyName.GetTrailingTrivia());
+
+ return await document.WithUpdatedRoot(anyName, existsIdentifier).ConfigureAwait(false);
+ }
+}
diff --git a/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.cs b/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.cs
new file mode 100644
index 00000000..dd0230e4
--- /dev/null
+++ b/src/Creedengo.Core/Analyzers/GCI6605.UseExistsInsteadOfAny.cs
@@ -0,0 +1,107 @@
+namespace Creedengo.Core.Analyzers;
+
+/// GCI6605: Use List.Exists instead of LINQ Any with a predicate.
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class UseExistsInsteadOfAny : DiagnosticAnalyzer
+{
+ private static readonly ImmutableArray SyntaxKinds = ImmutableArray.Create(SyntaxKind.InvocationExpression);
+
+ /// The diagnostic descriptor.
+ public static DiagnosticDescriptor Descriptor { get; } = Rule.CreateDescriptor(
+ id: Rule.Ids.GCI6605_UseExistsInsteadOfAny,
+ title: "Use Exists instead of Any",
+ message: "Use 'Exists' instead of 'Any' for improved performance on List",
+ category: Rule.Categories.Performance,
+ severity: DiagnosticSeverity.Warning,
+ description: "List.Exists(predicate) is more performant than the LINQ extension method Enumerable.Any(predicate) for List because it avoids enumerator allocation and interface dispatch overhead.");
+
+ ///
+ public override ImmutableArray SupportedDiagnostics => _supportedDiagnostics;
+ private static readonly ImmutableArray _supportedDiagnostics = ImmutableArray.Create(Descriptor);
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.EnableConcurrentExecution();
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.RegisterCompilationStartAction(static startContext =>
+ {
+ var enumerableType = startContext.Compilation.GetTypeByMetadataName("System.Linq.Enumerable");
+ if (enumerableType is null) return;
+
+ var listType = startContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.List`1");
+ var expressionType = startContext.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1");
+
+ startContext.RegisterSyntaxNodeAction(
+ nodeContext => AnalyzeInvocation(nodeContext, enumerableType, listType, expressionType),
+ SyntaxKinds);
+ });
+ }
+
+ private static void AnalyzeInvocation(
+ SyntaxNodeAnalysisContext context,
+ INamedTypeSymbol enumerableType,
+ INamedTypeSymbol? listType,
+ INamedTypeSymbol? expressionType)
+ {
+ var invocation = (InvocationExpressionSyntax)context.Node;
+
+ // Must be a member access like source.Any(predicate)
+ if (invocation.Expression is not MemberAccessExpressionSyntax { Name.Identifier.Text: "Any" } memberAccess)
+ return;
+
+ // Resolve the method symbol — must be System.Linq.Enumerable.Any with the predicate parameter
+ if (context.SemanticModel.GetSymbolInfo(invocation).Symbol is not IMethodSymbol method ||
+ !method.IsExtensionMethod ||
+ method.Parameters.Length != 1 || // Reduced extension method: only the predicate parameter (excludes parameterless Any())
+ !SymbolEqualityComparer.Default.Equals(method.ContainingType, enumerableType))
+ {
+ return;
+ }
+
+ // Receiver type must be List or derive from it
+ var receiverType = context.SemanticModel.GetTypeInfo(memberAccess.Expression).Type;
+ if (!IsList(receiverType, listType))
+ return;
+
+ // Exclude Expression contexts (e.g. Entity Framework, NoSQL drivers)
+ if (IsInsideExpressionTree(context.SemanticModel, invocation, expressionType))
+ return;
+
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, memberAccess.Name.GetLocation()));
+ }
+
+ private static bool IsList(ITypeSymbol? type, INamedTypeSymbol? listType)
+ {
+ if (type is null || listType is null) return false;
+
+ var current = type;
+ while (current is not null)
+ {
+ if (SymbolEqualityComparer.Default.Equals(current.OriginalDefinition, listType))
+ return true;
+ current = current.BaseType;
+ }
+ return false;
+ }
+
+ private static bool IsInsideExpressionTree(SemanticModel semanticModel, SyntaxNode node, INamedTypeSymbol? expressionType)
+ {
+ if (expressionType is null) return false;
+
+ for (var current = node.Parent; current is not null; current = current.Parent)
+ {
+ if (current is not LambdaExpressionSyntax and not AnonymousMethodExpressionSyntax)
+ continue;
+
+ var typeInfo = semanticModel.GetTypeInfo(current);
+ var convertedType = typeInfo.ConvertedType;
+ if (convertedType is INamedTypeSymbol namedType &&
+ SymbolEqualityComparer.Default.Equals(namedType.OriginalDefinition, expressionType))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/Creedengo.Core/Models/Rule.cs b/src/Creedengo.Core/Models/Rule.cs
index 6cc5d153..38021cfd 100644
--- a/src/Creedengo.Core/Models/Rule.cs
+++ b/src/Creedengo.Core/Models/Rule.cs
@@ -34,6 +34,7 @@ public static class Ids
public const string GCI2508_RemoveUselessToStringCall = "GCI2508";
public const string GCI2333_RemoveRedundantToCharArrayCall = "GCI2333";
public const string GCI98_UseThenByInsteadOfOrderBy = "GCI98";
+ public const string GCI6605_UseExistsInsteadOfAny = "GCI6605";
}
/// Creates a diagnostic descriptor.
diff --git a/src/Creedengo.Sandbox/S6605Sandbox.cs b/src/Creedengo.Sandbox/S6605Sandbox.cs
new file mode 100644
index 00000000..4b891ce6
--- /dev/null
+++ b/src/Creedengo.Sandbox/S6605Sandbox.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Creedengo.Sandbox;
+
+internal class S6605Sandbox
+{
+
+ public bool M1(List ages) => ages.Any(a => IsUnder30(a));
+ public bool M2(List ages) => ages.Exists(a => IsUnder30(a));
+
+ private bool IsUnder30(int age) => age < 30;
+
+}
diff --git a/src/Creedengo.Tests/Tests/GCI6605.UseExistsInsteadOfAny.Tests.cs b/src/Creedengo.Tests/Tests/GCI6605.UseExistsInsteadOfAny.Tests.cs
new file mode 100644
index 00000000..b50ed2d8
--- /dev/null
+++ b/src/Creedengo.Tests/Tests/GCI6605.UseExistsInsteadOfAny.Tests.cs
@@ -0,0 +1,248 @@
+namespace Creedengo.Tests.Tests;
+
+[TestClass]
+public sealed class UseExistsInsteadOfAnyTests
+{
+ private static readonly CodeFixerDlg VerifyAsync = TestRunner.VerifyAsync;
+
+ [TestMethod]
+ public Task EmptyCodeAsync() => VerifyAsync("");
+
+ #region Positive cases (should trigger diagnostic + fix)
+
+ [TestMethod]
+ public Task AnyWithLambdaOnListAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { 1, 2, 3 };
+ return list.[|Any|](x => x > 1);
+ }
+ }
+ """, """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { 1, 2, 3 };
+ return list.Exists(x => x > 1);
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task AnyWithMethodGroupOnListAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { "a", "b" };
+ return list.[|Any|](string.IsNullOrEmpty);
+ }
+
+ private static bool IsValid(string s) => !string.IsNullOrEmpty(s);
+ }
+ """, """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { "a", "b" };
+ return list.Exists(string.IsNullOrEmpty);
+ }
+
+ private static bool IsValid(string s) => !string.IsNullOrEmpty(s);
+ }
+ """);
+
+ [TestMethod]
+ public Task AnyOnDerivedListAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ private sealed class MyList : List;
+
+ public static bool Run()
+ {
+ var list = new MyList { 1, 2, 3 };
+ return list.[|Any|](x => x > 1);
+ }
+ }
+ """, """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ private sealed class MyList : List;
+
+ public static bool Run()
+ {
+ var list = new MyList { 1, 2, 3 };
+ return list.Exists(x => x > 1);
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task AnyInConditionAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static void Run()
+ {
+ var list = new List { 1, 2, 3 };
+ if (list.[|Any|](x => x == 2))
+ Console.WriteLine("found");
+ }
+ }
+ """, """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static void Run()
+ {
+ var list = new List { 1, 2, 3 };
+ if (list.Exists(x => x == 2))
+ Console.WriteLine("found");
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task AnyWithExplicitTypeArgumentAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { 1, 2, 3 };
+ return list.[|Any|](x => x > 1);
+ }
+ }
+ """, """
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { 1, 2, 3 };
+ return list.Exists(x => x > 1);
+ }
+ }
+ """);
+
+ #endregion
+
+ #region Negative cases (should NOT trigger diagnostic)
+
+ [TestMethod]
+ public Task DontTriggerOnParameterlessAnyAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var list = new List { 1, 2, 3 };
+ return list.Any();
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task DontTriggerOnNonListTypesAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ public static class Test
+ {
+ public static void Run()
+ {
+ var enumerable = Enumerable.Range(0, 10);
+ _ = enumerable.Any(x => x > 5);
+
+ var set = new HashSet { 1, 2, 3 };
+ _ = set.Any(x => x > 1);
+
+ var dic = new Dictionary { { 1, "a" } };
+ _ = dic.Any(kv => kv.Key > 0);
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task DontTriggerInsideExpressionTreeAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Linq.Expressions;
+ public static class Test
+ {
+ public static void Run()
+ {
+ var list = new List { 1, 2, 3 };
+ Expression> expr = () => list.Any(x => x > 1);
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task DontTriggerOnCustomAnyMethodAsync() => VerifyAsync("""
+ using System;
+ using System.Collections.Generic;
+ public static class Test
+ {
+ private sealed class MyCollection : List
+ {
+ public bool Any(Func predicate) => true;
+ }
+
+ public static bool Run()
+ {
+ var coll = new MyCollection { 1, 2, 3 };
+ return coll.Any(x => x > 1);
+ }
+ }
+ """);
+
+ [TestMethod]
+ public Task DontTriggerOnArrayAsync() => VerifyAsync("""
+ using System;
+ using System.Linq;
+ public static class Test
+ {
+ public static bool Run()
+ {
+ var arr = new int[] { 1, 2, 3 };
+ return arr.Any(x => x > 1);
+ }
+ }
+ """);
+
+ #endregion
+}