Repository: microsoft/kiota (csharp)

Issue:
Remove LINQ usage from the generated code
To make the generated code performant and amenable to AOT compilation and linker trimming, usage of LINQ should be removed. .NET JIT (which AOT also uses at its build-time backend) gives up while devirtualizing complex queries as it has to account for all possible types of generic from the implementation of LINQ APIs. Replacing LINQ queries with regular loops and conditions outperforms in terms of both time and space in most cases.

background https://github.com/microsoft/kiota-serialization-form-dotnet/issues/142

Pull request:
fix #4732

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 01068b4d5c..30ce596928 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 
 - TypeScript imports are now using ES6 imports with the .js extension.
+- Remove LINQ usage in generated code.
 
 ## [1.15.0] - 2024-06-06
 
diff --git a/it/csharp/dotnet.csproj b/it/csharp/dotnet.csproj
index 1b47f2c09f..a79f9605bd 100644
--- a/it/csharp/dotnet.csproj
+++ b/it/csharp/dotnet.csproj
@@ -21,4 +21,4 @@
     <Compile Remove="basic\**" />
   </ItemGroup>
 
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/Kiota.Builder/Refiners/CSharpRefiner.cs b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
index 5e36de1890..2a78396e7d 100644
--- a/src/Kiota.Builder/Refiners/CSharpRefiner.cs
+++ b/src/Kiota.Builder/Refiners/CSharpRefiner.cs
@@ -171,7 +171,7 @@ protected static void MakeEnumPropertiesNullable(CodeElement currentElement)
         new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.RequestBuilder),
             "System.Threading.Tasks", "Task"),
         new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder),
-            "System.Linq", "Enumerable"),
+            ExtensionsNamespaceName, "Enumerable"),
         new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.ClientConstructor) &&
                     method.Parameters.Any(y => y.IsOfKind(CodeParameterKind.BackingStore)),
             StoreNamespaceName,  "IBackingStoreFactory", "IBackingStoreFactorySingleton"),
diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
index 796e6a91ac..ead9a4484b 100644
--- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
@@ -10,8 +10,8 @@ public class CodeMethodWriter : BaseElementWriter<CodeMethod, CSharpConventionSe
 {
     public CodeMethodWriter(CSharpConventionService conventionService) : base(conventionService)
     {
-
     }
+
     public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer)
     {
         ArgumentNullException.ThrowIfNull(codeElement);
@@ -350,7 +350,7 @@ private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod me
         {
             if (isCollection)
             {
-                var collectionMethod = propType.IsArray ? "?.ToArray()" : "?.ToList()";
+                var collectionMethod = propType.IsArray ? "?.AsArray()" : "?.AsList()";
                 if (currentType.TypeDefinition == null)
                     return $"GetCollectionOfPrimitiveValues<{propertyType}>(){collectionMethod}";
                 else if (currentType.TypeDefinition is CodeEnum)
@@ -407,7 +407,7 @@ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams re
         };
         writer.WriteLine($"{prefix}await RequestAdapter.{GetSendRequestMethodName(isVoid, codeElement, codeElement.ReturnType)}(requestInfo{returnTypeFactory}, {errorMappingVarName}, cancellationToken).ConfigureAwait(false);");
         if (codeElement.ReturnType.IsCollection)
-            writer.WriteLine("return collectionResult?.ToList();");
+            writer.WriteLine("return collectionResult?.AsList();");
     }
     private const string RequestInfoVarName = "requestInfo";
     private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)


Test patch:
diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
index 2fe8182ed3..17fb96c17a 100644
--- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs
@@ -542,7 +542,7 @@ public void WritesRequestExecutorBodyForCollection()
         Assert.Contains("var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>", result);
         Assert.Contains("{ \"4XX\", Error4XX.CreateFromDiscriminatorValue },", result);
         Assert.Contains("SendCollectionAsync", result);
-        Assert.Contains("return collectionResult?.ToList()", result);
+        Assert.Contains("return collectionResult?.AsList()", result);
         Assert.Contains($"{ReturnTypeName}.CreateFromDiscriminatorValue", result);
         AssertExtensions.CurlyBracesAreClosed(result, 1);
     }
@@ -592,7 +592,7 @@ public void WritesModelFactoryBodyForUnionModels()
         Assert.Contains("ComplexType1Value = new ComplexType1()", result);
         Assert.Contains("else if(parseNode.GetStringValue() is string stringValueValue)", result);
         Assert.Contains("StringValue = stringValueValue", result);
-        Assert.Contains("parseNode.GetCollectionOfObjectValues<ComplexType2>(ComplexType2.CreateFromDiscriminatorValue)?.ToList() is List<ComplexType2> complexType2ValueValue", result);
+        Assert.Contains("parseNode.GetCollectionOfObjectValues<ComplexType2>(ComplexType2.CreateFromDiscriminatorValue)?.AsList() is List<ComplexType2> complexType2ValueValue", result);
         Assert.Contains("ComplexType2Value = complexType2ValueValue", result);
         Assert.Contains("return result", result);
         AssertExtensions.Before("GetStringValue() is string stringValueValue", "GetCollectionOfObjectValues<ComplexType2>", result);
@@ -631,7 +631,7 @@ public void WritesModelFactoryBodyForIntersectionModels()
         Assert.DoesNotContain("if(\"#kiota.complexType1\".Equals(mappingValue, StringComparison.OrdinalIgnoreCase))", result);
         Assert.Contains("if(parseNode.GetStringValue() is string stringValueValue)", result);
         Assert.Contains("StringValue = stringValueValue", result);
-        Assert.Contains("parseNode.GetCollectionOfObjectValues<ComplexType2>(ComplexType2.CreateFromDiscriminatorValue)?.ToList() is List<ComplexType2> complexType2ValueValue", result);
+        Assert.Contains("parseNode.GetCollectionOfObjectValues<ComplexType2>(ComplexType2.CreateFromDiscriminatorValue)?.AsList() is List<ComplexType2> complexType2ValueValue", result);
         Assert.Contains("ComplexType2Value = complexType2ValueValue", result);
         Assert.Contains("ComplexType1Value = new ComplexType1()", result);
         Assert.Contains("return result", result);