dotnet / dotnet/runtime

[API Proposal]: Add constructor BitArray(IEnumerable<bool> values)

Open
#121,737 11 comments 0 reactions 0 assignees View on GitHub
api-approved area-System.Collections
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

I would like to propose adding a new constructor to `System.Collections.BitArray` that takes an `IEnumerable` as its parameter, similar to other collection types.
This would allow `BitArray` to be instantiated directly from an `IEnumerable`, improving usability and consistency with other collection classes.

### API Proposal

```diff
namespace System.Collections;

public sealed class BitArray
{
+ public BitArray(System.Collections.Generic.IEnumerable values)
}
```

### API Usage

```csharp
IEnumerable enumerable = Enumerable.Range(0, 100).Where(static x => x % 31 == 0).Select(static x => (x & 1) == 0);
BitArray array = new(enumerable);
foreach (bool item in array.OfType())
{
Console.WriteLine(item ? "event" : "odd");
}
```

### Alternative Designs

If this constructor is not available, the following sample code would likely be the most optimal way to initialize a `BitArray`:

The code sample without `BitArray` with `IEnumerable` parameter.

```csharp
BitArray array;
IEnumerable enumerable = // something;
if (Enumerable.TryGetNonEnumeratedCount(enumerable, out int count))
{
array = new BitArray(count);
Span destination = CollectionsMarshal.AsBytes(array);
int index = -1;
foreach (bool sourceItem in enumerable)
{
++index;
if (sourceItem)
{
destination[index >>> 3] |= (byte)(1 << (index & 7));
}
}
}
else
{
List list = enumerable.ToList(); // x8 allocation
array = new BitArray(list.Count);
Span destination = CollectionsMarshal.AsBytes(array);
ReadOnlySpan source = CollectionsMarshal.AsSpan(list);
for (int index = 0; index < source.Length; index++)
{
if (source[index])
{
destination[index >>> 3] |= (byte)(1 << (index & 7));
}
}
}
```

With this constructor available, even if `Enumerable.TryGetNonEnumeratedCount` returns `false` and enumeration is required to determine the count, the temporary expansion buffer could be a bit-packed list instead of a `List`. This would reduce memory usage to one-eighth of the current requirement.

The above sample code is already sufficient for the constructors `BitArray(IEnumerable)` and `BitArray(IEnumerable)`, so adding dedicated constructors for these types would not be necessary.

#### the alternate API

```diff
namespace System.Collections;

public sealed class BitArray
{
+ public static BitArray Create(TSource values) where TSource : System.Collections.Generic.IEnumerable
}
```

This alternative API would have the advantage of avoiding boxing allocations when the `IEnumerable` argument comes from a value-type collection. However, such cases are extremely rare—Unity’s `NativeArray` is probably the only notable example. Given this, I don’t think it’s necessary to optimize the API to that extent, and a straightforward constructor would be perfectly suitable.

### Risks

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.