admin管理员组

文章数量:1417397

I have a list of fixed count of items, unfortunately I have to use a list instead of an array due to external APIs. I am aware that this list will have a fixed size of X, so when instantiating a list, I use the

public List(int capacity)

constructor.

Now, I would love to switch to collection expression and let the compiler do the heavy lifting for me, and also having cleaner code, but, which version of List constructor will the collection expression compile to? Specific capacity or just random big?

I have a list of fixed count of items, unfortunately I have to use a list instead of an array due to external APIs. I am aware that this list will have a fixed size of X, so when instantiating a list, I use the

public List(int capacity)

constructor.

Now, I would love to switch to collection expression and let the compiler do the heavy lifting for me, and also having cleaner code, but, which version of List constructor will the collection expression compile to? Specific capacity or just random big?

Share Improve this question asked Jan 31 at 9:39 John DemetriouJohn Demetriou 4,4026 gold badges56 silver badges91 bronze badges 3
  • 4 See for yourself. Note that this is an implementation detail. – canton7 Commented Jan 31 at 9:42
  • 3 As an addendum to what @canton7 (correctly) says - the implementation may change depending on your compiler version and what APIs are available on your target framework - but as long as you're using an up-to-date compiler, you can reasonably assume "a pretty good version" (older compiler versions may have just used List<int> list = new List<int>(); list.Add(1);list.Add(2);list.Add(3);list.Add(4); for similar source code, noting that the [...] syntax is itself fairly new, so demands a recent compiler) – Marc Gravell Commented Jan 31 at 9:48
  • 2 here is a similar example using C# 9 that isn't nearly as efficient – Marc Gravell Commented Jan 31 at 9:50
Add a comment  | 

2 Answers 2

Reset to default 10

which version of List constructor will the collection expression compile to?

This depends on which compiler and runtime, and the exact syntax used. If you have a recent compiler (I believe from C# 12 / .NET 8 onwards, but I haven't checked), then the compiler goes far out of it's way to be efficient here. Let's take the example (from @canton7's comment above):

List<int> list = [1, 2, 3, 4, 5];

Assuming this is used against a recent runtime, this goes to great lengths to be efficient; not only using new List<int>(5), but also using nefarious CollectionsMarshal techniques to push the underlying values as efficiently as possible - bypassing the usual list.Add(...) boilerplate, since it understands what is going to happen:

int num = 5;
List<int> list = new List<int>(num);
CollectionsMarshal.SetCount(list, num);
Span<int> span = CollectionsMarshal.AsSpan(list);
int num2 = 0;
span[num2] = 1;
num2++;
span[num2] = 2;
num2++;
span[num2] = 3;
num2++;
span[num2] = 4;
num2++;
span[num2] = 5;
num2++;
List<int> list2 = list;

If the necessary CollectionsMarshal methods aren't available, it will still use the correct initial size.

If we use the older syntax:

var list = new List<int> { 1, 2, 3, 4, 5 };

then this uses the default new List<int>() constructor without the size, and just uses .Add():

List<int> list = new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
list.Add(4);
list.Add(5);
List<int> list2 = list;

This may be due to how the language specification defines this to behave, or may just be to avoid unexpected surprises. You can improve that by manually specifying the size:

var list = new List<int>(5) { 1, 2, 3, 4, 5 };

but then you need to ensure that you're correct!

So: prefer [...] syntax over {...} syntax, and yes: you should be fine.

A collection expression will use the constructor with a capacity parameter if the collection literal has a known length and the target type supports collection initialisers.

From the known length translation section of the feature specification,

  • If T supports collection initializers, then:

    • if the type T contains an accessible constructor with a single parameter int capacity, then the literal is translated as:

      T __result = new T(capacity: __len);
      __result.Add(__e1);
       foreach (var __t in __s1)
          __result.Add(__t);
      
      // further additions of the remaining elements
      

Note: the name of the parameter is required to be capacity.

Here the spec is assuming that __e1 is an expression element (i.e. without the .. prefix) of the collection expression, and __s1 is a spread element (with the .. prefix) of the collection expression.

The definition of "known length" is at the top of that section,

A collection expression has a known length if the compile-time type of each spread element in the collection expression is countable.

And "countable" is defined in the spec for indices and ranges.

A type is Countable if it has a property named Length or Count with an accessible getter and a return type of int.


So something simple like,

List<int> list = [1,2,3,4];

will use the capacity constructor.

But if you use any non-countable type in the collection expression, it will not use the capacity constructor. e.g.

IEnumerable<int> someRandomEnumerable = new int[3];
List<int> list = [..someRandomEnumerable ,4];

本文标签: cWhat version of List constructor is used when defining it with a collection expressionStack Overflow