Skip to content

Replace collection length checks for compatibility #4904

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

jake-carpenter
Copy link
Contributor

Using the Length property assumes the collection will be a C# array, but that is based on the parameterArrayType setting for the C# client generator. By default, IEnumerable is used and the majority of other collection types won't accept Length.

For now, this fix utilizes the LINQ Count(), which theoretically should check for Length or Count properties on it's own before enumerating. This seems like it is still an improvement to the enumeration that was present before the linked changeset. I'm open to solving this a different way if there are ways to access the C# type during generation.

There are likely other templates where this is also a problem, but this fix is focused on path parameter generation.

@jake-carpenter
Copy link
Contributor Author

Build failures seem to be based on new mac images with .NET 6 not installed, which is probably not something I should address with code here.

@MoeHamdan
Copy link

MoeHamdan commented Jun 20, 2024

Hello

This works fine only if "Parameter Type" for "Generic Array Type" is set to IEnumerable, however if it set to ICollection it will fail. And by fail I mean it will generate Count() and ElementAt() which are not supported by ICollection

The only way I was able to fix this is by adding namespance System.Linq
image

@Khyalis
Copy link
Contributor

Khyalis commented Apr 19, 2025

I might be a bit late to the discussion.

What I do not understand about the change is, why for some cases (IsDateTimeArray , IsDateArray) that moved from .Length to .Count(), the indexed access remained valid, while for others, it needed to be replaced with .ElementAt().
Does this mean for these cases only types with an index access signature will ever be used?

Also, if targeting IEnumerable as the lowest common denominator (and not trying to differentiate between actual types / targeted frameworks), it might be worthwile to take into consideration that the for loop and its index variable may not be optimal.

  • The main purpose of the index variable appears to be to differentiate between the first and subsequent loop iterations. Otherwise, the intention appears to be to access all collection elements in order.
  • The loop condition (including the .Count()) call may be evaluated for every loop iteration, possibly causing multiple enumerations of the collection.
  • Accessing the "Current" element using .ElementAt() may also need to iterate over all previous Elements

Using a foreach-loop would also work with IEnumerable would also serve to access all collection elements in order; it would only need to iterate over the collection once.
Although additional code might be needed in order to recognize the first element, such as

bool isAfterFirst = false;
foreach (var item in {{ parameter.VariableName }})
{
    if (isAfterFirst) urlBuilder_.Append(',');
    urlBuilder_.Append(ConvertToString(item, System.Globalization.CultureInfo.InvariantCulture));
    isAfterFirst = true;
}

(Working with the enumerator directly (e.g. in order to avoid an additional variable) may not be feasible, with regards to foreach's special treatment of enumerators for which implicit conversions to IDisposable exist, given being oblivious to the actual type in use.)

@lahma
Copy link
Collaborator

lahma commented Apr 20, 2025

@Khyalis maybe you can propose a PR with foreach then? I think the isAfterFirst might also require postfixing with variable name to ensure no conflicts can happen.

@Khyalis
Copy link
Contributor

Khyalis commented Apr 20, 2025

@lahma You're right, depending on whether the parameter is optional, the template may be used outside an isolated block.
Instead of using individual variable names, I could imagine declaring nested blocks (if this does not violate a style guide).

{
   var variableName = ...;
}

(Ensuring a block in Client.Class.liquid might also work.)

I added #5149.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compiler errors in C# client generated code when request has a List<T> as path parameter
5 participants