diff --git a/PolyShim/Net70/Stopwatch.cs b/PolyShim/Net70/Stopwatch.cs new file mode 100644 index 0000000..4fd3419 --- /dev/null +++ b/PolyShim/Net70/Stopwatch.cs @@ -0,0 +1,44 @@ +#if (NETCOREAPP && !NET7_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD) +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] +internal static class MemberPolyfills_Net70_Stopwatch +{ + extension(Stopwatch) + { + // https://learn.microsoft.com/dotnet/api/system.diagnostics.stopwatch.getelapsedtime#system-diagnostics-stopwatch-getelapsedtime(system-int64-system-int64) + public static TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) + { + var tickFrequency = Stopwatch.Frequency; + var ticks = endingTimestamp - startingTimestamp; + + if (tickFrequency == TimeSpan.TicksPerSecond) + { + return new TimeSpan(ticks); + } + else if (tickFrequency > TimeSpan.TicksPerSecond) + { + var ticksPerStopwatchTick = (double)tickFrequency / TimeSpan.TicksPerSecond; + return new TimeSpan((long)(ticks / ticksPerStopwatchTick)); + } + else + { + var ticksPerStopwatchTick = (double)TimeSpan.TicksPerSecond / tickFrequency; + return new TimeSpan((long)(ticks * ticksPerStopwatchTick)); + } + } + + // https://learn.microsoft.com/dotnet/api/system.diagnostics.stopwatch.getelapsedtime#system-diagnostics-stopwatch-getelapsedtime(system-int64) + public static TimeSpan GetElapsedTime(long startingTimestamp) => + Stopwatch.GetElapsedTime(startingTimestamp, Stopwatch.GetTimestamp()); + } +} +#endif diff --git a/PolyShim/Net80/ITimer.cs b/PolyShim/Net80/ITimer.cs new file mode 100644 index 0000000..524c7ea --- /dev/null +++ b/PolyShim/Net80/ITimer.cs @@ -0,0 +1,21 @@ +#if !FEATURE_TIMEPROVIDER +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Threading.Tasks; + +namespace System.Threading; + +// https://learn.microsoft.com/dotnet/api/system.threading.itimer +internal interface ITimer : IDisposable +#if FEATURE_ASYNCINTERFACES + , IAsyncDisposable +#endif +{ + bool Change(TimeSpan dueTime, TimeSpan period); +} +#endif diff --git a/PolyShim/Net80/TimeProvider.cs b/PolyShim/Net80/TimeProvider.cs new file mode 100644 index 0000000..bf4dc54 --- /dev/null +++ b/PolyShim/Net80/TimeProvider.cs @@ -0,0 +1,95 @@ +#if !FEATURE_TIMEPROVIDER +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; + +namespace System; + +// https://learn.microsoft.com/dotnet/api/system.timeprovider +[ExcludeFromCodeCoverage] +internal abstract class TimeProvider +{ + public static TimeProvider System { get; } = new SystemTimeProvider(); + + protected TimeProvider() { } + + public abstract TimeZoneInfo LocalTimeZone { get; } + + public virtual DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow; + + public DateTimeOffset GetLocalNow() + { + var utcDateTime = GetUtcNow(); + var offset = LocalTimeZone.GetUtcOffset(utcDateTime); + return new DateTimeOffset(utcDateTime.DateTime + offset, offset); + } + + public virtual long GetTimestamp() => Stopwatch.GetTimestamp(); + + public virtual TimeSpan GetElapsedTime(long startingTimestamp, long endingTimestamp) => + Stopwatch.GetElapsedTime(startingTimestamp, endingTimestamp); + + public TimeSpan GetElapsedTime(long startingTimestamp) => + GetElapsedTime(startingTimestamp, GetTimestamp()); + +#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER) + public virtual ITimer CreateTimer( + TimerCallback callback, + object? state, + TimeSpan dueTime, + TimeSpan period + ) => new SystemTimeProviderTimer(dueTime, period, callback, state); +#endif + + private sealed class SystemTimeProvider : TimeProvider + { + public override TimeZoneInfo LocalTimeZone => TimeZoneInfo.Local; + } + +#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER) + private sealed class SystemTimeProviderTimer : ITimer + { + private readonly Timer _timer; + + public SystemTimeProviderTimer( + TimeSpan dueTime, + TimeSpan period, + TimerCallback callback, + object? state + ) + { + _timer = new Timer(callback, state, dueTime, period); + } + + public bool Change(TimeSpan dueTime, TimeSpan period) + { + try + { + return _timer.Change(dueTime, period); + } + catch + { + return false; + } + } + + public void Dispose() + { + _timer.Dispose(); + } + +#if FEATURE_ASYNCINTERFACES + public ValueTask DisposeAsync() => _timer.DisposeAsync(); +#endif + } +#endif +} +#endif diff --git a/PolyShim/NetCore10/Task.cs b/PolyShim/NetCore10/Task.cs index b164e74..0879302 100644 --- a/PolyShim/NetCore10/Task.cs +++ b/PolyShim/NetCore10/Task.cs @@ -35,7 +35,21 @@ internal static class MemberPolyfills_NetCore10_Task return tcs.Task; } +#endif + + // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromcanceled + public static Task FromCanceled(CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + tcs.TrySetCanceled(cancellationToken); + return tcs.Task; + } + // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromcanceled + public static Task FromCanceled(CancellationToken cancellationToken) => + Task.FromCanceled(cancellationToken); + +#if NETFRAMEWORK && !NET45_OR_GREATER // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run(system-action-system-threading-cancellationtoken) public static Task Run(Action action, CancellationToken cancellationToken) => Task.Factory.StartNew( @@ -149,6 +163,54 @@ public static Task> WhenAny(IEnumerable> tasks) => // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.whenany#system-threading-tasks-task-whenany-1(system-threading-tasks-task((-0))()) public static Task> WhenAny(params Task[] tasks) => WhenAny((IEnumerable>)tasks); +#endif + +#if NETFRAMEWORK && !NET45_OR_GREATER + // Timer is not available on .NET Standard 1.0 and 1.1 +#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER) + // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.delay#system-threading-tasks-task-delay(system-timespan-system-threading-cancellationtoken) + public static Task Delay(TimeSpan delay, CancellationToken cancellationToken) + { + var tcs = new TaskCompletionSource(); + + if (cancellationToken.IsCancellationRequested) + { + tcs.SetCanceled(); + return tcs.Task; + } + + Timer? timer = null; + CancellationTokenRegistration registration = default; + + void CleanupAndSetResult() + { + registration.Dispose(); + timer?.Dispose(); + tcs.TrySetResult(null); + } + + void CleanupAndSetCanceled() + { + registration.Dispose(); + timer?.Dispose(); + tcs.TrySetCanceled(); + } + + timer = new Timer( + _ => CleanupAndSetResult(), + null, + delay, + TimeSpan.FromMilliseconds(-1) + ); + + registration = cancellationToken.Register(() => CleanupAndSetCanceled()); + + return tcs.Task; + } + + // https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.delay#system-threading-tasks-task-delay(system-timespan) + public static Task Delay(TimeSpan delay) => Task.Delay(delay, CancellationToken.None); +#endif #endif } } diff --git a/PolyShim/NetCore10/TimerCallback.cs b/PolyShim/NetCore10/TimerCallback.cs new file mode 100644 index 0000000..e6cef73 --- /dev/null +++ b/PolyShim/NetCore10/TimerCallback.cs @@ -0,0 +1,12 @@ +#if NETSTANDARD && !NETSTANDARD1_2_OR_GREATER +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +namespace System.Threading; + +// https://learn.microsoft.com/dotnet/api/system.threading.timercallback +internal delegate void TimerCallback(object? state); +#endif diff --git a/PolyShim/NetCore30/Timer.cs b/PolyShim/NetCore30/Timer.cs new file mode 100644 index 0000000..b2ae961 --- /dev/null +++ b/PolyShim/NetCore30/Timer.cs @@ -0,0 +1,30 @@ +#if (NETCOREAPP && !NETCOREAPP3_0_OR_GREATER) || (NETFRAMEWORK) || (NETSTANDARD && !NETSTANDARD2_1_OR_GREATER) +// Timer is not available on .NET Standard 1.0 and 1.1 +#if !(NETSTANDARD && !NETSTANDARD1_2_OR_GREATER) +#nullable enable +// ReSharper disable RedundantUsingDirective +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming +// ReSharper disable PartialTypeWithSinglePart + +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +[ExcludeFromCodeCoverage] +internal static class MemberPolyfills_NetCore30_Timer +{ + extension(Timer timer) + { +#if FEATURE_ASYNCINTERFACES + // https://learn.microsoft.com/dotnet/api/system.threading.timer.disposeasync + public ValueTask DisposeAsync() + { + timer.Dispose(); + return default; + } +#endif + } +} +#endif +#endif diff --git a/PolyShim/PolyShim.csproj b/PolyShim/PolyShim.csproj index a870545..fae85b1 100644 --- a/PolyShim/PolyShim.csproj +++ b/PolyShim/PolyShim.csproj @@ -50,19 +50,19 @@ @@ -144,6 +144,24 @@ PrivateAssets="all" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework' AND $([MSBuild]::VersionEquals($(TargetFrameworkVersion), '4.0'))" /> + + + diff --git a/PolyShim/PolyShim.targets b/PolyShim/PolyShim.targets index 6360611..67077ac 100644 --- a/PolyShim/PolyShim.targets +++ b/PolyShim/PolyShim.targets @@ -302,6 +302,23 @@ $(DefineConstants);FEATURE_VALUETUPLE + + false + true + true + true + $(DefineConstants);FEATURE_TIMEPROVIDER diff --git a/PolyShim/Signatures.md b/PolyShim/Signatures.md index d5c8212..5456834 100644 --- a/PolyShim/Signatures.md +++ b/PolyShim/Signatures.md @@ -1,8 +1,8 @@ # Signatures -- **Total:** 355 -- **Types:** 76 -- **Members:** 279 +- **Total:** 364 +- **Types:** 78 +- **Members:** 286 ___ @@ -187,6 +187,8 @@ ___ - [`TValue? GetValueOrDefault(TKey)`](https://learn.microsoft.com/dotnet/api/system.collections.generic.collectionextensions.getvalueordefault#system-collections-generic-collectionextensions-getvalueordefault-2(system-collections-generic-ireadonlydictionary((-0-1))-0)) .NET Core 2.0 - `IsExternalInit` - [**[class]**](https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.isexternalinit) .NET 5.0 +- `ITimer` + - [**[interface]**](https://learn.microsoft.com/dotnet/api/system.threading.itimer) .NET 8.0 - `KeyValuePair` - [`void Deconstruct(out TKey, out TValue)`](https://learn.microsoft.com/dotnet/api/system.collections.generic.keyvaluepair-2.deconstruct) .NET Core 2.0 - `LibraryImportAttribute` @@ -334,6 +336,9 @@ ___ - [`bool SequenceEqual(ReadOnlySpan)`](https://learn.microsoft.com/dotnet/api/system.memoryextensions.sequenceequal#system-memoryextensions-sequenceequal-1(system-span((-0))-system-readonlyspan((-0)))) .NET Core 2.1 - [`int IndexOf(T)`](https://learn.microsoft.com/dotnet/api/system.memoryextensions.indexof#system-memoryextensions-indexof-1(system-span((-0))-0)) .NET Core 2.1 - [`void Reverse()`](https://learn.microsoft.com/dotnet/api/system.memoryextensions.reverse#system-memoryextensions-reverse-1(system-span((-0)))) .NET Core 2.1 +- `Stopwatch` + - [`TimeSpan GetElapsedTime(long, long)`](https://learn.microsoft.com/dotnet/api/system.diagnostics.stopwatch.getelapsedtime#system-diagnostics-stopwatch-getelapsedtime(system-int64-system-int64)) .NET 7.0 + - [`TimeSpan GetElapsedTime(long)`](https://learn.microsoft.com/dotnet/api/system.diagnostics.stopwatch.getelapsedtime#system-diagnostics-stopwatch-getelapsedtime(system-int64)) .NET 7.0 - `Stream` - [`int Read(Span)`](https://learn.microsoft.com/dotnet/api/system.io.stream.read#system-io-stream-read(system-span((system-byte)))) .NET Core 2.1 - [`int ReadAtLeast(Span, int, bool)`](https://learn.microsoft.com/dotnet/api/system.io.stream.readatleast) .NET 7.0 @@ -401,6 +406,9 @@ ___ - [`IAsyncEnumerable WhenEach(IEnumerable)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.wheneach#system-threading-tasks-task-wheneach(system-collections-generic-ienumerable((system-threading-tasks-task)))) .NET 9.0 - [`IAsyncEnumerable WhenEach(params Task[])`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.wheneach#system-threading-tasks-task-wheneach(system-threading-tasks-task())) .NET 9.0 - [`Task CompletedTask`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.completedtask) .NET Core 1.0 + - [`Task Delay(TimeSpan, CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.delay#system-threading-tasks-task-delay(system-timespan-system-threading-cancellationtoken)) .NET Core 1.0 + - [`Task Delay(TimeSpan)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.delay#system-threading-tasks-task-delay(system-timespan)) .NET Core 1.0 + - [`Task FromCanceled(CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromcanceled) .NET Core 1.0 - [`Task Run(Action, CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run(system-action-system-threading-cancellationtoken)) .NET Core 1.0 - [`Task Run(Action)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run(system-action)) .NET Core 1.0 - [`Task Run(Func, CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run(system-func((system-threading-tasks-task))-system-threading-cancellationtoken)) .NET Core 1.0 @@ -413,6 +421,7 @@ ___ - [`Task FromResult(T?)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromresult) .NET Core 1.0 - [`Task WhenAll(IEnumerable>)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.whenall#system-threading-tasks-task-whenall-1(system-collections-generic-ienumerable((system-threading-tasks-task((-0)))))) .NET Core 1.0 - [`Task WhenAll(params Task[])`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.whenall#system-threading-tasks-task-whenall-1(system-threading-tasks-task((-0))())) .NET Core 1.0 + - [`Task FromCanceled(CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.fromcanceled) .NET Core 1.0 - [`Task Run(Func, CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run-1(system-func((-0))-system-threading-cancellationtoken)) .NET Core 1.0 - [`Task Run(Func)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run-1(system-func((-0)))) .NET Core 1.0 - [`Task Run(Func>, CancellationToken)`](https://learn.microsoft.com/dotnet/api/system.threading.tasks.task.run#system-threading-tasks-task-run-1(system-func((system-threading-tasks-task((-0))))-system-threading-cancellationtoken)) .NET Core 1.0 @@ -441,6 +450,10 @@ ___ - [`void Write(ReadOnlySpan)`](https://learn.microsoft.com/dotnet/api/system.io.textwriter.write#system-io-textwriter-write(system-readonlyspan((system-char)))) .NET Core 2.1 - `ThreadAbortException` - [**[class]**](https://learn.microsoft.com/dotnet/api/system.threading.threadabortexception) .NET Core 2.0 +- `TimeProvider` + - [**[class]**](https://learn.microsoft.com/dotnet/api/system.timeprovider) .NET 8.0 +- `Timer` + - [`ValueTask DisposeAsync()`](https://learn.microsoft.com/dotnet/api/system.threading.timer.disposeasync) .NET Core 3.0 - `TimeSpan` - [`TimeSpan FromMilliseconds(long, long)`](https://learn.microsoft.com/dotnet/api/system.timespan.frommilliseconds#system-timespan-frommilliseconds(system-int64-system-int64)) .NET 9.0 - [`TimeSpan FromMilliseconds(long)`](https://learn.microsoft.com/dotnet/api/system.timespan.frommilliseconds#system-timespan-frommilliseconds(system-int64)) .NET 10.0 diff --git a/Readme.md b/Readme.md index 747e512..31209d7 100644 --- a/Readme.md +++ b/Readme.md @@ -193,6 +193,7 @@ Currently, **PolyShim** recognizes the following packages: - [`Microsoft.Bcl.AsyncInterfaces`](https://nuget.org/packages/Microsoft.Bcl.AsyncInterfaces) — `IAsyncEnumerable`, `IAsyncDisposable`, etc. - [`Microsoft.Bcl.HashCode`](https://nuget.org/packages/Microsoft.Bcl.HashCode) — `HashCode`, etc. - [`Microsoft.Bcl.Memory`](https://nuget.org/packages/Microsoft.Bcl.Memory) — `Index`, `Range`, etc. +- [`Microsoft.Bcl.TimeProvider`](https://nuget.org/packages/Microsoft.Bcl.TimeProvider) — `TimeProvider`, `ITimer`, etc. - [`Microsoft.Net.Http`](https://nuget.org/packages/Microsoft.Net.Http) — `HttpClient`, `HttpContent`, etc. (wider support than the `System.*` variant). For example, adding a reference to the `Microsoft.Bcl.AsyncInterfaces` package will enable **PolyShim**'s polyfills that work with `IAsyncEnumerable`, such as `Task.WhenEach(...)`: