Skip to content
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

List-like union as struct to save allocation + virtual dispatch #472

Merged
merged 6 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion MoreLinq/AggregateRight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public static TResult AggregateRight<TSource, TAccumulate, TResult>(this IEnumer
return resultSelector(source.AggregateRight(seed, func));
}

static TResult AggregateRightImpl<TSource, TResult>(IListLike<TSource> list, TResult accumulator, Func<TSource, TResult, TResult> func, int i)
static TResult AggregateRightImpl<TSource, TResult>(ListLike<TSource> list, TResult accumulator, Func<TSource, TResult, TResult> func, int i)
{
while (i-- > 0)
{
Expand Down
2 changes: 1 addition & 1 deletion MoreLinq/CountDown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public static IEnumerable<TResult> CountDown<T, TResult>(this IEnumerable<T> sou
? IterateCollection(collectionCount)
: IterateSequence();

IEnumerable<TResult> IterateList(IListLike<T> list)
IEnumerable<TResult> IterateList(ListLike<T> list)
{
var countdown = Math.Min(count, list.Count);

Expand Down
58 changes: 33 additions & 25 deletions MoreLinq/ListLike.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,51 @@ namespace MoreLinq
using System.Linq;

/// <summary>
/// Represents an list-like (indexable) data structure.
/// Represents a union over list types implementing either
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of a union, what about a delegating tuple?

static class ListLike 
{
  public static (Func<int> GetCount, Func<int, T> GetItem) => (() => list.Count, (i) => list[i]);
  public static (Func<int> GetCount, Func<int, T> GetItem) => (() => list.Count, (i) => list[i]);
}

Copy link
Member Author

@atifaziz atifaziz Jan 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't syntactically correct, but I think I get your idea nonetheless. This would cause two closures and indirect calls so I have my doubts it's as optimal.

/// <see cref="IList{T}"/> or <see cref="IReadOnlyList{T}"/>, allowing
/// both to be treated the same.
/// </summary>

interface IListLike<out T>
readonly struct ListLike<T>
{
int Count { get; }
T this[int index] { get; }
readonly IList<T>? _rw;
readonly IReadOnlyList<T>? _rx;
atifaziz marked this conversation as resolved.
Show resolved Hide resolved

public ListLike(IList<T> list)
{
_rw = list ?? throw new ArgumentNullException(nameof(list));
_rx = null;
}

public ListLike(IReadOnlyList<T> list)
{
_rw = null;
_rx = list ?? throw new ArgumentNullException(nameof(list));
}

public int Count => _rw?.Count ?? _rx?.Count ?? 0;
atifaziz marked this conversation as resolved.
Show resolved Hide resolved

public T this[int index] => _rw is { } rw ? rw[index]
: _rx is { } rx ? rx[index]
: throw new ArgumentOutOfRangeException(nameof(index));
}

static class ListLike
{
public static IListLike<T> ToListLike<T>(this IEnumerable<T> source)
=> source.TryAsListLike() ?? new List<T>(source.ToList());
public static ListLike<T> AsListLike<T>(this List<T> list) => new((IList<T>)list);
public static ListLike<T> AsListLike<T>(this IList<T> list) => new(list);
public static ListLike<T> AsListLike<T>(this IReadOnlyList<T> list) => new(list);

public static IListLike<T>? TryAsListLike<T>(this IEnumerable<T> source) =>
public static ListLike<T> ToListLike<T>(this IEnumerable<T> source)
=> source.TryAsListLike() ?? source.ToList().AsListLike();

public static ListLike<T>? TryAsListLike<T>(this IEnumerable<T> source) =>
source switch
{
null => throw new ArgumentNullException(nameof(source)),
IList<T> list => new List<T>(list),
IReadOnlyList<T> list => new ReadOnlyList<T>(list),
IList<T> list => list.AsListLike(),
IReadOnlyList<T> list => list.AsListLike(),
_ => null
};

sealed class List<T> : IListLike<T>
{
readonly IList<T> _list;
public List(IList<T> list) => _list = list ?? throw new ArgumentNullException(nameof(list));
public int Count => _list.Count;
public T this[int index] => _list[index];
}

sealed class ReadOnlyList<T> : IListLike<T>
{
readonly IReadOnlyList<T> _list;
public ReadOnlyList(IReadOnlyList<T> list) => _list = list ?? throw new ArgumentNullException(nameof(list));
public int Count => _list.Count;
public T this[int index] => _list[index];
}
}
}
2 changes: 1 addition & 1 deletion MoreLinq/ScanRight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public static IEnumerable<TAccumulate> ScanRight<TSource, TAccumulate>(this IEnu
return ScanRightImpl(source, func, list => (seed, list.Count));
}

static IEnumerable<TResult> ScanRightImpl<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult, TResult> func, Func<IListLike<TSource>, (TResult Seed, int Count)?> seeder)
static IEnumerable<TResult> ScanRightImpl<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult, TResult> func, Func<ListLike<TSource>, (TResult Seed, int Count)?> seeder)
{
var list = source.ToListLike();

Expand Down