Created
March 6, 2026 00:58
-
-
Save rent-a-developer/fe62142908ca1c96a9a646f722d46612 to your computer and use it in GitHub Desktop.
C# params: T[] vs. IEnumerable<T> vs. ReadOnlySpan<T>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| class Program | |
| { | |
| static void CallArray() => TakesArray(1, 2, 3); | |
| static void CallEnumerable() => TakesEnumerable(1, 2, 3); | |
| static void CallSpan() => TakesSpan(1, 2, 3); | |
| static void TakesArray(params Object[] args) => _ = 0; | |
| static void TakesEnumerable(params IEnumerable<Object> args) => _ = 0; | |
| static void TakesSpan(params ReadOnlySpan<Object> args) => _ = 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Reflection; | |
| using System.Runtime.CompilerServices; | |
| using System.Runtime.InteropServices; | |
| using System.Security; | |
| using System.Security.Permissions; | |
| [assembly: CompilationRelaxations(8)] | |
| [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] | |
| [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue | DebuggableAttribute.DebuggingModes.DisableOptimizations)] | |
| [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] | |
| [assembly: AssemblyVersion("0.0.0.0")] | |
| [module: UnverifiableCode] | |
| [module: RefSafetyRules(11)] | |
| [CompilerGenerated] | |
| internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, ICollection, IList, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T> | |
| { | |
| [CompilerGenerated] | |
| private readonly T[] _items; | |
| int ICollection.Count | |
| { | |
| get | |
| { | |
| return _items.Length; | |
| } | |
| } | |
| bool ICollection.IsSynchronized | |
| { | |
| get | |
| { | |
| return false; | |
| } | |
| } | |
| object ICollection.SyncRoot | |
| { | |
| [return: Nullable(1)] | |
| get | |
| { | |
| return this; | |
| } | |
| } | |
| object IList.this[int index] | |
| { | |
| [return: Nullable(2)] | |
| get | |
| { | |
| return _items[index]; | |
| } | |
| [param: Nullable(2)] | |
| set | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| } | |
| bool IList.IsFixedSize | |
| { | |
| get | |
| { | |
| return true; | |
| } | |
| } | |
| bool IList.IsReadOnly | |
| { | |
| get | |
| { | |
| return true; | |
| } | |
| } | |
| int IReadOnlyCollection<T>.Count | |
| { | |
| get | |
| { | |
| return _items.Length; | |
| } | |
| } | |
| T IReadOnlyList<T>.this[int index] | |
| { | |
| get | |
| { | |
| return _items[index]; | |
| } | |
| } | |
| int ICollection<T>.Count | |
| { | |
| get | |
| { | |
| return _items.Length; | |
| } | |
| } | |
| bool ICollection<T>.IsReadOnly | |
| { | |
| get | |
| { | |
| return true; | |
| } | |
| } | |
| T IList<T>.this[int index] | |
| { | |
| get | |
| { | |
| return _items[index]; | |
| } | |
| set | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| } | |
| public <>z__ReadOnlyArray(T[] items) | |
| { | |
| _items = items; | |
| } | |
| [return: Nullable(1)] | |
| IEnumerator IEnumerable.GetEnumerator() | |
| { | |
| return ((IEnumerable)_items).GetEnumerator(); | |
| } | |
| void ICollection.CopyTo([Nullable(1)] Array array, int index) | |
| { | |
| ((ICollection)_items).CopyTo(array, index); | |
| } | |
| int IList.Add([Nullable(2)] object value) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| void IList.Clear() | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| bool IList.Contains([Nullable(2)] object value) | |
| { | |
| return ((IList)_items).Contains(value); | |
| } | |
| int IList.IndexOf([Nullable(2)] object value) | |
| { | |
| return ((IList)_items).IndexOf(value); | |
| } | |
| void IList.Insert(int index, [Nullable(2)] object value) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| void IList.Remove([Nullable(2)] object value) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| void IList.RemoveAt(int index) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| [return: Nullable(new byte[] { 1, 0 })] | |
| IEnumerator<T> IEnumerable<T>.GetEnumerator() | |
| { | |
| return ((IEnumerable<T>)_items).GetEnumerator(); | |
| } | |
| void ICollection<T>.Add(T item) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| void ICollection<T>.Clear() | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| bool ICollection<T>.Contains(T item) | |
| { | |
| return ((ICollection<T>)_items).Contains(item); | |
| } | |
| void ICollection<T>.CopyTo([Nullable(new byte[] { 1, 0 })] T[] array, int arrayIndex) | |
| { | |
| ((ICollection<T>)_items).CopyTo(array, arrayIndex); | |
| } | |
| bool ICollection<T>.Remove(T item) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| int IList<T>.IndexOf(T item) | |
| { | |
| return ((IList<T>)_items).IndexOf(item); | |
| } | |
| void IList<T>.Insert(int index, T item) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| void IList<T>.RemoveAt(int index) | |
| { | |
| throw new NotSupportedException(); | |
| } | |
| } | |
| [NullableContext(1)] | |
| [Nullable(0)] | |
| internal class Program | |
| { | |
| private static void CallArray() | |
| { | |
| object[] array = new object[3]; | |
| array[0] = 1; | |
| array[1] = 2; | |
| array[2] = 3; | |
| TakesArray(array); | |
| } | |
| private static void CallEnumerable() | |
| { | |
| object[] array = new object[3]; | |
| array[0] = 1; | |
| array[1] = 2; | |
| array[2] = 3; | |
| TakesEnumerable(new <>z__ReadOnlyArray<object>(array)); | |
| } | |
| private static void CallSpan() | |
| { | |
| InlineArray3<object> buffer = default(InlineArray3<object>); | |
| <PrivateImplementationDetails>.InlineArrayElementRef<InlineArray3<object>, object>(ref buffer, 0) = 1; | |
| <PrivateImplementationDetails>.InlineArrayElementRef<InlineArray3<object>, object>(ref buffer, 1) = 2; | |
| <PrivateImplementationDetails>.InlineArrayElementRef<InlineArray3<object>, object>(ref buffer, 2) = 3; | |
| TakesSpan(<PrivateImplementationDetails>.InlineArrayAsReadOnlySpan<InlineArray3<object>, object>(ref buffer, 3)); | |
| } | |
| private static void TakesArray(params object[] args) | |
| { | |
| } | |
| private static void TakesEnumerable([ParamCollection] IEnumerable<object> args) | |
| { | |
| } | |
| private static void TakesSpan([ParamCollection][ScopedRef][Nullable(new byte[] { 0, 1 })] ReadOnlySpan<object> args) | |
| { | |
| } | |
| } | |
| [CompilerGenerated] | |
| internal sealed class <PrivateImplementationDetails> | |
| { | |
| internal static ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>([In][IsReadOnly] ref TBuffer buffer, int length) | |
| { | |
| return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(ref buffer)), length); | |
| } | |
| internal static ref TElement InlineArrayElementRef<TBuffer, TElement>(ref TBuffer buffer, int index) | |
| { | |
| return ref Unsafe.Add(ref Unsafe.As<TBuffer, TElement>(ref buffer), index); | |
| } | |
| } |
Author
rent-a-developer
commented
Mar 6, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment