Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 87 additions & 2 deletions src/Polyfill/Polyfill_List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,75 @@ public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> target) =>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.collectionextensions.addrange?view=net-11.0
public static void AddRange<T>(this List<T> target, ReadOnlySpan<T> source)
{
if (source.IsEmpty)
{
return;
}

var length = source.Length;

// List<T>.AddRange block copies an ICollection<T>. On .NET Framework it goes through an extra
// temporary array, which only pays off when the items can be block copied without GC bookkeeping.
if (length >= blockCopyThreshold
#if !NETCOREAPP
&& typeof(T).IsPrimitive
#endif
)
{
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(length);
try
{
source.CopyTo(buffer);
target.AddRange(new ArraySegment<T>(buffer, 0, length));
}
finally
{
ReturnToPool(pool, buffer, length);
}

return;
}

// Grow once, using the same doubling as List<T>, instead of several times while adding
var required = target.Count + length;
var capacity = target.Capacity;
if (required > capacity)
{
var newCapacity = capacity * 2;
if ((uint) newCapacity > 0x7FEFFFFF)
{
newCapacity = 0x7FEFFFFF;
}

if (newCapacity < required)
{
newCapacity = required;
}

target.Capacity = newCapacity;
}

foreach (var item in source)
{
target.Add(item);
}
}

// Below this the cost of renting a buffer outweighs the block copy
const int blockCopyThreshold = 64;

static void ReturnToPool<T>(System.Buffers.ArrayPool<T> pool, T[] buffer, int length)
{
// Do not keep references alive in the pool
if (!typeof(T).IsPrimitive)
{
Array.Clear(buffer, 0, length);
}

pool.Return(buffer);
}

/// <summary>Inserts the elements of a span into the <see cref="List{T}"/> at the specified index.</summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.collectionextensions.insertrange?view=net-11.0
public static void InsertRange<T>(this List<T> target, int index, ReadOnlySpan<T> source)
Expand All @@ -51,10 +114,32 @@ public static void CopyTo<T>(this List<T> target, Span<T> destination)
throw new ArgumentException("Destination is too short.", nameof(destination));
}

for (var index = 0; index < target.Count; index++)
#if NET5_0_OR_GREATER
System.Runtime.InteropServices.CollectionsMarshal.AsSpan(target).CopyTo(destination);
#else
var count = target.Count;
if (count < blockCopyThreshold)
{
destination[index] = target[index];
for (var index = 0; index < count; index++)
{
destination[index] = target[index];
}

return;
}

var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(count);
try
{
target.CopyTo(0, buffer, 0, count);
new ReadOnlySpan<T>(buffer, 0, count).CopyTo(destination);
}
finally
{
ReturnToPool(pool, buffer, count);
}
#endif
}
#endif

Expand Down
66 changes: 64 additions & 2 deletions src/Split/net461/Polyfill_List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,57 @@ public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> target) =>
/// <summary>Adds the elements of the specified span to the end of the <see cref="List{T}"/>.</summary>
public static void AddRange<T>(this List<T> target, ReadOnlySpan<T> source)
{
if (source.IsEmpty)
{
return;
}
var length = source.Length;
if (length >= blockCopyThreshold
&& typeof(T).IsPrimitive
)
{
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(length);
try
{
source.CopyTo(buffer);
target.AddRange(new ArraySegment<T>(buffer, 0, length));
}
finally
{
ReturnToPool(pool, buffer, length);
}
return;
}
var required = target.Count + length;
var capacity = target.Capacity;
if (required > capacity)
{
var newCapacity = capacity * 2;
if ((uint) newCapacity > 0x7FEFFFFF)
{
newCapacity = 0x7FEFFFFF;
}
if (newCapacity < required)
{
newCapacity = required;
}
target.Capacity = newCapacity;
}
foreach (var item in source)
{
target.Add(item);
}
}
const int blockCopyThreshold = 64;
static void ReturnToPool<T>(System.Buffers.ArrayPool<T> pool, T[] buffer, int length)
{
if (!typeof(T).IsPrimitive)
{
Array.Clear(buffer, 0, length);
}
pool.Return(buffer);
}
/// <summary>Inserts the elements of a span into the <see cref="List{T}"/> at the specified index.</summary>
public static void InsertRange<T>(this List<T> target, int index, ReadOnlySpan<T> source)
{
Expand All @@ -38,9 +84,25 @@ public static void CopyTo<T>(this List<T> target, Span<T> destination)
{
throw new ArgumentException("Destination is too short.", nameof(destination));
}
for (var index = 0; index < target.Count; index++)
var count = target.Count;
if (count < blockCopyThreshold)
{
for (var index = 0; index < count; index++)
{
destination[index] = target[index];
}
return;
}
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(count);
try
{
target.CopyTo(0, buffer, 0, count);
new ReadOnlySpan<T>(buffer, 0, count).CopyTo(destination);
}
finally
{
destination[index] = target[index];
ReturnToPool(pool, buffer, count);
}
}
#endif
Expand Down
66 changes: 64 additions & 2 deletions src/Split/net462/Polyfill_List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,57 @@ public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> target) =>
/// <summary>Adds the elements of the specified span to the end of the <see cref="List{T}"/>.</summary>
public static void AddRange<T>(this List<T> target, ReadOnlySpan<T> source)
{
if (source.IsEmpty)
{
return;
}
var length = source.Length;
if (length >= blockCopyThreshold
&& typeof(T).IsPrimitive
)
{
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(length);
try
{
source.CopyTo(buffer);
target.AddRange(new ArraySegment<T>(buffer, 0, length));
}
finally
{
ReturnToPool(pool, buffer, length);
}
return;
}
var required = target.Count + length;
var capacity = target.Capacity;
if (required > capacity)
{
var newCapacity = capacity * 2;
if ((uint) newCapacity > 0x7FEFFFFF)
{
newCapacity = 0x7FEFFFFF;
}
if (newCapacity < required)
{
newCapacity = required;
}
target.Capacity = newCapacity;
}
foreach (var item in source)
{
target.Add(item);
}
}
const int blockCopyThreshold = 64;
static void ReturnToPool<T>(System.Buffers.ArrayPool<T> pool, T[] buffer, int length)
{
if (!typeof(T).IsPrimitive)
{
Array.Clear(buffer, 0, length);
}
pool.Return(buffer);
}
/// <summary>Inserts the elements of a span into the <see cref="List{T}"/> at the specified index.</summary>
public static void InsertRange<T>(this List<T> target, int index, ReadOnlySpan<T> source)
{
Expand All @@ -38,9 +84,25 @@ public static void CopyTo<T>(this List<T> target, Span<T> destination)
{
throw new ArgumentException("Destination is too short.", nameof(destination));
}
for (var index = 0; index < target.Count; index++)
var count = target.Count;
if (count < blockCopyThreshold)
{
for (var index = 0; index < count; index++)
{
destination[index] = target[index];
}
return;
}
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(count);
try
{
target.CopyTo(0, buffer, 0, count);
new ReadOnlySpan<T>(buffer, 0, count).CopyTo(destination);
}
finally
{
destination[index] = target[index];
ReturnToPool(pool, buffer, count);
}
}
#endif
Expand Down
66 changes: 64 additions & 2 deletions src/Split/net47/Polyfill_List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,57 @@ public static ReadOnlyCollection<T> AsReadOnly<T>(this IList<T> target) =>
/// <summary>Adds the elements of the specified span to the end of the <see cref="List{T}"/>.</summary>
public static void AddRange<T>(this List<T> target, ReadOnlySpan<T> source)
{
if (source.IsEmpty)
{
return;
}
var length = source.Length;
if (length >= blockCopyThreshold
&& typeof(T).IsPrimitive
)
{
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(length);
try
{
source.CopyTo(buffer);
target.AddRange(new ArraySegment<T>(buffer, 0, length));
}
finally
{
ReturnToPool(pool, buffer, length);
}
return;
}
var required = target.Count + length;
var capacity = target.Capacity;
if (required > capacity)
{
var newCapacity = capacity * 2;
if ((uint) newCapacity > 0x7FEFFFFF)
{
newCapacity = 0x7FEFFFFF;
}
if (newCapacity < required)
{
newCapacity = required;
}
target.Capacity = newCapacity;
}
foreach (var item in source)
{
target.Add(item);
}
}
const int blockCopyThreshold = 64;
static void ReturnToPool<T>(System.Buffers.ArrayPool<T> pool, T[] buffer, int length)
{
if (!typeof(T).IsPrimitive)
{
Array.Clear(buffer, 0, length);
}
pool.Return(buffer);
}
/// <summary>Inserts the elements of a span into the <see cref="List{T}"/> at the specified index.</summary>
public static void InsertRange<T>(this List<T> target, int index, ReadOnlySpan<T> source)
{
Expand All @@ -38,9 +84,25 @@ public static void CopyTo<T>(this List<T> target, Span<T> destination)
{
throw new ArgumentException("Destination is too short.", nameof(destination));
}
for (var index = 0; index < target.Count; index++)
var count = target.Count;
if (count < blockCopyThreshold)
{
for (var index = 0; index < count; index++)
{
destination[index] = target[index];
}
return;
}
var pool = System.Buffers.ArrayPool<T>.Shared;
var buffer = pool.Rent(count);
try
{
target.CopyTo(0, buffer, 0, count);
new ReadOnlySpan<T>(buffer, 0, count).CopyTo(destination);
}
finally
{
destination[index] = target[index];
ReturnToPool(pool, buffer, count);
}
}
#endif
Expand Down
Loading
Loading