Description
EDITED 1/31/2024 by @stephentoub
Adding params to existing spans:
namespace System
{
public static class MemoryExtensions
{
public static bool TryWrite(this Span<char> destination, IFormatProvider? provider, CompositeFormat format, out int charsWritten, **params** ReadOnlySpan<object?> args);
}
public class String
{
public static string Format(IFormatProvider? provider, CompositeFormat format, **params** ReadOnlySpan<object?> args);
}
}
namespace System.Collections.Generic
{
public static class CollectionExtensions
{
public static void AddRange<T>(this List<T> list, **params** ReadOnlySpan<T> source);
public static void InsertRange<T>(this List<T> list, int index, **params** ReadOnlySpan<T> source);
}
}
namespace System.Collections.Immutable
{
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(**params** ReadOnlySpan<T> items);
}
public struct ImmutableArray<T>
{
public ImmutableArray<T> AddRange(**params** ReadOnlySpan<T> items);
public ImmutableArray<T> InsertRange(int index, **params** ReadOnlySpan<T> items);
public sealed class Builder
{
public void AddRange(**params** ReadOnlySpan<T> items);
public void AddRange<TDerived>(**params** ReadOnlySpan<TDerived> items) where TDerived : T;
}
}
public static class ImmutableHashSet
{
public static ImmutableHashSet<T> Create<T>(**params** ReadOnlySpan<T> items);
public static ImmutableHashSet<T> Create<T>(IEqualityComparer<T>? equalityComparer, **params** ReadOnlySpan<T> items);
}
public static class ImmutableList
{
public static ImmutableList<T> Create<T>(**params** ReadOnlySpan<T> items);
}
public static class ImmutableQueue
{
public static ImmutableQueue<T> Create<T>(**params** ReadOnlySpan<T> items);
}
public static class ImmutableSortedSet
{
public static ImmutableSortedSet<T> Create<T>(**params** ReadOnlySpan<T> items);
public static ImmutableSortedSet<T> Create<T>(IComparer<T>? comparer, **params** ReadOnlySpan<T> items)
}
public static class ImmutableStack
{
public static ImmutableStack<T> Create<T>(**params** ReadOnlySpan<T> items);
}
}
namespace System.Diagnostics.Metrics
{
public sealed class Counter<T> : Instrument<T> where T : struct
{
public void Add(T delta, **params** ReadOnlySpan<KeyValuePair<string, object?>> tags);
}
public sealed class Histogram<T> : Instrument<T> where T : struct
{
public void Record(T value, **params** ReadOnlySpan<KeyValuePair<string, object?>> tags);
}
public readonly struct Measurement<T> where T : struct
{
public Measurement(T value, **params** ReadOnlySpan<KeyValuePair<string, object?>> tags);
}
public struct TagList : IList<KeyValuePair<string, object?>>, IReadOnlyList<KeyValuePair<string, object?>>
{
public TagList(**params** ReadOnlySpan<KeyValuePair<string, object?>> tagList);
}
public sealed class UpDownCounter<T> : Instrument<T> where T : struct
{
public void Add(T delta, **params** ReadOnlySpan<KeyValuePair<string, object?>> tags);
}
}
namespace System.Text
{
public sealed class StringBuilder
{
public StringBuilder AppendFormat(IFormatProvider? provider, CompositeFormat format, **params** ReadOnlySpan<object?> args);
}
}
Adding new params span-based overloads (all of these have an existing corresponding params T[]
):
namespace System
{
public static class Console
{
public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
public static void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
}
public abstract class Delegate
{
public static Delegate? Combine(params ReadOnlySpan<Delegate?> delegates);
}
public class String
{
public static string Concat(params ReadOnlySpan<object?> args);
public static string Concat(params ReadOnlySpan<string?> values);
public static string Format([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> args);
public static string Format(IFormatProvider? provider, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> args);
public static string Join(char separator, params ReadOnlySpan<string?> value);
public static string Join(string? separator, params ReadOnlySpan<string?> value);
public static string Join(char separator, params ReadOnlySpan<object?> values);
public static string Join(string? separator, params ReadOnlySpan<object?> values);
public string[] Split(params ReadOnlySpan<char> separator);
public unsafe string Trim(params ReadOnlySpan<char> trimChars);
public unsafe string TrimStart(params ReadOnlySpan<char> trimChars);
public unsafe string TrimEnd(params ReadOnlySpan<char> trimChars);
}
}
namespace System.CodeDom.Compiler
{
public class IndentedTextWriter : TextWriter
{
public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
}
}
namespace System.IO
{
public static class Path
{
public static string Combine(params ReadOnlySpan<string> paths);
public static string Join(params ReadOnlySpan<string?> paths);
}
public class StreamWriter : TextWriter
{
public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
}
public abstract class TextWriter
{
public virtual void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
public virtual void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> arg);
}
}
namespace System.Text
{
public class StringBuilder
{
public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<object?> values);
public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<string?> values);
public StringBuilder AppendJoin(char separator, params ReadOnlySpan<object?> values);
public StringBuilder AppendJoin(char separator, params ReadOnlySpan<string?> values);
public StringBuilder AppendFormat([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> args);
public StringBuilder AppendFormat(IFormatProvider? provider, [StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params ReadOnlySpan<object?> args);
}
}
namespace System.Text.Json.Nodes
{
public sealed class JsonArray
{
public JsonArray(params ReadOnlySpan<JsonNode?> items);
public JsonArray(JsonNodeOptions options, params ReadOnlySpan<JsonNode?> items);
}
}
namespace System.Text.Json.Serialization.Metadata
{
public static IJsonTypeInfoResolver Combine(params ReadOnlySpan<IJsonTypeInfoResolver?> resolvers);
}
namespace System.Threading
{
public class CancellationTokenSource
{
public static CancellationTokenSource CreateLinkedTokenSource(params ReadOnlySpan<CancellationToken> tokens);
}
public class Task
{
public static void WaitAll(params ReadOnlySpan<Task> tasks);
public static Task WhenAll(params ReadOnlySpan<Task> tasks);
public static Task<TResult[]> WhenAll<TResult>(params ReadOnlySpan<Task<TResult>> tasks);
public static Task<Task> WhenAny(params ReadOnlySpan<Task> tasks);
public static Task<Task<TResult>> WhenAny<TResult>(params ReadOnlySpan<Task<TResult>> tasks);
}
}
Background and motivation
C# 12 is tentatively on a path to add support for params ReadOnlySpan<T>
, and for that to take precedence over params T[]
. That means in APIs where we currently have a params T[]
-based overload, there’s a benefit to us adding a params ReadOnlySpan<T>
overload: it’ll be preferred by the compiler, and the compiler will aim to stackalloc the span rather than using an array. As a result, any existing call sites for the params-based overload will, upon recompilation, switch to the span-based overload and allocate less.
(It's likely that params in general will be de-emphasized once collection literals also comes to the scene, hopefully also in C# 12. As such, the benefits of adding params here would primarily be about making existing code more efficient. I've not included in this write-up other places we might want additional span-based overloads where there aren't already params T[]
-based overloads; we can continue adding such APIs via separate, dedicated proposals.)
API Proposal
This is a rough strawman, to be refined as the language features are refined.
ImmutableHashSet:
public static ImmutableHashSet<T> Create<T>(params ReadOnlySpan<T> items);
public static ImmutableHashSet<T> Create<T>(IEqualityComparer<T>? comparer, params ReadOnlySpan<T> items) ;
ImmutableList :
public static ImmutableList<T> Create<T>(params ReadOnlySpan<T> items);
ImmutableQueue:
public static ImmutableQueue<T> Create<T>(params ReadOnlySpan<T> items);
ImmutableSortedSet:
public static ImmutableSortedSet<T> Create<T>(params ReadOnlySpan<T> items);
public static ImmutableSortedSet<T> Create<T>(IComparer<T>? comparer, params ReadOnlySpan<T> items) ;
ImmutableStack:
public static ImmutableStack<T> Create<T>(params ReadOnlySpan<T> items);
Console:
public static void Write(string format, params ReadOnlySpan<object?> arg);
public static void WriteLine(string format, params ReadOnlySpan<object?> arg);
Activator:
public static object? CreateInstance(Type type, params ReadOnlySpan<object?> args);
String:
public static string Concat(params ReadOnlySpan<object?> args);
public static string Concat(params ReadOnlySpan<string?> args);
public static string Format(string format, params ReadOnlySpan<object?> args);
public static string Format(IFormatProvider? provider, string format, params ReadOnlySpan<object?> args);
public static string Join(char separator, params ReadOnlySpan<object?> values);
public static string Join(char separator, params ReadOnlySpan<string?> values);
public static string Join(string? separator, params ReadOnlySpan<object?> values);
public static string Join(string? separator, params ReadOnlySpan<string?> values);
Path:
public static string Combine(params ReadOnlySpan<string> paths);
public static string Join(params ReadOnlySpan<string> paths);
TextWriter (and derived types with overrides):
public virtual void Write(string format, params ReadOnlySpan<object?> arg);
public virtual void WriteLine(string format, params ReadOnlySpan<object?> arg);
StringBuilder:
public StringBuilder AppendFormat(string format, params ReadOnlySpan<object?> args);
public StringBuilder AppendJoin(char separator, params ReadOnlySpan<object?> values);
public StringBuilder AppendJoin(char separator, params ReadOnlySpan<string?> values);
public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<object?> values);
public StringBuilder AppendJoin(string? separator, params ReadOnlySpan<object?> values);
CancellationTokenSource:
public static CancellationTokenSource CreateLinkedTokenSource(params ReadOnlySpan<CancellationToken> cancellationToken);
Task:
public static void WaitAll(params ReadOnlySpan<Task> tasks);
public static int WaitAny(params ReadOnlySpan<Task> tasks);
public static Task WhenAll(params ReadOnlySpan<Task> tasks);
public static Task<TResult[]> WhenAll<TResult>(params ReadOnlySpan<Task<TResult>[] tasks);
public static Task<Task> WhenAny(params ReadOnlySpan<Task> tasks);
public static Task<Task<TResult>> WhenAny<TResult>(params ReadOnlySpan<Task<TResult>[] tasks);
JsonArray:
public JsonArray(JsonNodeOptions options, params ReadOnlySpan<JsonNode?> items);
public JsonArray(params ReadOnlySpan<JsonNode?> items);
LoggerExtensions:
All of the BeginScope, Log, LogCritical, LogDebug, LogError, LogInformation, LogTrace, and LogWarning overloads already there, just with `params ReadOnlySpan<object?> args` instead of `params object?[] args`.
Additionally, the following already have ReadOnlySpan<T>
-based overloads where there's also a params T[]
-based overload, so we'd just need to add params
to the existing method:
ImmutableArray:
public static System.Collections.Immutable.ImmutableArray<T> Create<T>( ReadOnlySpan<T> items);
ImmutableArray<T>.Builder:
public void AddRange(ReadOnlySpan<T> items);
Counter<T> (metrics)
public void Add(T delta, ReadOnlySpan<KeyValuePair<string, object?> tags);
UpDownCounter<T> (metrics)
public void Add(T delta, ReadOnlySpan<KeyValuePair<string, object?> tags);
Histogram<T> (metrics)
public void Record(T value, ReadOnlySpan<KeyValuePair<string, object?> tags);
Measurement<T> (metrics)
public Measurement(T value, ReadOnlySpan<KeyValuePair<string, object?> tags);
API Usage
Existing usage.
Alternative Designs
No response
Risks
No response