diff --git a/src/R3/Internal/Shims/CancellationTokenExtensions.cs b/src/R3/Internal/Shims/CancellationTokenExtensions.cs new file mode 100644 index 00000000..aa0c51dc --- /dev/null +++ b/src/R3/Internal/Shims/CancellationTokenExtensions.cs @@ -0,0 +1,13 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace System.Threading; + +internal static class CancellationTokenExtensions +{ + public static CancellationTokenRegistration UnsafeRegister(this CancellationToken cancellationToken, Action callback, object? state) + { + return cancellationToken.Register(callback, state); + } +} + +#endif diff --git a/src/R3/Internal/Shims/CollectionsMarshal.cs b/src/R3/Internal/Shims/CollectionsMarshal.cs new file mode 100644 index 00000000..11e7edc4 --- /dev/null +++ b/src/R3/Internal/Shims/CollectionsMarshal.cs @@ -0,0 +1,24 @@ +using System.Runtime.CompilerServices; + +#if !NET6_0_OR_GREATER +namespace System.Runtime.InteropServices; + +internal static class CollectionsMarshal +{ +#pragma warning disable CS8618 +#pragma warning disable CS0169 +#pragma warning disable CS0649 + + class ListDummy + { + public T[] Items; + int size; + int version; + } + + internal static Span AsSpan(List list) + { + return Unsafe.As>(list).Items.AsSpan(0, list.Count); + } +} +#endif diff --git a/src/R3/Internal/Shims/StackTraceHidden.cs b/src/R3/Internal/Shims/StackTraceHidden.cs new file mode 100644 index 00000000..a8ae2dea --- /dev/null +++ b/src/R3/Internal/Shims/StackTraceHidden.cs @@ -0,0 +1,19 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace System.Diagnostics +{ + /// + /// Types and Methods attributed with StackTraceHidden will be omitted from the stack trace text shown in StackTrace.ToString() + /// and Exception.StackTrace + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)] + internal sealed class StackTraceHiddenAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public StackTraceHiddenAttribute() { } + } +} + +#endif diff --git a/src/R3/Internal/Shims/TaskExtensions.cs b/src/R3/Internal/Shims/TaskExtensions.cs new file mode 100644 index 00000000..085cade8 --- /dev/null +++ b/src/R3/Internal/Shims/TaskExtensions.cs @@ -0,0 +1,33 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace R3; + +internal static class TaskExtensions +{ + internal static Task WaitAsync(this Task task, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + var registration = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)); + + task.ContinueWith(t => + { + registration.Dispose(); + if (t.IsFaulted) + { + tcs.TrySetException(t.Exception!.InnerExceptions); + } + else if (t.IsCanceled) + { + tcs.TrySetCanceled(cancellationToken); + } + else + { + tcs.TrySetResult(t.Result); + } + }, TaskScheduler.Default); + + return tcs.Task; + } +} + +#endif diff --git a/src/R3/Internal/Shims/ThreadPool.cs b/src/R3/Internal/Shims/ThreadPool.cs new file mode 100644 index 00000000..ec532db5 --- /dev/null +++ b/src/R3/Internal/Shims/ThreadPool.cs @@ -0,0 +1,27 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace R3 // namespace priority +{ + public interface IThreadPoolWorkItem + { + void Execute(); + } + + public static class ThreadPool + { + static readonly WaitCallback waitCallback = Execute; + + public static bool UnsafeQueueUserWorkItem(IThreadPoolWorkItem callBack, bool preferLocal) + { + return global::System.Threading.ThreadPool.UnsafeQueueUserWorkItem(waitCallback, callBack); + } + + static void Execute(object? state) + { + var workItem = (IThreadPoolWorkItem)state!; + workItem.Execute(); + } + } +} + +#endif diff --git a/src/R3/Operators/AggregateOperators.cs b/src/R3/Operators/AggregateOperators.cs index a3fc8901..dc6ef6c0 100644 --- a/src/R3/Operators/AggregateOperators.cs +++ b/src/R3/Operators/AggregateOperators.cs @@ -101,6 +101,7 @@ public static Task MaxAsync(this Observable source, CancellationToken c }, cancellationToken); } +#if NET8_0_OR_GREATER public static Task SumAsync(this Observable source, CancellationToken cancellationToken = default) where T : IAdditionOperators @@ -108,7 +109,6 @@ public static Task SumAsync(this Observable source, CancellationToken c return AggregateAsync(source, default(T)!, static (sum, message) => checked(sum + message), Stubs.ReturnSelf, cancellationToken); // ignore complete } - public static Task AverageAsync(this Observable source, CancellationToken cancellationToken = default) where T : INumberBase { @@ -126,6 +126,7 @@ public static Task AverageAsync(this Observable source, Cancellati cancellationToken); } +#endif public static Task WaitAsync(this Observable source, CancellationToken cancellationToken = default) { diff --git a/src/R3/Operators/ContainsAsync.cs b/src/R3/Operators/ContainsAsync.cs index 91829407..247e7631 100644 --- a/src/R3/Operators/ContainsAsync.cs +++ b/src/R3/Operators/ContainsAsync.cs @@ -15,12 +15,12 @@ public static Task ContainsAsync(this Observable source, T value, IE } } -internal sealed class ContainsAsync(T value, IEqualityComparer equalityComparer, CancellationToken cancellationToken) +internal sealed class ContainsAsync(T compareValue, IEqualityComparer equalityComparer, CancellationToken cancellationToken) : TaskObserverBase(cancellationToken) { protected override void OnNextCore(T value) { - if (!equalityComparer.Equals(value)) + if (!equalityComparer.Equals(value, compareValue)) { TrySetResult(true); } diff --git a/src/R3/Operators/ElementAtAsync.cs b/src/R3/Operators/ElementAtAsync.cs index e132efe9..59d2268a 100644 --- a/src/R3/Operators/ElementAtAsync.cs +++ b/src/R3/Operators/ElementAtAsync.cs @@ -11,6 +11,8 @@ public static Task ElementAtAsync(this Observable source, int index, Ca return observer.Task; } +#if !NETSTANDARD2_0 + public static Task ElementAtAsync(this Observable source, Index index, CancellationToken cancellationToken = default) { if (index.IsFromEnd) @@ -26,6 +28,8 @@ public static Task ElementAtAsync(this Observable source, Index index, } } +#endif + public static Task ElementAtOrDefaultAsync(this Observable source, int index, T? defaultValue = default, CancellationToken cancellationToken = default) { if (index < 0) throw new ArgumentOutOfRangeException("index"); @@ -34,6 +38,8 @@ public static Task ElementAtOrDefaultAsync(this Observable source, int return observer.Task; } +#if !NETSTANDARD2_0 + public static Task ElementAtOrDefaultAsync(this Observable source, Index index, T? defaultValue = default, CancellationToken cancellationToken = default) { if (index.IsFromEnd) @@ -47,6 +53,9 @@ public static Task ElementAtOrDefaultAsync(this Observable source, Inde return ElementAtOrDefaultAsync(source, index.Value, defaultValue, cancellationToken); } } + +#endif + } internal sealed class ElementAtAsync(int index, bool useDefaultValue, T? defaultValue, CancellationToken cancellationToken) diff --git a/src/R3/Operators/ObserveOn.cs b/src/R3/Operators/ObserveOn.cs index 8062c490..76af3228 100644 --- a/src/R3/Operators/ObserveOn.cs +++ b/src/R3/Operators/ObserveOn.cs @@ -341,7 +341,7 @@ static void DrainMessages(object? state) var self = (_ObserveOn)state!; var queue = self.queue; - Notification value; + Notification value = default; while (true) { lock (queue) diff --git a/src/R3/Operators/ToAsyncEnumerable.cs b/src/R3/Operators/ToAsyncEnumerable.cs index f7b63da0..0e4e2a3d 100644 --- a/src/R3/Operators/ToAsyncEnumerable.cs +++ b/src/R3/Operators/ToAsyncEnumerable.cs @@ -1,4 +1,6 @@ -using System.Threading.Channels; +#if !NETSTANDARD2_0 + +using System.Threading.Channels; namespace R3; @@ -59,3 +61,5 @@ protected override void DisposeCore() registration.Dispose(); } } + +#endif diff --git a/src/R3/Operators/ToLookupAsync.cs b/src/R3/Operators/ToLookupAsync.cs index a2118d48..4a306872 100644 --- a/src/R3/Operators/ToLookupAsync.cs +++ b/src/R3/Operators/ToLookupAsync.cs @@ -1,4 +1,4 @@ -using System.Xml.Linq; +using System.Collections; namespace R3; diff --git a/src/R3/R3.csproj b/src/R3/R3.csproj index 974ff405..86f9636a 100644 --- a/src/R3/R3.csproj +++ b/src/R3/R3.csproj @@ -1,9 +1,10 @@  - net8.0 + netstandard2.0;netstandard2.1;net6.0;net8.0 enable enable + 12 true true @@ -16,25 +17,23 @@ - - True - True - CombineLatest.tt - - - True - True - Zip.tt - - - True - True - ZipLatest.tt - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + @@ -43,40 +42,40 @@ - - TextTemplatingFileGenerator - CombineLatest.cs - - - TextTemplatingFileGenerator - Zip.cs - - - TextTemplatingFileGenerator - ZipLatest.cs - + + TextTemplatingFileGenerator + CombineLatest.cs + + + TextTemplatingFileGenerator + Zip.cs + + + TextTemplatingFileGenerator + ZipLatest.cs + - + - - True - True - CombineLatest.tt - - - True - True - Zip.tt - - - True - True - ZipLatest.tt - + + True + True + CombineLatest.tt + + + True + True + Zip.tt + + + True + True + ZipLatest.tt + diff --git a/src/R3/SubscriptionTracker.cs b/src/R3/SubscriptionTracker.cs index bc33c987..ad87c8df 100644 --- a/src/R3/SubscriptionTracker.cs +++ b/src/R3/SubscriptionTracker.cs @@ -92,8 +92,7 @@ public static void ForEachActiveTask(Action action) lock (iterateCache) { var count = tracking.CaptureSnapshot(ref iterateCache, clear: false); - var span = CollectionsMarshal.AsSpan(iterateCache).Slice(0, count); - span.Sort(); + iterateCache.Sort(0, count, Comparer.Default); try { for (int i = 0; i < count; i++) @@ -103,7 +102,7 @@ public static void ForEachActiveTask(Action action) } finally { - span.Clear(); + iterateCache.Clear(); } } }