Skip to content
Open
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
121 changes: 79 additions & 42 deletions src/ObservableCollections/Shims/Collections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,70 +45,107 @@ public static void AddRange<T>(this List<T> list, ReadOnlySpan<T> source)
{
if (!source.IsEmpty)
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.ListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view, checked(view._size + source.Length));
if (!CollectionsMarshal.IsLegacyList)
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.ListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view._items, view._size, checked(view._size + source.Length));
}

source.CopyTo(view._items.AsSpan(view._size));
view._size += source.Length;
view._version++;
}
else
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.LegacyListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view._items, view._size, checked(view._size + source.Length));
}

source.CopyTo(view._items.AsSpan(view._size));
view._size += source.Length;
view._version++;
}

source.CopyTo(view._items.AsSpan(view._size));
view._size += source.Length;
view._version++;
}
}

// CollectionExtensions.InsertRange
public static void InsertRange<T>(this List<T> list, int index, ReadOnlySpan<T> source)
{
if (!source.IsEmpty)
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.ListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view, checked(view._size + source.Length));
{
if (!CollectionsMarshal.IsLegacyList)
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.ListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view._items, view._size, checked(view._size + source.Length));
}

if (index < view._size)
{
Array.Copy(view._items, index, view._items, index + source.Length, view._size - index);
}

source.CopyTo(view._items.AsSpan(index));
view._size += source.Length;
view._version++;
}
else
{
ref var view = ref Unsafe.As<List<T>, CollectionsMarshal.LegacyListView<T>>(ref list!);

if (view._items.Length - view._size < source.Length)
{
Grow(ref view._items, view._size, checked(view._size + source.Length));
}

if (index < view._size)
{
Array.Copy(view._items, index, view._items, index + source.Length, view._size - index);
}

source.CopyTo(view._items.AsSpan(index));
view._size += source.Length;
view._version++;
}

if (index < view._size)
{
Array.Copy(view._items, index, view._items, index + source.Length, view._size - index);
}

source.CopyTo(view._items.AsSpan(index));
view._size += source.Length;
view._version++;
}
}

static void Grow<T>(ref CollectionsMarshal.ListView<T> list, int capacity)
}
static void Grow<T>(ref T[] items, int size, int capacity)
{
SetCapacity(ref list, GetNewCapacity(ref list, capacity));
}

static void SetCapacity<T>(ref CollectionsMarshal.ListView<T> list, int value)
SetCapacity(ref items, size, GetNewCapacity(items.Length, capacity));
}
static void SetCapacity<T>(ref T[] items, int size, int capacity)
{
if (value != list._items.Length)
if (capacity != items.Length)
{
if (value > 0)
if (capacity > 0)
{
T[] newItems = new T[value];
if (list._size > 0)
T[] newItems = new T[capacity];
if (size > 0)
{
Array.Copy(list._items, newItems, list._size);
Array.Copy(items, newItems, size);
}
list._items = newItems;
items = newItems;
}
else
{
list._items = Array.Empty<T>();
items = Array.Empty<T>();
}
}
}

static int GetNewCapacity<T>(ref CollectionsMarshal.ListView<T> list, int capacity)
}
static int GetNewCapacity(int length, int capacity)
{
int newCapacity = list._items.Length == 0 ? 4 : 2 * list._items.Length;
int newCapacity = length == 0 ? 4 : 2 * length;

if ((uint)newCapacity > ArrayMaxLength) newCapacity = ArrayMaxLength;

Expand Down
50 changes: 43 additions & 7 deletions src/ObservableCollections/Shims/CollectionsMarshalEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,59 @@ namespace System.Runtime.InteropServices;

internal static class CollectionsMarshal
{
internal static readonly bool IsLegacyList;

#if NETSTANDARD2_0 || NETSTANDARD2_1
static CollectionsMarshal()
{
int listSize = 0;
try
{
listSize = typeof(List<>).GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).Length;
}
catch
{
listSize = 3;
}

// In .NET Framework, List<T> has a _syncRoot field, so the number of fields becomes 4.
IsLegacyList = listSize == 4;
}
#endif

/// <summary>
/// similar as AsSpan but modify size to create fixed-size span.
/// </summary>
public static Span<T> AsSpan<T>(List<T>? list)
{
if (list is null) return default;

ref var view = ref Unsafe.As<List<T>, ListView<T>>(ref list!);
return view._items.AsSpan(0, view._size);
}

if (list is null) return default;

if (IsLegacyList)
{
ref var view = ref Unsafe.As<List<T>, LegacyListView<T>>(ref list!);
return view._items.AsSpan(0, list.Count);
}
else
{
ref var view = ref Unsafe.As<List<T>, ListView<T>>(ref list!);
return view._items.AsSpan(0, list.Count);
}
}

internal sealed class ListView<T>
{
public T[] _items;
public int _size;
public int _version;
}

internal sealed class LegacyListView<T>
{
public T[] _items;
public int _size;
public int _version;
public Object _syncRoot; // in .NET Framework
}
}

#endif
#endif
Loading