From 13994f41f7cc676d1a23fd92852b92716efab418 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Mon, 29 Apr 2019 08:54:16 +0200 Subject: [PATCH 01/53] Use C# 8 --- .travis.yml | 7 +++---- MoreLinq/MoreLinq.csproj | 2 +- global.json | 2 +- msbuild.cmd | 13 +++++-------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 094d6c580..07d62e9e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,16 +5,16 @@ os: osx_image: xcode8.3 solution: MoreLinq.sln mono: 5.0.1 -dist: trusty +dist: xenial sudo: required -dotnet: 2.1.500 +dotnet: 2.2.203 env: - CONFIGURATION=Debug - CONFIGURATION=Release addons: apt: sources: - - sourceline: 'deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-trusty-prod trusty main' + - sourceline: 'deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main' key_url: 'https://packages.microsoft.com/keys/microsoft.asc' packages: - dotnet-hostfxr-1.0.1 @@ -22,7 +22,6 @@ addons: before_install: - dotnet --info - - msbuild /version - | if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then # Handle too many files on OS X diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index c11737d98..8b1b0abe3 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -115,7 +115,7 @@ 3.2.0 MoreLINQ Developers. net451;netstandard1.0;netstandard2.0 - 7.3 + 8 true portable true diff --git a/global.json b/global.json index c8f9e01ca..a9571de35 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.500" + "version": "2.2.200" } } diff --git a/msbuild.cmd b/msbuild.cmd index 3763315de..f24ead479 100644 --- a/msbuild.cmd +++ b/msbuild.cmd @@ -3,8 +3,8 @@ setlocal if "%PROCESSOR_ARCHITECTURE%"=="x86" set PROGRAMS=%ProgramFiles% if defined ProgramFiles(x86) set PROGRAMS=%ProgramFiles(x86)% for %%e in (Community Professional Enterprise) do ( - if exist "%PROGRAMS%\Microsoft Visual Studio\2017\%%e\MSBuild\15.0\Bin\MSBuild.exe" ( - set "MSBUILD=%PROGRAMS%\Microsoft Visual Studio\2017\%%e\MSBuild\15.0\Bin\MSBuild.exe" + if exist "%PROGRAMS%\Microsoft Visual Studio\2019\%%e\MSBuild\Current\Bin\MSBuild.exe" ( + set "MSBUILD=%PROGRAMS%\Microsoft Visual Studio\2019\%%e\MSBuild\Current\Bin\MSBuild.exe" ) ) if exist "%MSBUILD%" goto :build @@ -15,19 +15,16 @@ set MSBUILD_VERSION_MAJOR= set MSBUILD_VERSION_MINOR= for /f "delims=. tokens=1,2,3,4" %%m in ('msbuild /version /nologo') do ( set MSBUILD_VERSION_MAJOR=%%m - set MSBUILD_VERSION_MINOR=%%n ) if not defined MSBUILD_VERSION_MAJOR goto :nomsbuild -if not defined MSBUILD_VERSION_MINOR goto :nomsbuild -if %MSBUILD_VERSION_MAJOR% lss 15 goto :nomsbuild -if %MSBUILD_VERSION_MINOR% lss 1 goto :nomsbuild +if %MSBUILD_VERSION_MAJOR% lss 16 goto :nomsbuild :build "%MSBUILD%" %* goto :EOF :nomsbuild -echo>&2 Microsoft Build Engine 15.1 is required to build the solution. For -echo>&2 installation instructions, see: +echo>&2 Microsoft Build Engine 16.0 or a later version is required to build +echo>&2 the solution. For installation instructions, see: echo>&2 https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio echo>&2 At the very least, you will want to install the MSBuilt Tool workload echo>&2 that has the identifier "Microsoft.VisualStudio.Workload.MSBuildTools": From 9fbb80dbc6d8a6ec7333055d626a717b9ddca27d Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 2 May 2019 18:11:24 +0200 Subject: [PATCH 02/53] Annotate nullability of reference types --- MoreLinq/Assert.cs | 2 +- MoreLinq/Batch.cs | 2 +- MoreLinq/CountBy.cs | 21 +++-- MoreLinq/DistinctBy.cs | 2 +- MoreLinq/EndsWith.cs | 13 +-- MoreLinq/EquiZip.cs | 11 +-- MoreLinq/ExceptBy.cs | 2 +- MoreLinq/Experimental/Await.cs | 24 +++--- MoreLinq/Experimental/Memoize.cs | 10 ++- MoreLinq/Extensions.ToDataTable.g.cs | 2 + MoreLinq/Extensions.g.cs | 86 ++++++++++--------- MoreLinq/FallbackIfEmpty.cs | 12 +-- MoreLinq/FillBackward.cs | 6 +- MoreLinq/FillForward.cs | 6 +- MoreLinq/Flatten.cs | 2 +- MoreLinq/Fold.cs | 64 +++++++------- MoreLinq/FullJoin.cs | 4 +- MoreLinq/GroupAdjacent.cs | 19 ++-- MoreLinq/Interleave.cs | 10 +-- MoreLinq/Lag.cs | 2 +- MoreLinq/Lead.cs | 2 +- MoreLinq/LeftJoin.cs | 4 +- MoreLinq/ListLike.cs | 4 +- MoreLinq/Lookup.cs | 2 + MoreLinq/MaxBy.cs | 10 +-- MoreLinq/MinBy.cs | 6 +- MoreLinq/MoreLinq.csproj | 1 + MoreLinq/OrderBy.cs | 12 +-- MoreLinq/OrderedMerge.cs | 10 +-- MoreLinq/Pad.cs | 6 +- MoreLinq/PadStart.cs | 6 +- MoreLinq/PartialSort.cs | 20 +++-- MoreLinq/Partition.cs | 16 ++-- MoreLinq/PendNode.cs | 10 +-- MoreLinq/Permutations.cs | 22 +++-- MoreLinq/Rank.cs | 4 +- MoreLinq/RightJoin.cs | 4 +- MoreLinq/RunLengthEncode.cs | 6 +- MoreLinq/Segment.cs | 2 +- MoreLinq/SequenceException.cs | 4 +- MoreLinq/SortedMerge.cs | 8 +- MoreLinq/Split.cs | 12 +-- MoreLinq/StartsWith.cs | 6 +- MoreLinq/ToArrayByIndex.cs | 4 +- MoreLinq/ToDataTable.cs | 12 ++- MoreLinq/ToDictionary.cs | 4 +- MoreLinq/ToHashSet.cs | 2 +- MoreLinq/ToLookup.cs | 4 +- MoreLinq/Trace.cs | 8 +- MoreLinq/ZipImpl.cs | 24 +++--- MoreLinq/ZipShortest.cs | 6 +- .../MoreLinq.ExtensionsGenerator.csproj | 4 +- bld/ExtensionsGenerator/Program.cs | 2 + 53 files changed, 287 insertions(+), 260 deletions(-) diff --git a/MoreLinq/Assert.cs b/MoreLinq/Assert.cs index 722313366..1b272d8ed 100644 --- a/MoreLinq/Assert.cs +++ b/MoreLinq/Assert.cs @@ -60,7 +60,7 @@ public static IEnumerable Assert(this IEnumerable sou /// public static IEnumerable Assert(this IEnumerable source, - Func predicate, Func errorSelector) + Func predicate, Func? errorSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); diff --git a/MoreLinq/Batch.cs b/MoreLinq/Batch.cs index 9dc95fa9b..5d063ae30 100644 --- a/MoreLinq/Batch.cs +++ b/MoreLinq/Batch.cs @@ -60,7 +60,7 @@ public static IEnumerable Batch(this IEnumerable _() { - TSource[] bucket = null; + TSource[]? bucket = null; var count = 0; foreach (var item in source) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 11e22ba29..3b2ae0455 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -50,7 +50,7 @@ public static IEnumerable> CountBy(this I /// If null, the default equality comparer for is used. /// A sequence of unique keys and their number of occurrences in the original sequence. - public static IEnumerable> CountBy(this IEnumerable source, Func keySelector, IEqualityComparer comparer) + public static IEnumerable> CountBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -94,8 +94,7 @@ bool TryGetIndex(TKey key, out int i) keys = new List(); counts = new List(); - var havePrevKey = false; - var prevKey = default(TKey); + var prevKey = (false, default(TKey)); var index = 0; foreach (var item in source) @@ -103,10 +102,15 @@ bool TryGetIndex(TKey key, out int i) var key = keySelector(item); if (// key same as the previous? then re-use the index - havePrevKey && cmp.GetHashCode(prevKey) == cmp.GetHashCode(key) - && cmp.Equals(prevKey, key) - // otherwise try & find index of the key - || TryGetIndex(key, out index)) + prevKey switch + { + var (some, pk) + when some => cmp.GetHashCode(pk) == cmp.GetHashCode(key) + && cmp.Equals(pk, key) + // otherwise try & find index of the key + || TryGetIndex(key, out index), + _ => false + }) { counts[index]++; } @@ -121,8 +125,7 @@ bool TryGetIndex(TKey key, out int i) counts.Add(1); } - prevKey = key; - havePrevKey = true; + prevKey = (true, key); } } } diff --git a/MoreLinq/DistinctBy.cs b/MoreLinq/DistinctBy.cs index c8dc010a4..a05faab82 100644 --- a/MoreLinq/DistinctBy.cs +++ b/MoreLinq/DistinctBy.cs @@ -63,7 +63,7 @@ public static IEnumerable DistinctBy(this IEnumerable public static IEnumerable DistinctBy(this IEnumerable source, - Func keySelector, IEqualityComparer comparer) + Func keySelector, IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); diff --git a/MoreLinq/EndsWith.cs b/MoreLinq/EndsWith.cs index c4f8fff76..4a88a0b9b 100644 --- a/MoreLinq/EndsWith.cs +++ b/MoreLinq/EndsWith.cs @@ -66,24 +66,25 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second) /// elements at the same index. /// - public static bool EndsWith(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + public static bool EndsWith(this IEnumerable first, IEnumerable second, IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); - comparer = comparer ?? EqualityComparer.Default; + comparer ??= EqualityComparer.Default; List secondList; return second.TryGetCollectionCount() is int secondCount ? first.TryGetCollectionCount() is int firstCount && secondCount > firstCount ? false - : Impl(second, secondCount) - : Impl(secondList = second.ToList(), secondList.Count); + : Impl(second, secondCount, comparer) + : Impl(secondList = second.ToList(), secondList.Count, comparer); - bool Impl(IEnumerable snd, int count) + bool Impl(IEnumerable snd, int count, IEqualityComparer cmp) { using (var firstIter = first.TakeLast(count).GetEnumerator()) - return snd.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); + return snd.All(item => firstIter.MoveNext() + && cmp.Equals(firstIter.Current, item)); } } } diff --git a/MoreLinq/EquiZip.cs b/MoreLinq/EquiZip.cs index 36d5187bd..623403a5b 100644 --- a/MoreLinq/EquiZip.cs +++ b/MoreLinq/EquiZip.cs @@ -168,15 +168,12 @@ public static IEnumerable EquiZip( } static IEnumerable EquiZipImpl( - IEnumerable s1, - IEnumerable s2, - IEnumerable s3, - IEnumerable s4, + IEnumerable s1, + IEnumerable s2, + IEnumerable? s3, + IEnumerable? s4, Func resultSelector) { - Debug.Assert(s1 != null); - Debug.Assert(s2 != null); - const int zero = 0, one = 1; var limit = 1 + (s3 != null ? one : zero) diff --git a/MoreLinq/ExceptBy.cs b/MoreLinq/ExceptBy.cs index d204ce8b0..d3faa6ea8 100644 --- a/MoreLinq/ExceptBy.cs +++ b/MoreLinq/ExceptBy.cs @@ -73,7 +73,7 @@ public static IEnumerable ExceptBy(this IEnumerable ExceptBy(this IEnumerable first, IEnumerable second, Func keySelector, - IEqualityComparer keyComparer) + IEqualityComparer? keyComparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 229fccbcb..db0ddec6b 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -28,6 +28,7 @@ namespace MoreLinq.Experimental using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; + using Unit = System.ValueTuple; /// /// Represents options for a query whose results evaluate asynchronously. @@ -436,14 +437,14 @@ IEnumerable _(int? maxConcurrency, TaskScheduler scheduler, bool ordere // BlockingCollection.Add throws if called after CompleteAdding // and we want to deliberately tolerate the race condition. - var notices = new BlockingCollection<(Notice, (int, T, Task), ExceptionDispatchInfo)>(); + var notices = new BlockingCollection<(Notice, (int, T, Task), ExceptionDispatchInfo?)>(); var consumerCancellationTokenSource = new CancellationTokenSource(); - (Exception, Exception) lastCriticalErrors = default; + (Exception?, Exception?) lastCriticalErrors = default; void PostNotice(Notice notice, (int, T, Task) item, - Exception error) + Exception? error) { // If a notice fails to post then assume critical error // conditions (like low memory), capture the error without @@ -535,7 +536,10 @@ await enumerator.StartAsync( var (kind, result, error) = notice.Current; if (kind == Notice.Error) + { + Debug.Assert(error != null); error.Throw(); + } if (kind == Notice.End) break; @@ -584,7 +588,7 @@ await enumerator.StartAsync( } } - if (holds?.Count > 0) // yield any withheld, which should be in order... + if (holds != null && holds.Count > 0) // yield any withheld, which should be in order... { foreach (var (key, x, value) in holds) { @@ -688,7 +692,7 @@ static class AwaitQuery public static IAwaitQuery Create( Func> impl, - AwaitQueryOptions options = null) => + AwaitQueryOptions? options = null) => new AwaitQuery(impl, options); } @@ -697,7 +701,7 @@ sealed class AwaitQuery : IAwaitQuery readonly Func> _impl; public AwaitQuery(Func> impl, - AwaitQueryOptions options = null) + AwaitQueryOptions? options = null) { _impl = impl; Options = options ?? AwaitQueryOptions.Default; @@ -735,8 +739,8 @@ static class CompletedTask static CompletedTask() { - var tcs = new TaskCompletionSource(); - tcs.SetResult(null); + var tcs = new TaskCompletionSource(); + tcs.SetResult(default); Instance = tcs.Task; } @@ -751,9 +755,9 @@ sealed class ConcurrencyGate { public static readonly ConcurrencyGate Unbounded = new ConcurrencyGate(); - readonly SemaphoreSlim _semaphore; + readonly SemaphoreSlim? _semaphore; - ConcurrencyGate(SemaphoreSlim semaphore = null) => + ConcurrencyGate(SemaphoreSlim? semaphore = null) => _semaphore = semaphore; public ConcurrencyGate(int max) : diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 65d309fda..51f7358e0 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -20,6 +20,7 @@ namespace MoreLinq.Experimental using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics; using System.Runtime.ExceptionServices; static partial class ExperimentalEnumerable @@ -62,12 +63,12 @@ public static IEnumerable Memoize(this IEnumerable source) sealed class MemoizedEnumerable : IEnumerable, IDisposable { - List _cache; + List? _cache; readonly object _locker; readonly IEnumerable _source; - IEnumerator _sourceEnumerator; + IEnumerator? _sourceEnumerator; int? _errorIndex; - ExceptionDispatchInfo _error; + ExceptionDispatchInfo? _error; public MemoizedEnumerable(IEnumerable sequence) { @@ -115,7 +116,10 @@ public IEnumerator GetEnumerator() if (index >= _cache.Count) { if (index == _errorIndex) + { + Debug.Assert(_error != null); _error.Throw(); + } if (_sourceEnumerator == null) break; diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index 7931061c6..e938397a0 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -17,6 +17,8 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. +#nullable enable // TODO(nullable) review why this is needed + namespace MoreLinq.Extensions { using System; diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index e1927ea37..ddc5a4517 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -17,6 +17,8 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. +#nullable enable // TODO(nullable) review why this is needed + namespace MoreLinq.Extensions { using System; @@ -196,7 +198,7 @@ public static IEnumerable Assert(this IEnumerable sou /// public static IEnumerable Assert(this IEnumerable source, - Func predicate, Func errorSelector) + Func predicate, Func? errorSelector) => MoreEnumerable.Assert(source, predicate, errorSelector); } @@ -862,7 +864,7 @@ public static IEnumerable> CountBy(this I /// If null, the default equality comparer for is used. /// A sequence of unique keys and their number of occurrences in the original sequence. - public static IEnumerable> CountBy(this IEnumerable source, Func keySelector, IEqualityComparer comparer) + public static IEnumerable> CountBy(this IEnumerable source, Func keySelector, IEqualityComparer? comparer) => MoreEnumerable.CountBy(source, keySelector, comparer); } @@ -951,7 +953,7 @@ public static IEnumerable DistinctBy(this IEnumerable public static IEnumerable DistinctBy(this IEnumerable source, - Func keySelector, IEqualityComparer comparer) + Func keySelector, IEqualityComparer? comparer) => MoreEnumerable.DistinctBy(source, keySelector, comparer); } @@ -1002,7 +1004,7 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second) /// elements at the same index. /// - public static bool EndsWith(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + public static bool EndsWith(this IEnumerable first, IEnumerable second, IEqualityComparer? comparer) => MoreEnumerable.EndsWith(first, second, comparer); } @@ -1241,7 +1243,7 @@ public static IEnumerable ExceptBy(this IEnumerable ExceptBy(this IEnumerable first, IEnumerable second, Func keySelector, - IEqualityComparer keyComparer) + IEqualityComparer? keyComparer) => MoreEnumerable.ExceptBy(first, second, keySelector, keyComparer); } @@ -1650,7 +1652,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func selector) + public static IEnumerable Flatten(this IEnumerable source, Func selector) => MoreEnumerable.Flatten(source, selector); } @@ -2220,7 +2222,7 @@ public static IEnumerable FullJoin( Func firstSelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.FullJoin(first, second, keySelector, firstSelector, secondSelector, bothSelector, comparer); /// @@ -2315,7 +2317,7 @@ public static IEnumerable FullJoin( Func firstSelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.FullJoin(first, second, firstKeySelector, secondKeySelector, firstSelector, secondSelector, bothSelector, comparer); } @@ -2380,7 +2382,7 @@ public static IEnumerable> GroupAdjacent public static IEnumerable> GroupAdjacent( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, comparer); /// @@ -2483,7 +2485,7 @@ public static IEnumerable> GroupAdjacent source, Func keySelector, Func elementSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, elementSelector, comparer); /// @@ -2519,7 +2521,7 @@ public static IEnumerable GroupAdjacent( this IEnumerable source, Func keySelector, Func, TResult> resultSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, resultSelector, comparer); } @@ -2836,7 +2838,7 @@ public static IEnumerable LeftJoin( Func keySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.LeftJoin(first, second, keySelector, firstSelector, bothSelector, comparer); /// @@ -2921,7 +2923,7 @@ public static IEnumerable LeftJoin( Func secondKeySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.LeftJoin(first, second, firstKeySelector, secondKeySelector, firstSelector, bothSelector, comparer); } @@ -2972,7 +2974,7 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// is empty public static IExtremaEnumerable MaxBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) => MoreEnumerable.MaxBy(source, selector, comparer); } @@ -3022,7 +3024,7 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// is empty public static IExtremaEnumerable MinBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) => MoreEnumerable.MinBy(source, selector, comparer); } @@ -3091,7 +3093,7 @@ public static IOrderedEnumerable OrderBy(this IEnumerable source, /// A comparer used to define the semantics of element comparison /// An ordered copy of the source sequence - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer comparer, OrderByDirection direction) + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer, OrderByDirection direction) => MoreEnumerable.OrderBy(source, keySelector, comparer, direction); } @@ -3145,7 +3147,7 @@ public static IEnumerable OrderedMerge( public static IEnumerable OrderedMerge( this IEnumerable first, IEnumerable second, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.OrderedMerge(first, second, comparer); /// @@ -3249,7 +3251,7 @@ public static IEnumerable OrderedMerge( Func firstSelector, Func secondSelector, Func bothSelector, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.OrderedMerge(first, second, keySelector, firstSelector, secondSelector, bothSelector, comparer); /// @@ -3338,7 +3340,7 @@ public static IEnumerable OrderedMerge( Func firstSelector, Func secondSelector, Func bothSelector, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.OrderedMerge(first, second, firstKeySelector, secondKeySelector, firstSelector, secondSelector, bothSelector, comparer); } @@ -3613,7 +3615,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// public static IEnumerable PartialSort(this IEnumerable source, - int count, IComparer comparer) + int count, IComparer? comparer) => MoreEnumerable.PartialSort(source, count, comparer); /// @@ -3635,7 +3637,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// public static IEnumerable PartialSort(this IEnumerable source, - int count, IComparer comparer, OrderByDirection direction) + int count, IComparer? comparer, OrderByDirection direction) => MoreEnumerable.PartialSort(source, count, comparer, direction); } @@ -3708,7 +3710,7 @@ public static IEnumerable PartialSortBy( public static IEnumerable PartialSortBy( this IEnumerable source, int count, Func keySelector, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.PartialSortBy(source, count, keySelector, comparer); /// @@ -3733,7 +3735,7 @@ public static IEnumerable PartialSortBy( public static IEnumerable PartialSortBy( this IEnumerable source, int count, Func keySelector, - IComparer comparer, + IComparer? comparer, OrderByDirection direction) => MoreEnumerable.PartialSortBy(source, count, keySelector, comparer, direction); @@ -3907,7 +3909,7 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key, IEqualityComparer comparer, + TKey key, IEqualityComparer? comparer, Func, IEnumerable>, TResult> resultSelector) => MoreEnumerable.Partition(source, key, comparer, resultSelector); @@ -3961,7 +3963,7 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key1, TKey key2, IEqualityComparer comparer, + TKey key1, TKey key2, IEqualityComparer? comparer, Func, IEnumerable, IEnumerable>, TResult> resultSelector) => MoreEnumerable.Partition(source, key1, key2, comparer, resultSelector); @@ -3990,7 +3992,7 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key1, TKey key2, TKey key3, IEqualityComparer comparer, + TKey key1, TKey key2, TKey key3, IEqualityComparer? comparer, Func, IEnumerable, IEnumerable, IEnumerable>, TResult> resultSelector) => MoreEnumerable.Partition(source, key1, key2, key3, comparer, resultSelector); } @@ -4214,7 +4216,7 @@ public static IEnumerable RankBy(this IEnumerable s /// An object that defines the comparison semantics for keys used to rank items /// A sequence of position integers representing the ranks of the corresponding items in the sequence - public static IEnumerable RankBy(this IEnumerable source, Func keySelector, IComparer comparer) + public static IEnumerable RankBy(this IEnumerable source, Func keySelector, IComparer? comparer) => MoreEnumerable.RankBy(source, keySelector, comparer); } @@ -4327,7 +4329,7 @@ public static IEnumerable RightJoin( Func keySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.RightJoin(first, second, keySelector, secondSelector, bothSelector, comparer); /// @@ -4412,7 +4414,7 @@ public static IEnumerable RightJoin( Func secondKeySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.RightJoin(first, second, firstKeySelector, secondKeySelector, secondSelector, bothSelector, comparer); } @@ -4443,7 +4445,7 @@ public static IEnumerable> RunLengthEncode(this IEnumera /// The comparer used to identify equivalent items /// A sequence of KeyValuePair{T,int} where they key is the element and the value is the occurrence count - public static IEnumerable> RunLengthEncode(this IEnumerable sequence, IEqualityComparer comparer) + public static IEnumerable> RunLengthEncode(this IEnumerable sequence, IEqualityComparer? comparer) => MoreEnumerable.RunLengthEncode(sequence, comparer); } @@ -4848,7 +4850,7 @@ public static IEnumerable SortedMerge(this IEnumerableA variable argument array of zero or more other sequences to merge with /// A merged, order-preserving sequence containing al of the elements of the original sequences - public static IEnumerable SortedMerge(this IEnumerable source, OrderByDirection direction, IComparer comparer, params IEnumerable[] otherSequences) + public static IEnumerable SortedMerge(this IEnumerable source, OrderByDirection direction, IComparer? comparer, params IEnumerable[] otherSequences) => MoreEnumerable.SortedMerge(source, direction, comparer, otherSequences); } @@ -4910,7 +4912,7 @@ public static IEnumerable> Split(this IEnumerable< /// A sequence of splits of elements. public static IEnumerable> Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer) + TSource separator, IEqualityComparer? comparer) => MoreEnumerable.Split(source, separator, comparer); /// @@ -4942,7 +4944,7 @@ public static IEnumerable> Split(this IEnumerable< /// A sequence of splits of elements. public static IEnumerable> Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer, int count) + TSource separator, IEqualityComparer? comparer, int count) => MoreEnumerable.Split(source, separator, comparer, count); /// @@ -5067,7 +5069,7 @@ public static IEnumerable Split(this IEnumerable public static IEnumerable Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer, int count, + TSource separator, IEqualityComparer? comparer, int count, Func, TResult> resultSelector) => MoreEnumerable.Split(source, separator, comparer, count, resultSelector); @@ -5121,7 +5123,7 @@ public static bool StartsWith(this IEnumerable first, IEnumerable secon /// of elements at the same index. /// - public static bool StartsWith(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + public static bool StartsWith(this IEnumerable first, IEnumerable second, IEqualityComparer? comparer) => MoreEnumerable.StartsWith(first, second, comparer); } @@ -5349,7 +5351,7 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnumerable s /// A comparer used to define the semantics of element comparison /// An ordered copy of the source sequence - public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer comparer, OrderByDirection direction) + public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer? comparer, OrderByDirection direction) => MoreEnumerable.ThenBy(source, keySelector, comparer, direction); } @@ -5927,7 +5929,7 @@ public static partial class ToDictionaryExtension /// public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ToDictionary(source, comparer); /// @@ -5945,7 +5947,7 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ToDictionary(source, comparer); } @@ -5983,7 +5985,7 @@ public static HashSet ToHashSet(this IEnumerable sour /// This evaluates the input sequence completely. /// - public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer comparer) + public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer? comparer) => MoreEnumerable.ToHashSet(source, comparer); } @@ -6037,7 +6039,7 @@ public static partial class ToLookupExtension /// public static ILookup ToLookup(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ToLookup(source, comparer); /// @@ -6055,7 +6057,7 @@ public static ILookup ToLookup(this IEnumerable<(TKe /// public static ILookup ToLookup(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ToLookup(source, comparer); } @@ -6099,7 +6101,7 @@ public static IEnumerable Trace(this IEnumerable sour /// streams the results. /// - public static IEnumerable Trace(this IEnumerable source, string format) + public static IEnumerable Trace(this IEnumerable source, string? format) => MoreEnumerable.Trace(source, format); /// diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index 7d0c6915a..a22dc1011 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -45,7 +45,7 @@ static partial class MoreEnumerable public static IEnumerable FallbackIfEmpty(this IEnumerable source, T fallback) { if (source == null) throw new ArgumentNullException(nameof(source)); - return FallbackIfEmptyImpl(source, 1, fallback, default, default, default, null); + return FallbackIfEmptyImpl(source, 1, fallback, default!, default!, default!, null); } /// @@ -66,7 +66,7 @@ public static IEnumerable FallbackIfEmpty(this IEnumerable source, T fa public static IEnumerable FallbackIfEmpty(this IEnumerable source, T fallback1, T fallback2) { if (source == null) throw new ArgumentNullException(nameof(source)); - return FallbackIfEmptyImpl(source, 2, fallback1, fallback2, default, default, null); + return FallbackIfEmptyImpl(source, 2, fallback1, fallback2, default!, default!, null); } /// @@ -89,7 +89,7 @@ public static IEnumerable FallbackIfEmpty(this IEnumerable source, T fa public static IEnumerable FallbackIfEmpty(this IEnumerable source, T fallback1, T fallback2, T fallback3) { if (source == null) throw new ArgumentNullException(nameof(source)); - return FallbackIfEmptyImpl(source, 3, fallback1, fallback2, fallback3, default, null); + return FallbackIfEmptyImpl(source, 3, fallback1, fallback2, fallback3, default!, null); } /// @@ -154,12 +154,12 @@ public static IEnumerable FallbackIfEmpty(this IEnumerable source, IEnu { if (source == null) throw new ArgumentNullException(nameof(source)); if (fallback == null) throw new ArgumentNullException(nameof(fallback)); - return FallbackIfEmptyImpl(source, null, default, default, default, default, fallback); + return FallbackIfEmptyImpl(source, null, default!, default!, default!, default!, fallback); } static IEnumerable FallbackIfEmptyImpl(IEnumerable source, int? count, T fallback1, T fallback2, T fallback3, T fallback4, - IEnumerable fallback) + IEnumerable? fallback) { return source.TryGetCollectionCount() is int collectionCount ? collectionCount == 0 ? Fallback() : source @@ -185,7 +185,7 @@ IEnumerable Fallback() { switch (count) { - case null: return fallback; + case null: return fallback!; case int n when n >= 1 && n <= 4: return FallbackOnArgs(); default: throw new ArgumentOutOfRangeException(nameof(count), count, null); } diff --git a/MoreLinq/FillBackward.cs b/MoreLinq/FillBackward.cs index 900265f02..8a731cbd0 100644 --- a/MoreLinq/FillBackward.cs +++ b/MoreLinq/FillBackward.cs @@ -105,9 +105,9 @@ public static IEnumerable FillBackward(this IEnumerable source, Func FillBackwardImpl(IEnumerable source, Func predicate, Func fillSelector) + static IEnumerable FillBackwardImpl(IEnumerable source, Func predicate, Func? fillSelector) { - List blanks = null; + List? blanks = null; foreach (var item in source) { @@ -133,7 +133,7 @@ static IEnumerable FillBackwardImpl(IEnumerable source, Func p } } - if (blanks?.Count > 0) + if (blanks != null && blanks.Count > 0) { foreach (var blank in blanks) yield return blank; diff --git a/MoreLinq/FillForward.cs b/MoreLinq/FillForward.cs index f3561d1fd..0745a82f5 100644 --- a/MoreLinq/FillForward.cs +++ b/MoreLinq/FillForward.cs @@ -104,7 +104,7 @@ public static IEnumerable FillForward(this IEnumerable source, Func FillForwardImpl(IEnumerable source, Func predicate, Func fillSelector) + static IEnumerable FillForwardImpl(IEnumerable source, Func predicate, Func? fillSelector) { var seeded = false; var seed = default(T); @@ -114,8 +114,8 @@ static IEnumerable FillForwardImpl(IEnumerable source, Func pr { yield return seeded ? fillSelector != null - ? fillSelector(item, seed) - : seed + ? fillSelector(item, seed!) + : seed! : item; } else diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index e98466c3e..a715fdc37 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -88,7 +88,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func selector) + public static IEnumerable Flatten(this IEnumerable source, Func selector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); diff --git a/MoreLinq/Fold.cs b/MoreLinq/Fold.cs index bc242d758..ae4337292 100644 --- a/MoreLinq/Fold.cs +++ b/MoreLinq/Fold.cs @@ -23,22 +23,22 @@ namespace MoreLinq static partial class MoreEnumerable { static TResult FoldImpl(IEnumerable source, int count, - Func folder1 = null, - Func folder2 = null, - Func folder3 = null, - Func folder4 = null, - Func folder5 = null, - Func folder6 = null, - Func folder7 = null, - Func folder8 = null, - Func folder9 = null, - Func folder10 = null, - Func folder11 = null, - Func folder12 = null, - Func folder13 = null, - Func folder14 = null, - Func folder15 = null, - Func folder16 = null + Func? folder1 = null, + Func? folder2 = null, + Func? folder3 = null, + Func? folder4 = null, + Func? folder5 = null, + Func? folder6 = null, + Func? folder7 = null, + Func? folder8 = null, + Func? folder9 = null, + Func? folder10 = null, + Func? folder11 = null, + Func? folder12 = null, + Func? folder13 = null, + Func? folder14 = null, + Func? folder15 = null, + Func? folder16 = null ) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -69,22 +69,22 @@ static TResult FoldImpl(IEnumerable source, int count, switch (count) { - case 1: return folder1 (elements[0]); - case 2: return folder2 (elements[0], elements[1]); - case 3: return folder3 (elements[0], elements[1], elements[2]); - case 4: return folder4 (elements[0], elements[1], elements[2], elements[3]); - case 5: return folder5 (elements[0], elements[1], elements[2], elements[3], elements[4]); - case 6: return folder6 (elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]); - case 7: return folder7 (elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6]); - case 8: return folder8 (elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7]); - case 9: return folder9 (elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8]); - case 10: return folder10(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9]); - case 11: return folder11(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10]); - case 12: return folder12(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11]); - case 13: return folder13(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12]); - case 14: return folder14(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13]); - case 15: return folder15(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14]); - case 16: return folder16(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14], elements[15]); + case 1: return folder1 !(elements[0]); + case 2: return folder2 !(elements[0], elements[1]); + case 3: return folder3 !(elements[0], elements[1], elements[2]); + case 4: return folder4 !(elements[0], elements[1], elements[2], elements[3]); + case 5: return folder5 !(elements[0], elements[1], elements[2], elements[3], elements[4]); + case 6: return folder6 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]); + case 7: return folder7 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6]); + case 8: return folder8 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7]); + case 9: return folder9 !(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8]); + case 10: return folder10!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9]); + case 11: return folder11!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10]); + case 12: return folder12!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11]); + case 13: return folder13!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12]); + case 14: return folder14!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13]); + case 15: return folder15!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14]); + case 16: return folder16!(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10], elements[11], elements[12], elements[13], elements[14], elements[15]); default: throw new NotSupportedException(); } } diff --git a/MoreLinq/FullJoin.cs b/MoreLinq/FullJoin.cs index eacc8d21a..613e39a8d 100644 --- a/MoreLinq/FullJoin.cs +++ b/MoreLinq/FullJoin.cs @@ -113,7 +113,7 @@ public static IEnumerable FullJoin( Func firstSelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return first.FullJoin(second, @@ -218,7 +218,7 @@ public static IEnumerable FullJoin( Func firstSelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); diff --git a/MoreLinq/GroupAdjacent.cs b/MoreLinq/GroupAdjacent.cs index b24521998..5c06a08e9 100644 --- a/MoreLinq/GroupAdjacent.cs +++ b/MoreLinq/GroupAdjacent.cs @@ -83,7 +83,7 @@ public static IEnumerable> GroupAdjacent public static IEnumerable> GroupAdjacent( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -160,7 +160,7 @@ public static IEnumerable> GroupAdjacent source, Func keySelector, Func elementSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -246,7 +246,7 @@ public static IEnumerable GroupAdjacent( this IEnumerable source, Func keySelector, Func, TResult> resultSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -265,36 +265,31 @@ static IEnumerable GroupAdjacentImpl( Func, TResult> resultSelector, IEqualityComparer comparer) { - Debug.Assert(source != null); - Debug.Assert(keySelector != null); - Debug.Assert(elementSelector != null); - Debug.Assert(resultSelector != null); - Debug.Assert(comparer != null); using (var iterator = source.GetEnumerator()) { var group = default(TKey); - var members = (List) null; + var members = (List?) null; while (iterator.MoveNext()) { var key = keySelector(iterator.Current); var element = elementSelector(iterator.Current); - if (members != null && comparer.Equals(group, key)) + if (members != null && comparer.Equals(group!, key)) { members.Add(element); } else { if (members != null) - yield return resultSelector(group, members); + yield return resultSelector(group!, members); group = key; members = new List { element }; } } if (members != null) - yield return resultSelector(group, members); + yield return resultSelector(group!, members); } } diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index cd19e19a6..344887e65 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -83,13 +83,13 @@ static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInte var sequences = new[] { sequence }.Concat(otherSequences); // produce an iterator collection for all IEnumerable instancess passed to us - var iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); - List> iteratorList = null; + IEnumerator[]? iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); + List>? iteratorList = null; try { iteratorList = new List>(iterators); - iterators = null; + iterators = null; // disown var shouldContinue = true; var consumedIterators = 0; var iterCount = iteratorList.Count; @@ -115,7 +115,7 @@ static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInte switch (imbalanceStrategy) { case ImbalancedInterleaveStrategy.Pad: - var newIter = iteratorList[index] = Generate(default(T), x => default).GetEnumerator(); + var newIter = iteratorList[index] = Generate(default(T)!, x => default!).GetEnumerator(); newIter.MoveNext(); break; @@ -143,7 +143,7 @@ static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInte finally { Debug.Assert(iteratorList != null || iterators != null); - foreach (var iter in (iteratorList ?? (IList>) iterators)) + foreach (var iter in iteratorList ?? (IList>) iterators!) iter.Dispose(); } } diff --git a/MoreLinq/Lag.cs b/MoreLinq/Lag.cs index 0356dc3c1..95f5440bd 100644 --- a/MoreLinq/Lag.cs +++ b/MoreLinq/Lag.cs @@ -38,7 +38,7 @@ public static partial class MoreEnumerable public static IEnumerable Lag(this IEnumerable source, int offset, Func resultSelector) { - return Lag(source, offset, default, resultSelector); + return Lag(source, offset, default!, resultSelector); } /// diff --git a/MoreLinq/Lead.cs b/MoreLinq/Lead.cs index eaf9c0e2b..57e79c90a 100644 --- a/MoreLinq/Lead.cs +++ b/MoreLinq/Lead.cs @@ -39,7 +39,7 @@ public static partial class MoreEnumerable public static IEnumerable Lead(this IEnumerable source, int offset, Func resultSelector) { - return Lead(source, offset, default, resultSelector); + return Lead(source, offset, default!, resultSelector); } /// diff --git a/MoreLinq/LeftJoin.cs b/MoreLinq/LeftJoin.cs index 648071726..2dd1a068b 100644 --- a/MoreLinq/LeftJoin.cs +++ b/MoreLinq/LeftJoin.cs @@ -103,7 +103,7 @@ public static IEnumerable LeftJoin( Func keySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return first.LeftJoin(second, @@ -198,7 +198,7 @@ public static IEnumerable LeftJoin( Func secondKeySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); diff --git a/MoreLinq/ListLike.cs b/MoreLinq/ListLike.cs index 45f99cc71..e47206de8 100644 --- a/MoreLinq/ListLike.cs +++ b/MoreLinq/ListLike.cs @@ -36,11 +36,11 @@ static class ListLike public static IListLike ToListLike(this IEnumerable source) => source.TryAsListLike() ?? new List(source.ToList()); - public static IListLike TryAsListLike(this IEnumerable source) + public static IListLike? TryAsListLike(this IEnumerable source) => source is null ? throw new ArgumentNullException(nameof(source)) : source is IList list ? new List(list) : source is IReadOnlyList readOnlyList ? new ReadOnlyList(readOnlyList) - : (IListLike) null; + : (IListLike?) null; sealed class List : IListLike { diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 7c873a64e..c576d99a2 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -24,6 +24,8 @@ // SOFTWARE. #endregion +#nullable disable + namespace MoreLinq { using System; diff --git a/MoreLinq/MaxBy.cs b/MoreLinq/MaxBy.cs index 24b00f669..0d8dcda27 100644 --- a/MoreLinq/MaxBy.cs +++ b/MoreLinq/MaxBy.cs @@ -209,13 +209,13 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// is empty public static IExtremaEnumerable MaxBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); - comparer = comparer ?? Comparer.Default; - return new ExtremaEnumerable(source, selector, (x, y) => comparer.Compare(x, y)); + var cmp = comparer ?? Comparer.Default; + return new ExtremaEnumerable(source, selector, (x, y) => cmp.Compare(x, y)); } sealed class ExtremaEnumerable : IExtremaEnumerable @@ -343,8 +343,8 @@ IEnumerable Extrema() abstract class Extrema { - public virtual TStore New() => default; - public virtual void Restart(ref TStore store) => store = default; + public virtual TStore New() => default!; + public virtual void Restart(ref TStore store) => store = default!; public void Add(ref TStore store, int? limit, T item) { diff --git a/MoreLinq/MinBy.cs b/MoreLinq/MinBy.cs index 38a30937c..18dba71fa 100644 --- a/MoreLinq/MinBy.cs +++ b/MoreLinq/MinBy.cs @@ -64,13 +64,13 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// is empty public static IExtremaEnumerable MinBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); - comparer = comparer ?? Comparer.Default; - return new ExtremaEnumerable(source, selector, (x, y) => -Math.Sign(comparer.Compare(x, y))); + var cmp = comparer ?? Comparer.Default; + return new ExtremaEnumerable(source, selector, (x, y) => -Math.Sign(cmp.Compare(x, y))); } } } diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 8b1b0abe3..bc73c5cef 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -116,6 +116,7 @@ MoreLINQ Developers. net451;netstandard1.0;netstandard2.0 8 + enable true portable true diff --git a/MoreLinq/OrderBy.cs b/MoreLinq/OrderBy.cs index 4e5ce8565..9b5c97e18 100644 --- a/MoreLinq/OrderBy.cs +++ b/MoreLinq/OrderBy.cs @@ -49,13 +49,13 @@ public static IOrderedEnumerable OrderBy(this IEnumerable source, /// A comparer used to define the semantics of element comparison /// An ordered copy of the source sequence - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer comparer, OrderByDirection direction) + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, IComparer? comparer, OrderByDirection direction) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return direction == OrderByDirection.Ascending - ? source.OrderBy(keySelector, comparer) - : source.OrderByDescending(keySelector, comparer); + ? source.OrderBy(keySelector, comparer) + : source.OrderByDescending(keySelector, comparer); } /// @@ -84,13 +84,13 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnumerable s /// A comparer used to define the semantics of element comparison /// An ordered copy of the source sequence - public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer comparer, OrderByDirection direction) + public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, IComparer? comparer, OrderByDirection direction) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return direction == OrderByDirection.Ascending - ? source.ThenBy(keySelector, comparer) - : source.ThenByDescending(keySelector, comparer); + ? source.ThenBy(keySelector, comparer) + : source.ThenByDescending(keySelector, comparer); } } } diff --git a/MoreLinq/OrderedMerge.cs b/MoreLinq/OrderedMerge.cs index 003a888c6..30f6eacc7 100644 --- a/MoreLinq/OrderedMerge.cs +++ b/MoreLinq/OrderedMerge.cs @@ -68,7 +68,7 @@ public static IEnumerable OrderedMerge( public static IEnumerable OrderedMerge( this IEnumerable first, IEnumerable second, - IComparer comparer) + IComparer? comparer) { return OrderedMerge(first, second, e => e, f => f, s => s, (a, _) => a, comparer); } @@ -178,7 +178,7 @@ public static IEnumerable OrderedMerge( Func firstSelector, Func secondSelector, Func bothSelector, - IComparer comparer) + IComparer? comparer) { if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); // Argument name changes to 'firstKeySelector' return OrderedMerge(first, second, keySelector, keySelector, firstSelector, secondSelector, bothSelector, comparer); @@ -272,7 +272,7 @@ public static IEnumerable OrderedMerge( Func firstSelector, Func secondSelector, Func bothSelector, - IComparer comparer) + IComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); @@ -282,7 +282,7 @@ public static IEnumerable OrderedMerge( if (bothSelector == null) throw new ArgumentNullException(nameof(bothSelector)); if (secondSelector == null) throw new ArgumentNullException(nameof(secondSelector)); - comparer = comparer ?? Comparer.Default; + var cmp = comparer ?? Comparer.Default; return _(); IEnumerable _() { using (var e1 = first.GetEnumerator()) @@ -299,7 +299,7 @@ public static IEnumerable OrderedMerge( var key1 = firstKeySelector(element1); var element2 = e2.Current; var key2 = secondKeySelector(element2); - var comparison = comparer.Compare(key1, key2); + var comparison = cmp.Compare(key1, key2); if (comparison < 0) { diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index d45f2f380..1557e50dc 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -48,7 +48,7 @@ static partial class MoreEnumerable public static IEnumerable Pad(this IEnumerable source, int width) { - return Pad(source, width, default(TSource)); + return Pad(source, width, default(TSource)!); } /// @@ -111,11 +111,11 @@ public static IEnumerable Pad(this IEnumerable source if (source == null) throw new ArgumentNullException(nameof(source)); if (paddingSelector == null) throw new ArgumentNullException(nameof(paddingSelector)); if (width < 0) throw new ArgumentException(null, nameof(width)); - return PadImpl(source, width, default, paddingSelector); + return PadImpl(source, width, default!, paddingSelector); } static IEnumerable PadImpl(IEnumerable source, - int width, T padding, Func paddingSelector) + int width, T padding, Func? paddingSelector) { Debug.Assert(source != null); Debug.Assert(width >= 0); diff --git a/MoreLinq/PadStart.cs b/MoreLinq/PadStart.cs index db676b082..bd5e7a5a0 100644 --- a/MoreLinq/PadStart.cs +++ b/MoreLinq/PadStart.cs @@ -47,7 +47,7 @@ static partial class MoreEnumerable public static IEnumerable PadStart(this IEnumerable source, int width) { - return PadStart(source, width, default(TSource)); + return PadStart(source, width, default(TSource)!); } /// @@ -112,11 +112,11 @@ public static IEnumerable PadStart(this IEnumerable s if (source == null) throw new ArgumentNullException(nameof(source)); if (paddingSelector == null) throw new ArgumentNullException(nameof(paddingSelector)); if (width < 0) throw new ArgumentException(null, nameof(width)); - return PadStartImpl(source, width, default, paddingSelector); + return PadStartImpl(source, width, default!, paddingSelector); } static IEnumerable PadStartImpl(IEnumerable source, - int width, T padding, Func paddingSelector) + int width, T padding, Func? paddingSelector) { return source.TryGetCollectionCount() is int collectionCount diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index 27df35d80..9b69dc8d7 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -82,7 +82,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// public static IEnumerable PartialSort(this IEnumerable source, - int count, IComparer comparer) + int count, IComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return PartialSortByImpl(source, count, null, null, comparer); @@ -107,9 +107,9 @@ public static IEnumerable PartialSort(this IEnumerable source, /// public static IEnumerable PartialSort(this IEnumerable source, - int count, IComparer comparer, OrderByDirection direction) + int count, IComparer? comparer, OrderByDirection direction) { - comparer = comparer ?? Comparer.Default; + comparer ??= Comparer.Default; if (direction == OrderByDirection.Descending) { comparer = new ReverseComparer(comparer); } @@ -182,7 +182,7 @@ public static IEnumerable PartialSortBy( public static IEnumerable PartialSortBy( this IEnumerable source, int count, Func keySelector, - IComparer comparer) + IComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -211,10 +211,10 @@ public static IEnumerable PartialSortBy( public static IEnumerable PartialSortBy( this IEnumerable source, int count, Func keySelector, - IComparer comparer, + IComparer? comparer, OrderByDirection direction) { - comparer = comparer ?? Comparer.Default; + comparer ??= Comparer.Default; if (direction == OrderByDirection.Descending) { comparer = new ReverseComparer(comparer); } @@ -223,8 +223,9 @@ public static IEnumerable PartialSortBy( static IEnumerable PartialSortByImpl( IEnumerable source, int count, - Func keySelector, - IComparer keyComparer, IComparer comparer) + Func? keySelector, + IComparer? keyComparer, + IComparer? comparer) { Debug.Assert(source != null); @@ -237,6 +238,7 @@ static IEnumerable PartialSortByImpl( var key = default(TKey); if (keys != null) { + Debug.Assert(keySelector != null); key = keySelector(item); i = keys.BinarySearch(key, keyComparer); } @@ -256,7 +258,7 @@ static IEnumerable PartialSortByImpl( // TODO Stable sorting - keys?.Insert(i, key); + keys?.Insert(i, key!); top.Insert(i, item); } diff --git a/MoreLinq/Partition.cs b/MoreLinq/Partition.cs index eb7750c49..723857960 100644 --- a/MoreLinq/Partition.cs +++ b/MoreLinq/Partition.cs @@ -174,11 +174,11 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key, IEqualityComparer comparer, + TKey key, IEqualityComparer? comparer, Func, IEnumerable>, TResult> resultSelector) { if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return PartitionImpl(source, 1, key, default, default, comparer, + return PartitionImpl(source, 1, key, default!, default!, comparer, (a, b, c, rest) => resultSelector(a, rest)); } @@ -232,11 +232,11 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key1, TKey key2, IEqualityComparer comparer, + TKey key1, TKey key2, IEqualityComparer? comparer, Func, IEnumerable, IEnumerable>, TResult> resultSelector) { if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return PartitionImpl(source, 2, key1, key2, default, comparer, + return PartitionImpl(source, 2, key1, key2, default!, comparer, (a, b, c, rest) => resultSelector(a, b, rest)); } @@ -292,12 +292,12 @@ public static TResult Partition(this IEnumerable public static TResult Partition(this IEnumerable> source, - TKey key1, TKey key2, TKey key3, IEqualityComparer comparer, + TKey key1, TKey key2, TKey key3, IEqualityComparer? comparer, Func, IEnumerable, IEnumerable, IEnumerable>, TResult> resultSelector) => PartitionImpl(source, 3, key1, key2, key3, comparer, resultSelector); static TResult PartitionImpl(IEnumerable> source, - int count, TKey key1, TKey key2, TKey key3, IEqualityComparer comparer, + int count, TKey key1, TKey key2, TKey key3, IEqualityComparer? comparer, Func, IEnumerable, IEnumerable, IEnumerable>, TResult> resultSelector) { Debug.Assert(count > 0 && count <= 3); @@ -305,9 +305,9 @@ static TResult PartitionImpl(IEnumerable.Default; + comparer ??= EqualityComparer.Default; - List> etc = null; + List>? etc = null; var groups = new[] { diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index 28ebe8c42..9da48e859 100644 --- a/MoreLinq/PendNode.cs +++ b/MoreLinq/PendNode.cs @@ -62,7 +62,7 @@ sealed class Source : PendNode public IEnumerator GetEnumerator() { var i = 0; - T[] concats = null; // Array for > 4 concatenations + T[]? concats = null; // Array for > 4 concatenations var concat1 = default(T); // Slots for up to 4 concatenations var concat2 = default(T); var concat3 = default(T); @@ -108,10 +108,10 @@ public IEnumerator GetEnumerator() if (concats == null) { - if (i == 4) { yield return concat4; i--; } - if (i == 3) { yield return concat3; i--; } - if (i == 2) { yield return concat2; i--; } - if (i == 1) { yield return concat1; i--; } + if (i == 4) { yield return concat4!; i--; } + if (i == 3) { yield return concat3!; i--; } + if (i == 2) { yield return concat2!; i--; } + if (i == 1) { yield return concat1!; i--; } yield break; } diff --git a/MoreLinq/Permutations.cs b/MoreLinq/Permutations.cs index 4482e41ed..9adff3be1 100644 --- a/MoreLinq/Permutations.cs +++ b/MoreLinq/Permutations.cs @@ -73,6 +73,8 @@ class PermutationEnumerator : IEnumerator> IEnumerator _generatorIterator; bool _hasMoreResults; + IList? _current; + public PermutationEnumerator(IEnumerable valueSet) { _valueSet = valueSet.ToArray(); @@ -81,30 +83,34 @@ public PermutationEnumerator(IEnumerable valueSet) // 1) for empty sets and sets of cardinality 1, there exists only a single permutation. // 2) for sets larger than 1 element, the number of nested loops needed is: set.Count-1 _generator = NestedLoops(NextPermutation, Enumerable.Range(2, Math.Max(0, _valueSet.Count - 1))); - Reset(); + Reset(ref _current, ref _generatorIterator, ref _hasMoreResults); } - public void Reset() + public void Reset() => + Reset(ref _current, ref _generatorIterator, ref _hasMoreResults); + + void Reset(ref IList? current, ref IEnumerator generatorIterator, ref bool hasMoreResults) { - _generatorIterator?.Dispose(); + current = null; + generatorIterator?.Dispose(); // restore lexographic ordering of the permutation indexes for (var i = 0; i < _permutation.Length; i++) _permutation[i] = i; // start a newiteration over the nested loop generator - _generatorIterator = _generator.GetEnumerator(); + generatorIterator = _generator.GetEnumerator(); // we must advance the nestedloop iterator to the initial element, // this ensures that we only ever produce N!-1 calls to NextPermutation() - _generatorIterator.MoveNext(); - _hasMoreResults = true; // there's always at least one permutation: the original set itself + generatorIterator.MoveNext(); + hasMoreResults = true; // there's always at least one permutation: the original set itself } - public IList Current { get; private set; } + public IList Current => _current!; object IEnumerator.Current => Current; public bool MoveNext() { - Current = PermuteValueSet(); + _current = PermuteValueSet(); // check if more permutation left to enumerate var prevResult = _hasMoreResults; _hasMoreResults = _generatorIterator.MoveNext(); diff --git a/MoreLinq/Rank.cs b/MoreLinq/Rank.cs index d2c806ad0..7a91cb8fd 100644 --- a/MoreLinq/Rank.cs +++ b/MoreLinq/Rank.cs @@ -72,12 +72,12 @@ public static IEnumerable RankBy(this IEnumerable s /// An object that defines the comparison semantics for keys used to rank items /// A sequence of position integers representing the ranks of the corresponding items in the sequence - public static IEnumerable RankBy(this IEnumerable source, Func keySelector, IComparer comparer) + public static IEnumerable RankBy(this IEnumerable source, Func keySelector, IComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - comparer = comparer ?? Comparer.Default; + comparer ??= Comparer.Default; return _(); IEnumerable _() { source = source.ToArray(); // avoid enumerating source twice diff --git a/MoreLinq/RightJoin.cs b/MoreLinq/RightJoin.cs index 3c7fa1738..babbe28c1 100644 --- a/MoreLinq/RightJoin.cs +++ b/MoreLinq/RightJoin.cs @@ -102,7 +102,7 @@ public static IEnumerable RightJoin( Func keySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return first.RightJoin(second, @@ -197,7 +197,7 @@ public static IEnumerable RightJoin( Func secondKeySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); diff --git a/MoreLinq/RunLengthEncode.cs b/MoreLinq/RunLengthEncode.cs index cf9ee29fa..b49962cde 100644 --- a/MoreLinq/RunLengthEncode.cs +++ b/MoreLinq/RunLengthEncode.cs @@ -45,12 +45,12 @@ public static IEnumerable> RunLengthEncode(this IEnumera /// The comparer used to identify equivalent items /// A sequence of KeyValuePair{T,int} where they key is the element and the value is the occurrence count - public static IEnumerable> RunLengthEncode(this IEnumerable sequence, IEqualityComparer comparer) + public static IEnumerable> RunLengthEncode(this IEnumerable sequence, IEqualityComparer? comparer) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); - comparer = comparer ?? EqualityComparer.Default; + var cmp = comparer ?? EqualityComparer.Default; return _(); IEnumerable> _() { // This implementation could also have been written using a foreach loop, @@ -66,7 +66,7 @@ public static IEnumerable> RunLengthEncode(this IEnumera while (iter.MoveNext()) { - if (comparer.Equals(prevItem, iter.Current)) + if (cmp.Equals(prevItem, iter.Current)) { ++runCount; } diff --git a/MoreLinq/Segment.cs b/MoreLinq/Segment.cs index 3100d6085..010888689 100644 --- a/MoreLinq/Segment.cs +++ b/MoreLinq/Segment.cs @@ -97,7 +97,7 @@ public static IEnumerable> Segment(this IEnumerable source, { ++index; // check if the item represents the start of a new segment - var isNewSegment = newSegmentPredicate(iter.Current, prevItem, index); + var isNewSegment = newSegmentPredicate(iter.Current, prevItem!, index); prevItem = iter.Current; if (!isNewSegment) diff --git a/MoreLinq/SequenceException.cs b/MoreLinq/SequenceException.cs index 07fcca571..65a025c92 100644 --- a/MoreLinq/SequenceException.cs +++ b/MoreLinq/SequenceException.cs @@ -46,7 +46,7 @@ public SequenceException() : /// /// A message that describes the error. - public SequenceException(string message) : + public SequenceException(string? message) : this(message, null) { } /// @@ -57,7 +57,7 @@ public SequenceException(string message) : /// A message that describes the error. /// The exception that is the cause of the current exception. - public SequenceException(string message, Exception innerException) : + public SequenceException(string? message, Exception? innerException) : base(string.IsNullOrEmpty(message) ? DefaultMessage : message, innerException) { } #if !NO_EXCEPTION_SERIALIZATION diff --git a/MoreLinq/SortedMerge.cs b/MoreLinq/SortedMerge.cs index 705528324..401f0c3d6 100644 --- a/MoreLinq/SortedMerge.cs +++ b/MoreLinq/SortedMerge.cs @@ -65,7 +65,7 @@ public static IEnumerable SortedMerge(this IEnumerableA variable argument array of zero or more other sequences to merge with /// A merged, order-preserving sequence containing al of the elements of the original sequences - public static IEnumerable SortedMerge(this IEnumerable source, OrderByDirection direction, IComparer comparer, params IEnumerable[] otherSequences) + public static IEnumerable SortedMerge(this IEnumerable source, OrderByDirection direction, IComparer? comparer, params IEnumerable[] otherSequences) { if (source == null) throw new ArgumentNullException(nameof(source)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); @@ -73,14 +73,14 @@ public static IEnumerable SortedMerge(this IEnumerable.Default; + var cmp = comparer ?? Comparer.Default; // define an precedence function based on the comparer and direction // this is a function that will return True if (b) should precede (a) var precedenceFunc = direction == OrderByDirection.Ascending - ? (Func)((a, b) => comparer.Compare(b, a) < 0) - : (a, b) => comparer.Compare(b, a) > 0; + ? (Func)((a, b) => cmp.Compare(b, a) < 0) + : (a, b) => cmp.Compare(b, a) > 0; // return the sorted merge result return Impl(new[] { source }.Concat(otherSequences)); diff --git a/MoreLinq/Split.cs b/MoreLinq/Split.cs index b4481661a..bc2975bda 100644 --- a/MoreLinq/Split.cs +++ b/MoreLinq/Split.cs @@ -108,7 +108,7 @@ public static IEnumerable Split(this IEnumerableA sequence of splits of elements. public static IEnumerable> Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer) + TSource separator, IEqualityComparer? comparer) { return Split(source, separator, comparer, int.MaxValue); } @@ -127,7 +127,7 @@ public static IEnumerable> Split(this IEnumerable< /// A sequence of splits of elements. public static IEnumerable> Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer, int count) + TSource separator, IEqualityComparer? comparer, int count) { return Split(source, separator, comparer, count, s => s); } @@ -175,15 +175,15 @@ public static IEnumerable Split(this IEnumerable public static IEnumerable Split(this IEnumerable source, - TSource separator, IEqualityComparer comparer, int count, + TSource separator, IEqualityComparer? comparer, int count, Func, TResult> resultSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - comparer = comparer ?? EqualityComparer.Default; - return Split(source, item => comparer.Equals(item, separator), count, resultSelector); + var cmp = comparer ?? EqualityComparer.Default; + return Split(source, item => cmp.Equals(item, separator), count, resultSelector); } /// @@ -275,7 +275,7 @@ public static IEnumerable Split(this IEnumerable items = null; + List? items = null; foreach (var item in source) { diff --git a/MoreLinq/StartsWith.cs b/MoreLinq/StartsWith.cs index e4dddc150..b27cf8c58 100644 --- a/MoreLinq/StartsWith.cs +++ b/MoreLinq/StartsWith.cs @@ -68,7 +68,7 @@ public static bool StartsWith(this IEnumerable first, IEnumerable secon /// of elements at the same index. /// - public static bool StartsWith(this IEnumerable first, IEnumerable second, IEqualityComparer comparer) + public static bool StartsWith(this IEnumerable first, IEnumerable second, IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); @@ -80,11 +80,11 @@ public static bool StartsWith(this IEnumerable first, IEnumerable secon return false; } - comparer = comparer ?? EqualityComparer.Default; + var cmp = comparer ?? EqualityComparer.Default; using (var firstIter = first.GetEnumerator()) { - return second.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); + return second.All(item => firstIter.MoveNext() && cmp.Equals(firstIter.Current, item)); } } } diff --git a/MoreLinq/ToArrayByIndex.cs b/MoreLinq/ToArrayByIndex.cs index 1ccf13d93..ad0f503c5 100644 --- a/MoreLinq/ToArrayByIndex.cs +++ b/MoreLinq/ToArrayByIndex.cs @@ -121,8 +121,8 @@ public static TResult[] ToArrayByIndex(this IEnumerable source, if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); var lastIndex = -1; - var indexed = (List>) null; - List> Indexed() => indexed ?? (indexed = new List>()); + var indexed = (List>?) null; + List> Indexed() => indexed ??= new List>(); foreach (var e in source) { diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index fa2719739..7b06b01fe 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -20,10 +20,16 @@ namespace MoreLinq using System; using System.Collections.Generic; using System.Data; + using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; + static class Array + { + public static readonly T[] Empty = new T[0]; + } + static partial class MoreEnumerable { /// @@ -41,7 +47,7 @@ static partial class MoreEnumerable public static TTable ToDataTable(this IEnumerable source, TTable table) where TTable : DataTable { - return ToDataTable(source, table, null); + return ToDataTable(source, table, Array>>.Empty); } /// @@ -209,7 +215,7 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) if (columns.Count == 0) { - columns.AddRange(schemas.Select(m => new DataColumn(m.Member.Name, m.Type)).ToArray()); + columns.AddRange(schemas.Select(m => new DataColumn(m.Member!.Name /* TODO(nullable): should not be needed */, m.Type)).ToArray()); } else { @@ -220,6 +226,8 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) var member = info.Member; var column = info.Column; + Debug.Assert(member != null); // TODO(nullable): should not be needed + if (column == null) throw new ArgumentException($"Column named '{member.Name}' is missing.", nameof(table)); diff --git a/MoreLinq/ToDictionary.cs b/MoreLinq/ToDictionary.cs index 82a541317..925dbea7f 100644 --- a/MoreLinq/ToDictionary.cs +++ b/MoreLinq/ToDictionary.cs @@ -53,7 +53,7 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.ToDictionary(e => e.Key, e => e.Value, comparer); @@ -90,7 +90,7 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.ToDictionary(e => e.Key, e => e.Value, comparer); diff --git a/MoreLinq/ToHashSet.cs b/MoreLinq/ToHashSet.cs index ef6df582a..7301ed357 100644 --- a/MoreLinq/ToHashSet.cs +++ b/MoreLinq/ToHashSet.cs @@ -53,7 +53,7 @@ public static HashSet ToHashSet(this IEnumerable sour /// This evaluates the input sequence completely. /// - public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer comparer) + public static HashSet ToHashSet(this IEnumerable source, IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return new HashSet(source, comparer); diff --git a/MoreLinq/ToLookup.cs b/MoreLinq/ToLookup.cs index 47b8115f6..16436feb1 100644 --- a/MoreLinq/ToLookup.cs +++ b/MoreLinq/ToLookup.cs @@ -53,7 +53,7 @@ public static ILookup ToLookup(this IEnumerable public static ILookup ToLookup(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.ToLookup(e => e.Key, e => e.Value, comparer); @@ -90,7 +90,7 @@ public static ILookup ToLookup(this IEnumerable<(TKe /// public static ILookup ToLookup(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.ToLookup(e => e.Key, e => e.Value, comparer); diff --git a/MoreLinq/Trace.cs b/MoreLinq/Trace.cs index 8fac03eda..ff9438a3f 100644 --- a/MoreLinq/Trace.cs +++ b/MoreLinq/Trace.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; static partial class MoreEnumerable { @@ -38,7 +37,7 @@ static partial class MoreEnumerable public static IEnumerable Trace(this IEnumerable source) { - return Trace(source, (string) null); + return Trace(source, (string?) null); } /// @@ -59,7 +58,7 @@ public static IEnumerable Trace(this IEnumerable sour /// streams the results. /// - public static IEnumerable Trace(this IEnumerable source, string format) + public static IEnumerable Trace(this IEnumerable source, string? format) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -93,9 +92,6 @@ public static IEnumerable Trace(this IEnumerable sour static IEnumerable TraceImpl(IEnumerable source, Func formatter) { - Debug.Assert(source != null); - Debug.Assert(formatter != null); - return source #if !NO_TRACING .Pipe(x => System.Diagnostics.Trace.WriteLine(formatter(x))) diff --git a/MoreLinq/ZipImpl.cs b/MoreLinq/ZipImpl.cs index 2005660cb..e8775fecc 100644 --- a/MoreLinq/ZipImpl.cs +++ b/MoreLinq/ZipImpl.cs @@ -26,18 +26,18 @@ static partial class MoreEnumerable delegate TResult Folder(params T[] args); static IEnumerable ZipImpl( - IEnumerable s1, - IEnumerable s2, - IEnumerable s3, - IEnumerable s4, + IEnumerable s1, + IEnumerable s2, + IEnumerable? s3, + IEnumerable? s4, Func resultSelector, int limit, - Folder errorSelector = null) + Folder? errorSelector = null) { - IEnumerator e1 = null; - IEnumerator e2 = null; - IEnumerator e3 = null; - IEnumerator e4 = null; + IEnumerator? e1 = null; + IEnumerator? e2 = null; + IEnumerator? e3 = null; + IEnumerator? e4 = null; var terminations = 0; try @@ -69,10 +69,10 @@ static IEnumerable ZipImpl( e4?.Dispose(); } - T Read(ref IEnumerator e, int n) + T Read(ref IEnumerator? e, int n) { if (e == null || terminations > limit) - return default; + return default!; T value; if (e.MoveNext()) @@ -84,7 +84,7 @@ T Read(ref IEnumerator e, int n) e.Dispose(); e = null; terminations++; - value = default; + value = default!; } if (errorSelector != null && terminations > 0 && terminations < n) diff --git a/MoreLinq/ZipShortest.cs b/MoreLinq/ZipShortest.cs index 4aa27f636..b79168186 100644 --- a/MoreLinq/ZipShortest.cs +++ b/MoreLinq/ZipShortest.cs @@ -169,8 +169,10 @@ public static IEnumerable ZipShortest( } static IEnumerable ZipImpl( - IEnumerable s1, IEnumerable s2, - IEnumerable s3, IEnumerable s4, + IEnumerable s1, + IEnumerable s2, + IEnumerable? s3, + IEnumerable? s4, Func resultSelector) { return ZipImpl(s1, s2, s3, s4, resultSelector, 0); diff --git a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj index 6f5aad70f..cb1a4cb72 100644 --- a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj +++ b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj @@ -5,7 +5,7 @@ false - - + + diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 5a5f6012c..2187e22f9 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -302,6 +302,8 @@ public static partial class {m.Name}Extension // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. +#nullable enable // TODO(nullable) review why this is needed + namespace MoreLinq.Extensions {{ {string.Join("\n", imports)} From 52406abbd3c56013aa6d459a914583fe5408bac4 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 13:14:25 +0200 Subject: [PATCH 03/53] Use VS 2019 Preview image for AppVeyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 3859a240c..37d399923 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: '{build}' -image: Visual Studio 2017 +image: Visual Studio 2019 Preview skip_commits: files: - '*.md' From 30e06c4d5f1f56d1303ab15eb92f423d70990c32 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 13:40:07 +0200 Subject: [PATCH 04/53] Install .NET Core SDK on AppVeyor --- appveyor.yml | 1 + tools/dotnet-install.ps1 | 602 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 603 insertions(+) create mode 100644 tools/dotnet-install.ps1 diff --git a/appveyor.yml b/appveyor.yml index 37d399923..7c37617cb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,6 +13,7 @@ install: - eclint check -n "**/*.{cs,tt,cmd,sh,md,txt,yml}" - eclint check -w "**/*.{cs,tt,cmd,sh,md,txt,yml,json,sln,csproj,shfbproj}" - git reset --hard +- ps: tools\dotnet-install.ps1 -Version ((type .\global.json | ConvertFrom-Json).sdk.version) before_build: - dotnet --info build_script: diff --git a/tools/dotnet-install.ps1 b/tools/dotnet-install.ps1 new file mode 100644 index 000000000..8c59e162a --- /dev/null +++ b/tools/dotnet-install.ps1 @@ -0,0 +1,602 @@ +# +# Copyright (c) .NET Foundation and contributors. All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for full license information. +# + +<# +.SYNOPSIS + Installs dotnet cli +.DESCRIPTION + Installs dotnet cli. If dotnet installation already exists in the given directory + it will update it only if the requested version differs from the one already installed. +.PARAMETER Channel + Default: LTS + Download from the Channel specified. Possible values: + - Current - most current release + - LTS - most current supported release + - 2-part version in a format A.B - represents a specific release + examples: 2.0; 1.0 + - Branch name + examples: release/2.0.0; Master +.PARAMETER Version + Default: latest + Represents a build version on specific channel. Possible values: + - latest - most latest build on specific channel + - coherent - most latest coherent build on specific channel + coherent applies only to SDK downloads + - 3-part version in a format A.B.C - represents specific version of build + examples: 2.0.0-preview2-006120; 1.1.0 +.PARAMETER InstallDir + Default: %LocalAppData%\Microsoft\dotnet + Path to where to install dotnet. Note that binaries will be placed directly in a given directory. +.PARAMETER Architecture + Default: - this value represents currently running OS architecture + Architecture of dotnet binaries to be installed. + Possible values are: , x64 and x86 +.PARAMETER SharedRuntime + This parameter is obsolete and may be removed in a future version of this script. + The recommended alternative is '-Runtime dotnet'. + + Default: false + Installs just the shared runtime bits, not the entire SDK. + This is equivalent to specifying `-Runtime dotnet`. +.PARAMETER Runtime + Installs just a shared runtime, not the entire SDK. + Possible values: + - dotnet - the Microsoft.NETCore.App shared runtime + - aspnetcore - the Microsoft.AspNetCore.App shared runtime +.PARAMETER DryRun + If set it will not perform installation but instead display what command line to use to consistently install + currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link + with specific version so that this command can be used deterministicly in a build script. + It also displays binaries location if you prefer to install or download it yourself. +.PARAMETER NoPath + By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. + If set it will display binaries location but not set any environment variable. +.PARAMETER Verbose + Displays diagnostics information. +.PARAMETER AzureFeed + Default: https://dotnetcli.azureedge.net/dotnet + This parameter typically is not changed by the user. + It allows changing the URL for the Azure feed used by this installer. +.PARAMETER UncachedFeed + This parameter typically is not changed by the user. + It allows changing the URL for the Uncached feed used by this installer. +.PARAMETER FeedCredential + Used as a query string to append to the Azure feed. + It allows changing the URL to use non-public blob storage accounts. +.PARAMETER ProxyAddress + If set, the installer will use the proxy when making web requests +.PARAMETER ProxyUseDefaultCredentials + Default: false + Use default credentials, when using proxy address. +.PARAMETER SkipNonVersionedFiles + Default: false + Skips installing non-versioned files if they already exist, such as dotnet.exe. +.PARAMETER NoCdn + Disable downloading from the Azure CDN, and use the uncached feed directly. +#> +[cmdletbinding()] +param( + [string]$Channel="LTS", + [string]$Version="Latest", + [string]$InstallDir="", + [string]$Architecture="", + [ValidateSet("dotnet", "aspnetcore", IgnoreCase = $false)] + [string]$Runtime, + [Obsolete("This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'.")] + [switch]$SharedRuntime, + [switch]$DryRun, + [switch]$NoPath, + [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", + [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", + [string]$FeedCredential, + [string]$ProxyAddress, + [switch]$ProxyUseDefaultCredentials, + [switch]$SkipNonVersionedFiles, + [switch]$NoCdn +) + +Set-StrictMode -Version Latest +$ErrorActionPreference="Stop" +$ProgressPreference="SilentlyContinue" + +if ($NoCdn) { + $AzureFeed = $UncachedFeed +} + +$BinFolderRelativePath="" + +if ($SharedRuntime -and (-not $Runtime)) { + $Runtime = "dotnet" +} + +# example path with regex: shared/1.0.0-beta-12345/somepath +$VersionRegEx="/\d+\.\d+[^/]+/" +$OverrideNonVersionedFiles = !$SkipNonVersionedFiles + +function Say($str) { + Write-Host "dotnet-install: $str" +} + +function Say-Verbose($str) { + Write-Verbose "dotnet-install: $str" +} + +function Say-Invocation($Invocation) { + $command = $Invocation.MyCommand; + $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") + Say-Verbose "$command $args" +} + +function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) { + $Attempts = 0 + + while ($true) { + try { + return $ScriptBlock.Invoke() + } + catch { + $Attempts++ + if ($Attempts -lt $MaxAttempts) { + Start-Sleep $SecondsBetweenAttempts + } + else { + throw + } + } + } +} + +function Get-Machine-Architecture() { + Say-Invocation $MyInvocation + + # possible values: amd64, x64, x86, arm64, arm + return $ENV:PROCESSOR_ARCHITECTURE +} + +function Get-CLIArchitecture-From-Architecture([string]$Architecture) { + Say-Invocation $MyInvocation + + switch ($Architecture.ToLower()) { + { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } + { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } + { $_ -eq "x86" } { return "x86" } + { $_ -eq "arm" } { return "arm" } + { $_ -eq "arm64" } { return "arm64" } + default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" } + } +} + +function Get-Version-Info-From-Version-Text([string]$VersionText) { + Say-Invocation $MyInvocation + + $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); + + $VersionInfo = @{} + $VersionInfo.CommitHash = $Data[0].Trim() + $VersionInfo.Version = $Data[1].Trim() + return $VersionInfo +} + +function Load-Assembly([string] $Assembly) { + try { + Add-Type -Assembly $Assembly | Out-Null + } + catch { + # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. + # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. + } +} + +function GetHTTPResponse([Uri] $Uri) +{ + Invoke-With-Retry( + { + + $HttpClient = $null + + try { + # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. + Load-Assembly -Assembly System.Net.Http + + if(-not $ProxyAddress) { + try { + # Despite no proxy being explicitly specified, we may still be behind a default proxy + $DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy; + if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))) { + $ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString + $ProxyUseDefaultCredentials = $true + } + } catch { + # Eat the exception and move forward as the above code is an attempt + # at resolving the DefaultProxy that may not have been a problem. + $ProxyAddress = $null + Say-Verbose("Exception ignored: $_.Exception.Message - moving forward...") + } + } + + if($ProxyAddress) { + $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler + $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials} + $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler + } + else { + + $HttpClient = New-Object System.Net.Http.HttpClient + } + # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out + # 10 minutes allows it to work over much slower connections. + $HttpClient.Timeout = New-TimeSpan -Minutes 10 + $Response = $HttpClient.GetAsync("${Uri}${FeedCredential}").Result + if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) { + # The feed credential is potentially sensitive info. Do not log FeedCredential to console output. + $ErrorMsg = "Failed to download $Uri." + if ($Response -ne $null) { + $ErrorMsg += " $Response" + } + + throw $ErrorMsg + } + + return $Response + } + finally { + if ($HttpClient -ne $null) { + $HttpClient.Dispose() + } + } + }) +} + + +function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) { + Say-Invocation $MyInvocation + + $VersionFileUrl = $null + if ($Runtime -eq "dotnet") { + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" + } + elseif ($Runtime -eq "aspnetcore") { + $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" + } + elseif (-not $Runtime) { + if ($Coherent) { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" + } + else { + $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version" + } + } + else { + throw "Invalid value for `$Runtime" + } + + $Response = GetHTTPResponse -Uri $VersionFileUrl + $StringContent = $Response.Content.ReadAsStringAsync().Result + + switch ($Response.Content.Headers.ContentType) { + { ($_ -eq "application/octet-stream") } { $VersionText = $StringContent } + { ($_ -eq "text/plain") } { $VersionText = $StringContent } + { ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent } + default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } + } + + $VersionInfo = Get-Version-Info-From-Version-Text $VersionText + + return $VersionInfo +} + + +function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) { + Say-Invocation $MyInvocation + + switch ($Version.ToLower()) { + { $_ -eq "latest" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False + return $LatestVersionInfo.Version + } + { $_ -eq "coherent" } { + $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True + return $LatestVersionInfo.Version + } + default { return $Version } + } +} + +function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if ($Runtime -eq "dotnet") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif ($Runtime -eq "aspnetcore") { + $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + } + elseif (-not $Runtime) { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip" + } + else { + throw "Invalid value for `$Runtime" + } + + Say-Verbose "Constructed primary payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { + Say-Invocation $MyInvocation + + if (-not $Runtime) { + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" + } + elseif ($Runtime -eq "dotnet") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" + } + else { + return $null + } + + Say-Verbose "Constructed legacy payload URL: $PayloadURL" + + return $PayloadURL +} + +function Get-User-Share-Path() { + Say-Invocation $MyInvocation + + $InstallRoot = $env:DOTNET_INSTALL_DIR + if (!$InstallRoot) { + $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" + } + return $InstallRoot +} + +function Resolve-Installation-Path([string]$InstallDir) { + Say-Invocation $MyInvocation + + if ($InstallDir -eq "") { + return Get-User-Share-Path + } + return $InstallDir +} + +function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) { + Say-Invocation $MyInvocation + + $VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile + Say-Verbose "Local version file: $VersionFile" + + if (Test-Path $VersionFile) { + $VersionText = cat $VersionFile + Say-Verbose "Local version file text: $VersionText" + return Get-Version-Info-From-Version-Text $VersionText + } + + Say-Verbose "Local version file not found." + + return $null +} + +function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { + Say-Invocation $MyInvocation + + $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion + Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath" + return Test-Path $DotnetPackagePath -PathType Container +} + +function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { + # Too much spam + # Say-Invocation $MyInvocation + + return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) +} + +function Get-Path-Prefix-With-Version($path) { + $match = [regex]::match($path, $VersionRegEx) + if ($match.Success) { + return $entry.FullName.Substring(0, $match.Index + $match.Length) + } + + return $null +} + +function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { + Say-Invocation $MyInvocation + + $ret = @() + foreach ($entry in $Zip.Entries) { + $dir = Get-Path-Prefix-With-Version $entry.FullName + if ($dir -ne $null) { + $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) + if (-Not (Test-Path $path -PathType Container)) { + $ret += $dir + } + } + } + + $ret = $ret | Sort-Object | Get-Unique + + $values = ($ret | foreach { "$_" }) -join ";" + Say-Verbose "Directories to unpack: $values" + + return $ret +} + +# Example zip content and extraction algorithm: +# Rule: files if extracted are always being extracted to the same relative path locally +# .\ +# a.exe # file does not exist locally, extract +# b.dll # file exists locally, override only if $OverrideFiles set +# aaa\ # same rules as for files +# ... +# abc\1.0.0\ # directory contains version and exists locally +# ... # do not extract content under versioned part +# abc\asd\ # same rules as for files +# ... +# def\ghi\1.0.1\ # directory contains version and does not exist locally +# ... # extract content +function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { + Say-Invocation $MyInvocation + + Load-Assembly -Assembly System.IO.Compression.FileSystem + Set-Variable -Name Zip + try { + $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) + + $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath + + foreach ($entry in $Zip.Entries) { + $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName + if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { + $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) + $DestinationDir = Split-Path -Parent $DestinationPath + $OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) + if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { + New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null + [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) + } + } + } + } + finally { + if ($Zip -ne $null) { + $Zip.Dispose() + } + } +} + +function DownloadFile([Uri]$Uri, [string]$OutPath) { + if ($Uri -notlike "http*") { + Say-Verbose "Copying file from $Uri to $OutPath" + Copy-Item $Uri.AbsolutePath $OutPath + return + } + + $Stream = $null + + try { + $Response = GetHTTPResponse -Uri $Uri + $Stream = $Response.Content.ReadAsStreamAsync().Result + $File = [System.IO.File]::Create($OutPath) + $Stream.CopyTo($File) + $File.Close() + } + finally { + if ($Stream -ne $null) { + $Stream.Dispose() + } + } +} + +function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { + $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) + if (-Not $NoPath) { + Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." + $env:path = "$BinPath;" + $env:path + } + else { + Say "Binaries of dotnet can be found in $BinPath" + } +} + +$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture +$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version +$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture + +if ($DryRun) { + Say "Payload URLs:" + Say "Primary - $DownloadLink" + if ($LegacyDownloadLink) { + Say "Legacy - $LegacyDownloadLink" + } + Say "Repeatable invocation: .\$($MyInvocation.Line)" + exit 0 +} + +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" + +if ($Runtime -eq "dotnet") { + $assetName = ".NET Core Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.NETCore.App" +} +elseif ($Runtime -eq "aspnetcore") { + $assetName = "ASP.NET Core Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App" +} +elseif (-not $Runtime) { + $assetName = ".NET Core SDK" + $dotnetPackageRelativePath = "sdk" +} +else { + throw "Invalid value for `$Runtime" +} + +# Check if the SDK version is already installed. +$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +if ($isAssetInstalled) { + Say "$assetName version $SpecificVersion is already installed." + Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + exit 0 +} + +New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null + +$installDrive = $((Get-Item $InstallRoot).PSDrive.Name); +$diskInfo = Get-PSDrive -Name $installDrive +if ($diskInfo.Free / 1MB -le 100) { + Say "There is not enough disk space on drive ${installDrive}:" + exit 0 +} + +$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) +Say-Verbose "Zip path: $ZipPath" + +$DownloadFailed = $false +Say "Downloading link: $DownloadLink" +try { + DownloadFile -Uri $DownloadLink -OutPath $ZipPath +} +catch { + Say "Cannot download: $DownloadLink" + if ($LegacyDownloadLink) { + $DownloadLink = $LegacyDownloadLink + $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) + Say-Verbose "Legacy zip path: $ZipPath" + Say "Downloading legacy link: $DownloadLink" + try { + DownloadFile -Uri $DownloadLink -OutPath $ZipPath + } + catch { + Say "Cannot download: $DownloadLink" + $DownloadFailed = $true + } + } + else { + $DownloadFailed = $true + } +} + +if ($DownloadFailed) { + throw "Could not find/download: `"$assetName`" with version = $SpecificVersion`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" +} + +Say "Extracting zip from $DownloadLink" +Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot + +# Check if the SDK version is now installed; if not, fail the installation. +$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion +if (!$isAssetInstalled) { + throw "`"$assetName`" with version = $SpecificVersion failed to install with an unknown error." +} + +Remove-Item $ZipPath + +Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath + +Say "Installation finished" +exit 0 \ No newline at end of file From 6b4468ed3a3298e557b8cd499af00194a07ebe07 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 13:58:34 +0200 Subject: [PATCH 05/53] Don't send telemetry data --- appveyor.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 7c37617cb..17c39b902 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,8 @@ skip_commits: - '*.txt' - '.editorconfig' - lic/* +environment: + DOTNET_CLI_TELEMETRY_OPTOUT: true install: - ps: Install-Product node 6 - npm install -g eclint From b37e9410597d64d7759047d8ce6de967ce5ff329 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 13:59:00 +0200 Subject: [PATCH 06/53] Prevent dotnet from pre-populating packages cache --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 17c39b902..38bd4f475 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,7 @@ skip_commits: - lic/* environment: DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true install: - ps: Install-Product node 6 - npm install -g eclint From 8328713182beefe2bf27f5c179872750ea961c23 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 13:59:28 +0200 Subject: [PATCH 07/53] Use dotnet (instead of MSBuild) for main build --- build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index fcb616e9d..36522cb46 100644 --- a/build.cmd +++ b/build.cmd @@ -13,7 +13,7 @@ if "%1"=="docs" shift & goto :docs dotnet restore ^ && call :codegen MoreLinq\Extensions.g.cs -x "[/\\]ToDataTable\.cs$" -u System.Linq -u System.Collections MoreLinq ^ && call :codegen MoreLinq\Extensions.ToDataTable.g.cs -i "[/\\]ToDataTable\.cs$" -u System.Data -u System.Linq.Expressions MoreLinq ^ - && for %%i in (debug release) do call msbuild.cmd "MoreLinq.sln" /v:m /p:Configuration=%%i %* || exit /b 1 + && for %%i in (debug release) do dotnet build -c %%i --no-restore %* || exit /b 1 goto :EOF :docs From cd955b2e9f05ac3b6e50b297b94737846b13ca21 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 14:07:22 +0200 Subject: [PATCH 08/53] Revert to VS 2017 image on AppVeyor --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 38bd4f475..c5f2e5d53 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: '{build}' -image: Visual Studio 2019 Preview +image: Visual Studio 2017 skip_commits: files: - '*.md' From 0f3405e56808e73e2eb6b850aeff9f6eb888d443 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Fri, 3 May 2019 14:11:56 +0200 Subject: [PATCH 09/53] Use dotnet CLI for packing --- pack.cmd | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pack.cmd b/pack.cmd index d0068339a..e0b93a8db 100644 --- a/pack.cmd +++ b/pack.cmd @@ -10,12 +10,11 @@ if not exist dist md dist if not %errorlevel%==0 exit /b %errorlevel% set VERSION_SUFFIX= if not "%~1"=="" set VERSION_SUFFIX=/p:VersionSuffix=%1 -call build /v:m ^ - && call msbuild.cmd /v:m /t:Pack ^ - /p:Configuration=Release ^ - /p:IncludeSymbols=true ^ - /p:IncludeSource=true ^ - /p:PackageOutputPath=%cd%/dist ^ - %VERSION_SUFFIX% ^ - MoreLinq\MoreLinq.csproj +call build ^ + && dotnet pack /p:Configuration=Release ^ + /p:IncludeSymbols=true ^ + /p:IncludeSource=true ^ + /p:PackageOutputPath=%cd%/dist ^ + %VERSION_SUFFIX% ^ + MoreLinq\MoreLinq.csproj goto :EOF From e15c4982898840a37f61048da42312998f6159cd Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 22 May 2019 18:00:25 +0200 Subject: [PATCH 10/53] Use VS 2019 image for AppVeyor See https://github.com/appveyor/ci/issues/2907 (appveyor/ci#2907) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index c5f2e5d53..4c3ea3d1c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ version: '{build}' -image: Visual Studio 2017 +image: Visual Studio 2019 skip_commits: files: - '*.md' From a337fd6657e2046584889c5dd2e8d04defcfe101 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 22 May 2019 18:54:40 +0200 Subject: [PATCH 11/53] Review ScanBy for nullable reference types --- MoreLinq/Extensions.g.cs | 2 +- MoreLinq/ScanBy.cs | 36 +++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index ce76b63a3..90fa33acf 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -4572,7 +4572,7 @@ public static IEnumerable> ScanBy keySelector, Func seedSelector, Func accumulator, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ScanBy(source, keySelector, seedSelector, accumulator, comparer); } diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index 551aa5043..fccf7f787 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -78,7 +78,7 @@ public static IEnumerable> ScanBy keySelector, Func seedSelector, Func accumulator, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -87,19 +87,26 @@ public static IEnumerable> ScanBy> _() { - comparer = comparer ?? EqualityComparer.Default; + var cmp = comparer ?? EqualityComparer.Default; var stateByKey = new Dictionary(comparer); - var prevKey = (HasValue: false, Value: default(TKey)); - var nullKeyState = (HasValue: false, Value: default(TState)); + var prevKey = (false, default(TKey)); + var nullKeyState = (false, default(TState)); var state = default(TState); bool TryGetState(TKey key, out TState value) { if (key == null) { - value = nullKeyState.Value; - return nullKeyState.HasValue; + switch (nullKeyState) + { + case (true, var v): + value = v; + return true; + case (false, _): + value = default!; + return false; + } } return stateByKey.TryGetValue(key, out value); @@ -109,17 +116,20 @@ bool TryGetState(TKey key, out TState value) { var key = keySelector(item); - if (!(prevKey.HasValue - // key same as the previous? then re-use the state - && comparer.GetHashCode(prevKey.Value) == comparer.GetHashCode(key) - && comparer.Equals(prevKey.Value, key) - // otherwise try & find state of the key - || TryGetState(key, out state))) + if (// key same as the previous? then re-use the state + prevKey switch + { + (true, var pk) + => (cmp.GetHashCode(pk) != cmp.GetHashCode(key) || !cmp.Equals(pk, key)) + // otherwise try & find state of the key + && !TryGetState(key, out state), + _ => true + }) { state = seedSelector(key); } - state = accumulator(state, key, item); + state = accumulator(state!, key, item); if (key != null) stateByKey[key] = state; From 320bb9a2366492a2a0ead210409652caab7e6edd Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 22 May 2019 19:23:48 +0200 Subject: [PATCH 12/53] Simplify Count/ScanBy patterns --- MoreLinq/CountBy.cs | 14 +++++--------- MoreLinq/ScanBy.cs | 18 +++++++----------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 3b2ae0455..33db62240 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -102,15 +102,11 @@ bool TryGetIndex(TKey key, out int i) var key = keySelector(item); if (// key same as the previous? then re-use the index - prevKey switch - { - var (some, pk) - when some => cmp.GetHashCode(pk) == cmp.GetHashCode(key) - && cmp.Equals(pk, key) - // otherwise try & find index of the key - || TryGetIndex(key, out index), - _ => false - }) + prevKey is (true, var pk) + && cmp.GetHashCode(pk) == cmp.GetHashCode(key) + && cmp.Equals(pk, key) + // otherwise try & find index of the key + || TryGetIndex(key, out index)) { counts[index]++; } diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index fccf7f787..ddec8d75e 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -116,18 +116,14 @@ bool TryGetState(TKey key, out TState value) { var key = keySelector(item); - if (// key same as the previous? then re-use the state - prevKey switch - { - (true, var pk) - => (cmp.GetHashCode(pk) != cmp.GetHashCode(key) || !cmp.Equals(pk, key)) - // otherwise try & find state of the key - && !TryGetState(key, out state), - _ => true - }) - { + var haveState = + // key same as the previous? then re-use the state + prevKey is (true, var pk) && cmp.GetHashCode(pk) == cmp.GetHashCode(key) && cmp.Equals(pk, key) + // otherwise try & find state of the key + || TryGetState(key, out state); + + if (!haveState) state = seedSelector(key); - } state = accumulator(state!, key, item); From 9723435a0f0318d90ccec7085a15c24babcf0f4f Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 12:05:00 +0100 Subject: [PATCH 13/53] Use instead of --- MoreLinq/MoreLinq.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 8700829d1..98c51daff 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -118,7 +118,7 @@ MoreLINQ Developers. net451;netstandard1.0;netstandard2.0 8 - enable + enable true portable true From 311b5e3b52f90c43c14efa194fb38ecb33a249cc Mon Sep 17 00:00:00 2001 From: Orace Date: Thu, 14 Nov 2019 19:41:45 +0100 Subject: [PATCH 14/53] Fix build --- MoreLinq.Test/MoreLinq.Test.csproj | 1 - MoreLinq/CountBy.cs | 2 +- MoreLinq/Delegating.cs | 14 +++++++------- MoreLinq/EndsWith.cs | 2 +- MoreLinq/Experimental/Await.cs | 3 +-- MoreLinq/Experimental/Memoize.cs | 3 +-- MoreLinq/FillForward.cs | 2 +- MoreLinq/Flatten.cs | 2 +- MoreLinq/GroupAdjacent.cs | 4 +--- MoreLinq/IndexBy.cs | 4 ++-- MoreLinq/Interleave.cs | 1 - MoreLinq/Pad.cs | 3 --- MoreLinq/PartialSort.cs | 9 +++------ MoreLinq/PendNode.cs | 8 ++++---- MoreLinq/Random.cs | 2 +- MoreLinq/Reactive/Observable.cs | 2 +- MoreLinq/Reactive/Subject.cs | 29 +++++++++++++++++++++++++---- MoreLinq/ScanBy.cs | 8 ++++---- MoreLinq/Segment.cs | 2 +- MoreLinq/SortedMerge.cs | 4 ++-- MoreLinq/ToArrayByIndex.cs | 2 +- MoreLinq/ToDataTable.cs | 4 +--- MoreLinq/ToDelimitedString.cs | 4 ---- MoreLinq/Transpose.cs | 2 +- MoreLinq/TrySingle.cs | 8 ++++---- 25 files changed, 64 insertions(+), 61 deletions(-) diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index ea2da2dde..a08121f0e 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -40,7 +40,6 @@ - diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 33db62240..edc0058da 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -94,7 +94,7 @@ bool TryGetIndex(TKey key, out int i) keys = new List(); counts = new List(); - var prevKey = (false, default(TKey)); + var prevKey = (false, default(TKey)!); var index = 0; foreach (var item in source) diff --git a/MoreLinq/Delegating.cs b/MoreLinq/Delegating.cs index 6ccbf6885..4b9e8c189 100644 --- a/MoreLinq/Delegating.cs +++ b/MoreLinq/Delegating.cs @@ -33,8 +33,8 @@ public static IDisposable Disposable(Action delegatee) => new DelegatingDisposable(delegatee); public static IObserver Observer(Action onNext, - Action onError = null, - Action onCompleted = null) => + Action? onError = null, + Action? onCompleted = null) => new DelegatingObserver(onNext, onError, onCompleted); } @@ -48,7 +48,7 @@ public DelegatingDisposable(Action delegatee) => public void Dispose() { var delegatee = _delegatee; - if (delegatee == null || Interlocked.CompareExchange(ref _delegatee, null, delegatee) != delegatee) + if (delegatee == null || Interlocked.CompareExchange(ref _delegatee, null!, delegatee) != delegatee) return; delegatee(); } @@ -57,12 +57,12 @@ public void Dispose() sealed class DelegatingObserver : IObserver { readonly Action _onNext; - readonly Action _onError; - readonly Action _onCompleted; + readonly Action? _onError; + readonly Action? _onCompleted; public DelegatingObserver(Action onNext, - Action onError = null, - Action onCompleted = null) + Action? onError = null, + Action? onCompleted = null) { _onNext = onNext ?? throw new ArgumentNullException(nameof(onNext)); _onError = onError; diff --git a/MoreLinq/EndsWith.cs b/MoreLinq/EndsWith.cs index 415ddfe12..c6b449351 100644 --- a/MoreLinq/EndsWith.cs +++ b/MoreLinq/EndsWith.cs @@ -83,7 +83,7 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second, bool Impl(IEnumerable snd, int count, IEqualityComparer cmp) { using var firstIter = first.TakeLast(count).GetEnumerator(); - return snd.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); + return snd.All(item => firstIter.MoveNext() && cmp.Equals(firstIter.Current, item)); } } } diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index db0ddec6b..405c100fd 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -537,8 +537,7 @@ await enumerator.StartAsync( if (kind == Notice.Error) { - Debug.Assert(error != null); - error.Throw(); + error!.Throw(); } if (kind == Notice.End) diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 51f7358e0..5aa3c13ad 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -117,8 +117,7 @@ public IEnumerator GetEnumerator() { if (index == _errorIndex) { - Debug.Assert(_error != null); - _error.Throw(); + _error!.Throw(); } if (_sourceEnumerator == null) diff --git a/MoreLinq/FillForward.cs b/MoreLinq/FillForward.cs index 0745a82f5..dc20668b7 100644 --- a/MoreLinq/FillForward.cs +++ b/MoreLinq/FillForward.cs @@ -107,7 +107,7 @@ public static IEnumerable FillForward(this IEnumerable source, Func FillForwardImpl(IEnumerable source, Func predicate, Func? fillSelector) { var seeded = false; - var seed = default(T); + var seed = default(T)!; foreach (var item in source) { if (predicate(item)) diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index a715fdc37..d5acfef1f 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -123,7 +123,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func GroupAdjacentImpl( using var iterator = source.GetEnumerator(); - var group = default(TKey); + var group = default(TKey)!; var members = (List?) null; while (iterator.MoveNext()) @@ -294,7 +294,6 @@ static IEnumerable GroupAdjacentImpl( static IGrouping CreateGroupAdjacentGrouping(TKey key, IList members) { - Debug.Assert(members != null); return Grouping.Create(key, members.IsReadOnly ? members : new ReadOnlyCollection(members)); } @@ -313,7 +312,6 @@ sealed class Grouping : IGrouping public Grouping(TKey key, IEnumerable members) { - Debug.Assert(members != null); Key = key; _members = members; } diff --git a/MoreLinq/IndexBy.cs b/MoreLinq/IndexBy.cs index 1a407b07c..9403c5bec 100644 --- a/MoreLinq/IndexBy.cs +++ b/MoreLinq/IndexBy.cs @@ -69,8 +69,8 @@ public static IEnumerable> IndexBy( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) => - from e in source.ScanBy(keySelector, k => (Index: -1, Item: default(TSource)), (s, k, e) => (s.Index + 1, e), comparer) + IEqualityComparer? comparer) => + from e in source.ScanBy(keySelector, k => (Index: -1, Item: default(TSource)!), (s, k, e) => (s.Index + 1, e), comparer) select new KeyValuePair(e.Value.Index, e.Value.Item); } } diff --git a/MoreLinq/Interleave.cs b/MoreLinq/Interleave.cs index 344887e65..9f65c9e22 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -142,7 +142,6 @@ static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInte } finally { - Debug.Assert(iteratorList != null || iterators != null); foreach (var iter in iteratorList ?? (IList>) iterators!) iter.Dispose(); } diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index 1557e50dc..1d91bb3a4 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -117,9 +117,6 @@ public static IEnumerable Pad(this IEnumerable source static IEnumerable PadImpl(IEnumerable source, int width, T padding, Func? paddingSelector) { - Debug.Assert(source != null); - Debug.Assert(width >= 0); - var count = 0; foreach (var item in source) { diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index 7350f91c1..34bfd3e36 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -225,19 +225,16 @@ static IEnumerable PartialSortByImpl( IComparer? keyComparer, IComparer? comparer) { - Debug.Assert(source != null); - - var keys = keySelector != null ? new List(count) : null; + var keys = keySelector == null ? null : new List(count); var top = new List(count); foreach (var item in source) { int i; - var key = default(TKey); + var key = default(TKey)!; if (keys != null) { - Debug.Assert(keySelector != null); - key = keySelector(item); + key = keySelector!(item); i = keys.BinarySearch(key, keyComparer); } else diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index 9da48e859..be0e68349 100644 --- a/MoreLinq/PendNode.cs +++ b/MoreLinq/PendNode.cs @@ -63,10 +63,10 @@ public IEnumerator GetEnumerator() { var i = 0; T[]? concats = null; // Array for > 4 concatenations - var concat1 = default(T); // Slots for up to 4 concatenations - var concat2 = default(T); - var concat3 = default(T); - var concat4 = default(T); + var concat1 = default(T)!; // Slots for up to 4 concatenations + var concat2 = default(T)!; + var concat3 = default(T)!; + var concat4 = default(T)!; var current = this; for (; current is Item item; current = item.Next) diff --git a/MoreLinq/Random.cs b/MoreLinq/Random.cs index eaf66bca1..33566a3db 100644 --- a/MoreLinq/Random.cs +++ b/MoreLinq/Random.cs @@ -230,7 +230,7 @@ sealed class GlobalRandom : Random public static readonly Random Instance = new GlobalRandom(); static int _seed = Environment.TickCount; - [ThreadStatic] static Random _threadRandom; + [ThreadStatic] static Random? _threadRandom; static Random ThreadRandom => _threadRandom ??= new Random(Interlocked.Increment(ref _seed)); GlobalRandom() { } diff --git a/MoreLinq/Reactive/Observable.cs b/MoreLinq/Reactive/Observable.cs index 405958a0d..0c28c9467 100644 --- a/MoreLinq/Reactive/Observable.cs +++ b/MoreLinq/Reactive/Observable.cs @@ -42,7 +42,7 @@ static partial class Observable /// The subscription, which when disposed, will unsubscribe /// from . - public static IDisposable Subscribe(this IObservable source, Action onNext, Action onError = null, Action onCompleted = null) => + public static IDisposable Subscribe(this IObservable source, Action onNext, Action? onError = null, Action? onCompleted = null) => source == null ? throw new ArgumentNullException(nameof(source)) : source.Subscribe(Delegate.Observer(onNext, onError, onCompleted)); diff --git a/MoreLinq/Reactive/Subject.cs b/MoreLinq/Reactive/Subject.cs index cbb1a9764..53a369c19 100644 --- a/MoreLinq/Reactive/Subject.cs +++ b/MoreLinq/Reactive/Subject.cs @@ -21,17 +21,27 @@ namespace MoreLinq.Reactive using System.Collections.Generic; using Delegate = Delegating.Delegate; - sealed class Subject : IObservable, IObserver + /// + /// Subject + /// + /// + public sealed class Subject : IObservable, IObserver { - List> _observers; + List>? _observers; bool _completed; - Exception _error; + Exception? _error; bool HasObservers => (_observers?.Count ?? 0) > 0; List> Observers => _observers ??= new List>(); bool IsMuted => _completed || _error != null; + /// + /// + /// + /// + /// + /// public IDisposable Subscribe(IObserver observer) { if (observer == null) throw new ArgumentNullException(nameof(observer)); @@ -66,7 +76,7 @@ public IDisposable Subscribe(IObserver observer) if (observers[i] == observer) { if (_shouldDeleteObserver) - observers[i] = null; + observers[i] = null!; else observers.RemoveAt(i); break; @@ -77,6 +87,10 @@ public IDisposable Subscribe(IObserver observer) bool _shouldDeleteObserver; // delete (null) or remove an observer? + /// + /// + /// + /// public void OnNext(T value) { if (!HasObservers) @@ -113,9 +127,16 @@ public void OnNext(T value) } } + /// + /// + /// + /// public void OnError(Exception error) => OnFinality(ref _error, error, (observer, err) => observer.OnError(err)); + /// + /// + /// public void OnCompleted() => OnFinality(ref _completed, true, (observer, _) => observer.OnCompleted()); diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index bb32a389a..19ed562c4 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -90,9 +90,9 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { var stateByKey = new Dictionary(comparer); - var prevKey = (false, default(TKey)); - var nullKeyState = (false, default(TState)); - var state = default(TState); + var prevKey = (false, default(TKey)!); + var nullKeyState = (false, default(TState)!); + var state = default(TState)!; bool TryGetState(TKey key, out TState value) { @@ -118,7 +118,7 @@ bool TryGetState(TKey key, out TState value) var haveState = // key same as the previous? then re-use the state - prevKey is (true, var pk) && cmp.GetHashCode(pk) == cmp.GetHashCode(key) && cmp.Equals(pk, key) + prevKey is (true, var pk) && comparer.GetHashCode(pk) == comparer.GetHashCode(key) && comparer.Equals(pk, key) // otherwise try & find state of the key || TryGetState(key, out state); diff --git a/MoreLinq/Segment.cs b/MoreLinq/Segment.cs index 7e657c64b..fefee4dd8 100644 --- a/MoreLinq/Segment.cs +++ b/MoreLinq/Segment.cs @@ -80,7 +80,7 @@ public static IEnumerable> Segment(this IEnumerable source, using var iter = source.GetEnumerator(); var segment = new List(); - var prevItem = default(T); + var prevItem = default(T)!; // ensure that the first item is always part // of the first segment. This is an intentional diff --git a/MoreLinq/SortedMerge.cs b/MoreLinq/SortedMerge.cs index 7e54c2ce5..b19273c15 100644 --- a/MoreLinq/SortedMerge.cs +++ b/MoreLinq/SortedMerge.cs @@ -79,8 +79,8 @@ public static IEnumerable SortedMerge(this IEnumerable)((a, b) => cmp.Compare(b, a) < 0) - : (a, b) => cmp.Compare(b, a) > 0; + ? (Func)((a, b) => comparer.Compare(b, a) < 0) + : (a, b) => comparer.Compare(b, a) > 0; // return the sorted merge result return Impl(new[] { source }.Concat(otherSequences)); diff --git a/MoreLinq/ToArrayByIndex.cs b/MoreLinq/ToArrayByIndex.cs index 4bbad61ab..ad0f503c5 100644 --- a/MoreLinq/ToArrayByIndex.cs +++ b/MoreLinq/ToArrayByIndex.cs @@ -121,7 +121,7 @@ public static TResult[] ToArrayByIndex(this IEnumerable source, if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); var lastIndex = -1; - var indexed = (List>) null; + var indexed = (List>?) null; List> Indexed() => indexed ??= new List>(); foreach (var e in source) diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 7b06b01fe..993ba1600 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -215,7 +215,7 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) if (columns.Count == 0) { - columns.AddRange(schemas.Select(m => new DataColumn(m.Member!.Name /* TODO(nullable): should not be needed */, m.Type)).ToArray()); + columns.AddRange(schemas.Select(m => new DataColumn(m.Member.Name, m.Type)).ToArray()); } else { @@ -226,8 +226,6 @@ static MemberInfo[] BuildOrBindSchema(DataTable table, MemberInfo[] members) var member = info.Member; var column = info.Column; - Debug.Assert(member != null); // TODO(nullable): should not be needed - if (column == null) throw new ArgumentException($"Column named '{member.Name}' is missing.", nameof(table)); diff --git a/MoreLinq/ToDelimitedString.cs b/MoreLinq/ToDelimitedString.cs index c46f78f34..c02291c70 100644 --- a/MoreLinq/ToDelimitedString.cs +++ b/MoreLinq/ToDelimitedString.cs @@ -53,10 +53,6 @@ public static string ToDelimitedString(this IEnumerable source static string ToDelimitedStringImpl(IEnumerable source, string delimiter, Func append) { - Debug.Assert(source != null); - Debug.Assert(delimiter != null); - Debug.Assert(append != null); - var sb = new StringBuilder(); var i = 0; diff --git a/MoreLinq/Transpose.cs b/MoreLinq/Transpose.cs index 0234c9224..f3cb02888 100644 --- a/MoreLinq/Transpose.cs +++ b/MoreLinq/Transpose.cs @@ -78,7 +78,7 @@ public static IEnumerable> Transpose(this IEnumerable(this IEnumerable so switch (source.TryGetCollectionCount()) { case 0: - return resultSelector(zero, default); + return resultSelector(zero, default!); case 1: { var item = source switch @@ -116,15 +116,15 @@ public static TResult TrySingle(this IEnumerable so return resultSelector(one, item); } case int _: - return resultSelector(many, default); + return resultSelector(many, default!); default: { using var e = source.GetEnumerator(); if (!e.MoveNext()) - return resultSelector(zero, default); + return resultSelector(zero, default!); var current = e.Current; return !e.MoveNext() ? resultSelector(one, current) - : resultSelector(many, default); + : resultSelector(many, default!); } } } From 22f358ee1d97b99cf9be7adec2366a962dbbf91e Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sat, 16 Nov 2019 12:12:30 +0200 Subject: [PATCH 15/53] Fix c#8 nullable-default --- MoreLinq/CountBy.cs | 2 +- MoreLinq/Experimental/Await.cs | 4 ++-- MoreLinq/Experimental/Memoize.cs | 2 +- MoreLinq/Flatten.cs | 4 ++-- MoreLinq/ScanBy.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index edc0058da..623fc14a8 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -94,7 +94,7 @@ bool TryGetIndex(TKey key, out int i) keys = new List(); counts = new List(); - var prevKey = (false, default(TKey)!); + (bool, TKey) prevKey = (false, default); var index = 0; foreach (var item in source) diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 405c100fd..dd664c1a8 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -533,11 +533,11 @@ await enumerator.StartAsync( : new AggregateException(error1)); } - var (kind, result, error) = notice.Current; + (Notice kind, (int, T, Task) result, ExceptionDispatchInfo? error) = notice.Current; if (kind == Notice.Error) { - error!.Throw(); + error?.Throw(); } if (kind == Notice.End) diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 5aa3c13ad..a6dcad91b 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -117,7 +117,7 @@ public IEnumerator GetEnumerator() { if (index == _errorIndex) { - _error!.Throw(); + _error?.Throw(); } if (_sourceEnumerator == null) diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index d5acfef1f..77f259c9b 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -95,7 +95,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func _() { - var e = source.GetEnumerator(); + IEnumerator? e = source.GetEnumerator(); var stack = new Stack(); stack.Push(e); @@ -123,7 +123,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func> ScanBy> _(IEqualityComparer comparer) { var stateByKey = new Dictionary(comparer); - var prevKey = (false, default(TKey)!); - var nullKeyState = (false, default(TState)!); + (bool, TKey) prevKey = (false, default); + (bool, TState) nullKeyState = (false, default); var state = default(TState)!; bool TryGetState(TKey key, out TState value) From 24375db3a7189275030778c3620a10ed7d557e68 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sun, 17 Nov 2019 12:56:49 +0200 Subject: [PATCH 16/53] Add Nullable attributes from Nullable package. --- MoreLinq/MoreLinq.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index 286c8112b..31c10ac3a 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -246,4 +246,11 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + From 6c086140a7c1addeb6295a6d31d9d45bde738878 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sun, 17 Nov 2019 13:30:24 +0200 Subject: [PATCH 17/53] Resolve appveyor CI of "unexpected trailing whitespace" --- MoreLinq/Reactive/Subject.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MoreLinq/Reactive/Subject.cs b/MoreLinq/Reactive/Subject.cs index 53a369c19..bdbdcc7ea 100644 --- a/MoreLinq/Reactive/Subject.cs +++ b/MoreLinq/Reactive/Subject.cs @@ -37,7 +37,7 @@ public sealed class Subject : IObservable, IObserver bool IsMuted => _completed || _error != null; /// - /// + /// Notifies the provider that an observer is to receive notifications. /// /// /// @@ -88,7 +88,7 @@ public IDisposable Subscribe(IObserver observer) bool _shouldDeleteObserver; // delete (null) or remove an observer? /// - /// + /// Action OnNext /// /// public void OnNext(T value) @@ -128,14 +128,14 @@ public void OnNext(T value) } /// - /// + /// Action for Exception handling /// /// public void OnError(Exception error) => OnFinality(ref _error, error, (observer, err) => observer.OnError(err)); /// - /// + /// Action OnCompleted /// public void OnCompleted() => OnFinality(ref _completed, true, (observer, _) => observer.OnCompleted()); From 68d152b8ebf3b5f43585c0cea21ca818520a8818 Mon Sep 17 00:00:00 2001 From: moh-hassan Date: Sun, 17 Nov 2019 15:49:34 +0200 Subject: [PATCH 18/53] modify Extensions.g.cs and set nullability --- MoreLinq/Extensions.g.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index f3e32832f..f79e47375 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -2925,7 +2925,7 @@ public static IEnumerable> IndexBy( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) => MoreEnumerable. IndexBy(source, keySelector, comparer); + IEqualityComparer? comparer) => MoreEnumerable. IndexBy(source, keySelector, comparer); } From 97d424dfc7f1bf5346a4886b8a925512ed33a450 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 16:45:33 +0200 Subject: [PATCH 19/53] Fix build errors from previous merge --- MoreLinq/CountBy.cs | 4 +- MoreLinq/Experimental/Await.cs | 68 +++++++++++++++++----------------- MoreLinq/ScanBy.cs | 8 ++-- MoreLinq/ZipImpl.cs | 2 +- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 623fc14a8..00266c6e7 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -94,7 +94,7 @@ bool TryGetIndex(TKey key, out int i) keys = new List(); counts = new List(); - (bool, TKey) prevKey = (false, default); + var prevKey = (false, default(TKey)); var index = 0; foreach (var item in source) @@ -102,7 +102,7 @@ bool TryGetIndex(TKey key, out int i) var key = keySelector(item); if (// key same as the previous? then re-use the index - prevKey is (true, var pk) + prevKey is (true, {} pk) && cmp.GetHashCode(pk) == cmp.GetHashCode(key) && cmp.Equals(pk, key) // otherwise try & find index of the key diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 036864fe1..d0d940bd8 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -442,39 +442,6 @@ IEnumerable _(int? maxConcurrency, TaskScheduler scheduler, bool ordere var consumerCancellationTokenSource = new CancellationTokenSource(); (Exception?, Exception?) lastCriticalErrors = default; - void PostNotice(Notice notice, - (int, T, Task) item, - Exception? error) - { - // If a notice fails to post then assume critical error - // conditions (like low memory), capture the error without - // further allocation of resources and trip the cancellation - // token source used by the main loop waiting on notices. - // Note that only the "last" critical error is reported - // as maintaining a list would incur allocations. The idea - // here is to make a best effort attempt to report any of - // the error conditions that may be occuring, which is still - // better than nothing. - - try - { - var edi = error != null - ? ExceptionDispatchInfo.Capture(error) - : null; - notices.Add((notice, item, edi)); - } - catch (Exception e) - { - // Don't use ExceptionDispatchInfo.Capture here to avoid - // inducing allocations if already under low memory - // conditions. - - lastCriticalErrors = (e, error); - consumerCancellationTokenSource.Cancel(); - throw; - } - } - var completed = false; var cancellationTokenSource = new CancellationTokenSource(); @@ -505,6 +472,39 @@ await enumerator.StartAsync( { PostNotice(Notice.Error, default, e); } + + void PostNotice(Notice notice, + (int, T, Task) item, + Exception? error) + { + // If a notice fails to post then assume critical error + // conditions (like low memory), capture the error without + // further allocation of resources and trip the cancellation + // token source used by the main loop waiting on notices. + // Note that only the "last" critical error is reported + // as maintaining a list would incur allocations. The idea + // here is to make a best effort attempt to report any of + // the error conditions that may be occuring, which is still + // better than nothing. + + try + { + var edi = error != null + ? ExceptionDispatchInfo.Capture(error) + : null; + notices.Add((notice, item, edi)); + } + catch (Exception e) + { + // Don't use ExceptionDispatchInfo.Capture here to avoid + // inducing allocations if already under low memory + // conditions. + + lastCriticalErrors = (e, error); + consumerCancellationTokenSource.Cancel(); + throw; + } + } }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, @@ -533,7 +533,7 @@ await enumerator.StartAsync( : new AggregateException(error1)); } - (Notice kind, (int, T, Task) result, ExceptionDispatchInfo? error) = notice.Current; + var (kind, result, error) = notice.Current; if (kind == Notice.Error) { diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index 5c4c5e165..3b1b0649c 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -90,8 +90,8 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { var stateByKey = new Dictionary(comparer); - (bool, TKey) prevKey = (false, default); - (bool, TState) nullKeyState = (false, default); + var prevKey = (false, default(TKey)); + var nullKeyState = (false, default(TState)); var state = default(TState)!; bool TryGetState(TKey key, out TState value) @@ -100,7 +100,7 @@ bool TryGetState(TKey key, out TState value) { switch (nullKeyState) { - case (true, var v): + case (true, {} v): value = v; return true; case (false, _): @@ -118,7 +118,7 @@ bool TryGetState(TKey key, out TState value) var haveState = // key same as the previous? then re-use the state - prevKey is (true, var pk) && comparer.GetHashCode(pk) == comparer.GetHashCode(key) && comparer.Equals(pk, key) + prevKey is (true, {} pk) && comparer.GetHashCode(pk) == comparer.GetHashCode(key) && comparer.Equals(pk, key) // otherwise try & find state of the key || TryGetState(key, out state); diff --git a/MoreLinq/ZipImpl.cs b/MoreLinq/ZipImpl.cs index e8775fecc..1f008a1dc 100644 --- a/MoreLinq/ZipImpl.cs +++ b/MoreLinq/ZipImpl.cs @@ -32,7 +32,7 @@ static IEnumerable ZipImpl( IEnumerable? s4, Func resultSelector, int limit, - Folder? errorSelector = null) + Folder? errorSelector = null) { IEnumerator? e1 = null; IEnumerator? e2 = null; From e8669c2b01c8fee078a0c202435356dac2aff5e9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:03:12 +0200 Subject: [PATCH 20/53] Revert change in Subject visibility Reverts a change in commit: 311b5e3b52f90c43c14efa194fb38ecb33a249cc --- MoreLinq.Test/MoreLinq.Test.csproj | 1 + MoreLinq/Reactive/Subject.cs | 27 ++++----------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index 0f283923a..867d06dff 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -44,6 +44,7 @@ + diff --git a/MoreLinq/Reactive/Subject.cs b/MoreLinq/Reactive/Subject.cs index bdbdcc7ea..2f01e8047 100644 --- a/MoreLinq/Reactive/Subject.cs +++ b/MoreLinq/Reactive/Subject.cs @@ -15,17 +15,15 @@ // limitations under the License. #endregion +#nullable enable + namespace MoreLinq.Reactive { using System; using System.Collections.Generic; using Delegate = Delegating.Delegate; - /// - /// Subject - /// - /// - public sealed class Subject : IObservable, IObserver + sealed class Subject : IObservable, IObserver { List>? _observers; bool _completed; @@ -36,12 +34,6 @@ public sealed class Subject : IObservable, IObserver bool IsMuted => _completed || _error != null; - /// - /// Notifies the provider that an observer is to receive notifications. - /// - /// - /// - /// public IDisposable Subscribe(IObserver observer) { if (observer == null) throw new ArgumentNullException(nameof(observer)); @@ -87,10 +79,6 @@ public IDisposable Subscribe(IObserver observer) bool _shouldDeleteObserver; // delete (null) or remove an observer? - /// - /// Action OnNext - /// - /// public void OnNext(T value) { if (!HasObservers) @@ -127,16 +115,9 @@ public void OnNext(T value) } } - /// - /// Action for Exception handling - /// - /// public void OnError(Exception error) => - OnFinality(ref _error, error, (observer, err) => observer.OnError(err)); + OnFinality(ref _error, error, (observer, err) => observer.OnError(err!)); - /// - /// Action OnCompleted - /// public void OnCompleted() => OnFinality(ref _completed, true, (observer, _) => observer.OnCompleted()); From fe52462809a1208c9ee0746d8b6e0c6567b0c725 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:12:29 +0200 Subject: [PATCH 21/53] Revert changes to Await, Flatten & Memoize This reverts some changes from commit: 22f358ee1d97b99cf9be7adec2366a962dbbf91e --- MoreLinq/Experimental/Await.cs | 2 +- MoreLinq/Experimental/Memoize.cs | 2 +- MoreLinq/Flatten.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index d0d940bd8..ba4c962a9 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -537,7 +537,7 @@ void PostNotice(Notice notice, if (kind == Notice.Error) { - error?.Throw(); + error!.Throw(); } if (kind == Notice.End) diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index a6dcad91b..5aa3c13ad 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -117,7 +117,7 @@ public IEnumerator GetEnumerator() { if (index == _errorIndex) { - _error?.Throw(); + _error!.Throw(); } if (_sourceEnumerator == null) diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index 3cfaee4b6..14fd517ea 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -95,7 +95,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func _() { - IEnumerator? e = source.GetEnumerator(); + var e = source.GetEnumerator(); var stack = new Stack(); stack.Push(e); From 406a2563674839b81a70ff9299e425f372b3cb41 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:17:58 +0200 Subject: [PATCH 22/53] Revert some uses of dammit (!) Reverts a change in commit: 311b5e3b52f90c43c14efa194fb38ecb33a249cc --- MoreLinq/GroupAdjacent.cs | 2 +- MoreLinq/IndexBy.cs | 2 +- MoreLinq/PartialSort.cs | 2 +- MoreLinq/PendNode.cs | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/MoreLinq/GroupAdjacent.cs b/MoreLinq/GroupAdjacent.cs index f4d7dbc73..7e797ff42 100644 --- a/MoreLinq/GroupAdjacent.cs +++ b/MoreLinq/GroupAdjacent.cs @@ -268,7 +268,7 @@ static IEnumerable GroupAdjacentImpl( using var iterator = source.GetEnumerator(); - var group = default(TKey)!; + var group = default(TKey); var members = (List?) null; while (iterator.MoveNext()) diff --git a/MoreLinq/IndexBy.cs b/MoreLinq/IndexBy.cs index 9403c5bec..64fad32d3 100644 --- a/MoreLinq/IndexBy.cs +++ b/MoreLinq/IndexBy.cs @@ -70,7 +70,7 @@ public static IEnumerable> this IEnumerable source, Func keySelector, IEqualityComparer? comparer) => - from e in source.ScanBy(keySelector, k => (Index: -1, Item: default(TSource)!), (s, k, e) => (s.Index + 1, e), comparer) + from e in source.ScanBy(keySelector, k => (Index: -1, Item: default(TSource)), (s, k, e) => (s.Index + 1, e), comparer) select new KeyValuePair(e.Value.Index, e.Value.Item); } } diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index 34bfd3e36..eb514ebd2 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -231,7 +231,7 @@ static IEnumerable PartialSortByImpl( foreach (var item in source) { int i; - var key = default(TKey)!; + var key = default(TKey); if (keys != null) { key = keySelector!(item); diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index be0e68349..9da48e859 100644 --- a/MoreLinq/PendNode.cs +++ b/MoreLinq/PendNode.cs @@ -63,10 +63,10 @@ public IEnumerator GetEnumerator() { var i = 0; T[]? concats = null; // Array for > 4 concatenations - var concat1 = default(T)!; // Slots for up to 4 concatenations - var concat2 = default(T)!; - var concat3 = default(T)!; - var concat4 = default(T)!; + var concat1 = default(T); // Slots for up to 4 concatenations + var concat2 = default(T); + var concat3 = default(T); + var concat4 = default(T); var current = this; for (; current is Item item; current = item.Next) From 347b752e9f3da869ea176075c03cdd44090e5042 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:40:57 +0200 Subject: [PATCH 23/53] Add to-do review note for TrySingle --- MoreLinq/Experimental/TrySingle.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/MoreLinq/Experimental/TrySingle.cs b/MoreLinq/Experimental/TrySingle.cs index c0522041c..6be3f8212 100644 --- a/MoreLinq/Experimental/TrySingle.cs +++ b/MoreLinq/Experimental/TrySingle.cs @@ -96,6 +96,10 @@ public static (TCardinality Cardinality, T Value) public static TResult TrySingle(this IEnumerable source, TCardinality zero, TCardinality one, TCardinality many, + // TODO review second argument of resultSelector + // ...that can be defaulted to null for nullable references + // so the signature is not quite accurate, but can we do + // something about that? Func resultSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); From 22ca363854a4c300d9019587771f8c42301d40d7 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:42:15 +0200 Subject: [PATCH 24/53] Fix enumerators type in Transpose --- MoreLinq/Transpose.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MoreLinq/Transpose.cs b/MoreLinq/Transpose.cs index f3cb02888..54ff80c14 100644 --- a/MoreLinq/Transpose.cs +++ b/MoreLinq/Transpose.cs @@ -58,7 +58,7 @@ public static IEnumerable> Transpose(this IEnumerable> _() { - var enumerators = source.Select(e => e.GetEnumerator()).Acquire(); + IEnumerator?[] enumerators = source.Select(e => e.GetEnumerator()).Acquire(); try { @@ -68,17 +68,18 @@ public static IEnumerable> Transpose(this IEnumerable Date: Wed, 12 Aug 2020 17:43:23 +0200 Subject: [PATCH 25/53] Revert change to condition in PartialSort Reverts a change in commit: 311b5e3b52f90c43c14efa194fb38ecb33a249cc --- MoreLinq/PartialSort.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index eb514ebd2..3d5a66d70 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -225,7 +225,7 @@ static IEnumerable PartialSortByImpl( IComparer? keyComparer, IComparer? comparer) { - var keys = keySelector == null ? null : new List(count); + var keys = keySelector != null ? new List(count) : null; var top = new List(count); foreach (var item in source) From aea549362ea07638725a8b90469fa950f96ab442 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Wed, 12 Aug 2020 17:53:41 +0200 Subject: [PATCH 26/53] Remove unused namespace post removal of assertions --- MoreLinq/EquiZip.cs | 1 - MoreLinq/Experimental/Memoize.cs | 1 - MoreLinq/GroupAdjacent.cs | 1 - MoreLinq/Pad.cs | 1 - MoreLinq/PartialSort.cs | 1 - MoreLinq/ToDataTable.cs | 1 - MoreLinq/ToDelimitedString.cs | 1 - 7 files changed, 7 deletions(-) diff --git a/MoreLinq/EquiZip.cs b/MoreLinq/EquiZip.cs index e7b990038..9cde2a6d1 100644 --- a/MoreLinq/EquiZip.cs +++ b/MoreLinq/EquiZip.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 5aa3c13ad..2ac99aa41 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -20,7 +20,6 @@ namespace MoreLinq.Experimental using System; using System.Collections; using System.Collections.Generic; - using System.Diagnostics; using System.Runtime.ExceptionServices; static partial class ExperimentalEnumerable diff --git a/MoreLinq/GroupAdjacent.cs b/MoreLinq/GroupAdjacent.cs index 7e797ff42..c8c505735 100644 --- a/MoreLinq/GroupAdjacent.cs +++ b/MoreLinq/GroupAdjacent.cs @@ -21,7 +21,6 @@ namespace MoreLinq using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index 1d91bb3a4..dab2e5772 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; static partial class MoreEnumerable { diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index 3d5a66d70..0b6039780 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 993ba1600..b3bda9498 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -20,7 +20,6 @@ namespace MoreLinq using System; using System.Collections.Generic; using System.Data; - using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; diff --git a/MoreLinq/ToDelimitedString.cs b/MoreLinq/ToDelimitedString.cs index c02291c70..c77d7eeb2 100644 --- a/MoreLinq/ToDelimitedString.cs +++ b/MoreLinq/ToDelimitedString.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Text; static partial class MoreEnumerable From cca922759fccd930d978773b3f6bb814334321a7 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 10:47:16 +0200 Subject: [PATCH 27/53] Require .NET Core SDK 3.1.300 --- .travis.yml | 2 +- global.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 99a6754fc..9c82769aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ solution: MoreLinq.sln mono: 5.0.1 dist: xenial sudo: required -dotnet: 3.1.102 +dotnet: 3.1.300 env: - CONFIGURATION=Debug - CONFIGURATION=Release diff --git a/global.json b/global.json index cb62ab482..aca3f3cb6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "3.1.102", + "version": "3.1.300", "rollForward": "latestFeature" } } From 3d602b90c26cc543fd2837d87ae24a823c06caad Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 14:57:31 +0200 Subject: [PATCH 28/53] Simplify variable declaration/init --- MoreLinq/CountBy.cs | 2 +- MoreLinq/PendNode.cs | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 00266c6e7..7aa62f502 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -94,7 +94,7 @@ bool TryGetIndex(TKey key, out int i) keys = new List(); counts = new List(); - var prevKey = (false, default(TKey)); + (bool, TKey) prevKey = default; var index = 0; foreach (var item in source) diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index 9da48e859..34dcdbb7b 100644 --- a/MoreLinq/PendNode.cs +++ b/MoreLinq/PendNode.cs @@ -62,11 +62,11 @@ sealed class Source : PendNode public IEnumerator GetEnumerator() { var i = 0; - T[]? concats = null; // Array for > 4 concatenations - var concat1 = default(T); // Slots for up to 4 concatenations - var concat2 = default(T); - var concat3 = default(T); - var concat4 = default(T); + T[]? concats = null; // Array for > 4 concatenations + (bool, T Value) concat1 = default; // Slots for up to 4 concatenations + (bool, T Value) concat2 = default; + (bool, T Value) concat3 = default; + (bool, T Value) concat4 = default; var current = this; for (; current is Item item; current = item.Next) @@ -87,10 +87,10 @@ public IEnumerator GetEnumerator() { switch (i++) { - case 0: concat1 = item.Value; break; - case 1: concat2 = item.Value; break; - case 2: concat3 = item.Value; break; - case 3: concat4 = item.Value; break; + case 0: concat1 = (true, item.Value); break; + case 1: concat2 = (true, item.Value); break; + case 2: concat3 = (true, item.Value); break; + case 3: concat4 = (true, item.Value); break; default: throw new IndexOutOfRangeException(); } continue; @@ -108,10 +108,10 @@ public IEnumerator GetEnumerator() if (concats == null) { - if (i == 4) { yield return concat4!; i--; } - if (i == 3) { yield return concat3!; i--; } - if (i == 2) { yield return concat2!; i--; } - if (i == 1) { yield return concat1!; i--; } + if (i == 4) { yield return concat4.Value; i--; } + if (i == 3) { yield return concat3.Value; i--; } + if (i == 2) { yield return concat2.Value; i--; } + if (i == 1) { yield return concat1.Value; i--; } yield break; } From 939686fd47aa1bd74eea762b43794d9e6c832aa2 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 12:08:26 +0200 Subject: [PATCH 29/53] Remove need for dammit (!) in FillForward --- MoreLinq/FillForward.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/MoreLinq/FillForward.cs b/MoreLinq/FillForward.cs index dc20668b7..0577b8053 100644 --- a/MoreLinq/FillForward.cs +++ b/MoreLinq/FillForward.cs @@ -106,22 +106,21 @@ public static IEnumerable FillForward(this IEnumerable source, Func FillForwardImpl(IEnumerable source, Func predicate, Func? fillSelector) { - var seeded = false; - var seed = default(T)!; + (bool, T) seed = default; + foreach (var item in source) { if (predicate(item)) { - yield return seeded + yield return seed is (true, {} someSeed) ? fillSelector != null - ? fillSelector(item, seed!) - : seed! + ? fillSelector(item, someSeed) + : someSeed : item; } else { - seeded = true; - seed = item; + seed = (true, item); yield return item; } } From b8303de979d9ad1f8c179f5a348553ae2ab2da34 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 12:16:47 +0200 Subject: [PATCH 30/53] Remove need for dammit (!) in GroupAdjacent --- MoreLinq/GroupAdjacent.cs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/MoreLinq/GroupAdjacent.cs b/MoreLinq/GroupAdjacent.cs index c8c505735..f19eeeb9a 100644 --- a/MoreLinq/GroupAdjacent.cs +++ b/MoreLinq/GroupAdjacent.cs @@ -264,37 +264,39 @@ static IEnumerable GroupAdjacentImpl( Func, TResult> resultSelector, IEqualityComparer comparer) { - using var iterator = source.GetEnumerator(); - var group = default(TKey); - var members = (List?) null; + (TKey, List) group = default; while (iterator.MoveNext()) { var key = keySelector(iterator.Current); var element = elementSelector(iterator.Current); - if (members != null && comparer.Equals(group!, key)) - { - members.Add(element); - } - else + + if (group is ({} k, {} members)) { - if (members != null) - yield return resultSelector(group!, members); - group = key; - members = new List { element }; + if (comparer.Equals(k, key)) + { + members.Add(element); + continue; + } + else + { + yield return resultSelector(k, members); + } } + + group = (key, new List { element }); } - if (members != null) - yield return resultSelector(group!, members); + { + if (group is ({} k, {} members)) + yield return resultSelector(k, members); + } } - static IGrouping CreateGroupAdjacentGrouping(TKey key, IList members) - { - return Grouping.Create(key, members.IsReadOnly ? members : new ReadOnlyCollection(members)); - } + static IGrouping CreateGroupAdjacentGrouping(TKey key, IList members) => + Grouping.Create(key, members.IsReadOnly ? members : new ReadOnlyCollection(members)); static class Grouping { From 9725078ce61640b0fde9d8a37bcfbff38353d6c5 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 12:19:29 +0200 Subject: [PATCH 31/53] Revert assert removal --- MoreLinq/Pad.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index dab2e5772..cfe9c6921 100644 --- a/MoreLinq/Pad.cs +++ b/MoreLinq/Pad.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Diagnostics; static partial class MoreEnumerable { @@ -116,6 +117,8 @@ public static IEnumerable Pad(this IEnumerable source static IEnumerable PadImpl(IEnumerable source, int width, T padding, Func? paddingSelector) { + Debug.Assert(width >= 0); + var count = 0; foreach (var item in source) { From ea9f318d8ba18e4446f77b2b734e5a51ea3a1ea8 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 14:21:05 +0200 Subject: [PATCH 32/53] Remove need for dammit (!) in ScanBy --- MoreLinq/ScanBy.cs | 71 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index 3b1b0649c..a1825cd6d 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; static partial class MoreEnumerable { @@ -90,53 +91,59 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { var stateByKey = new Dictionary(comparer); - var prevKey = (false, default(TKey)); var nullKeyState = (false, default(TState)); - var state = default(TState)!; - bool TryGetState(TKey key, out TState value) - { - if (key == null) - { - switch (nullKeyState) - { - case (true, {} v): - value = v; - return true; - case (false, _): - value = default!; - return false; - } - } - - return stateByKey.TryGetValue(key, out value); - } + (bool, TKey, TState) prev = default; foreach (var item in source) { var key = keySelector(item); - var haveState = - // key same as the previous? then re-use the state - prevKey is (true, {} pk) && comparer.GetHashCode(pk) == comparer.GetHashCode(key) && comparer.Equals(pk, key) - // otherwise try & find state of the key - || TryGetState(key, out state); + var state = // key same as the previous? then re-use the state + prev is (true, {} pk, {} ps) + && comparer.GetHashCode(pk) == comparer.GetHashCode(key) + && comparer.Equals(pk, key) ? ps + : // otherwise try & find state of the key + TryGetState(stateByKey, nullKeyState, key, out var ns) ? ns + : seedSelector(key); - if (!haveState) - state = seedSelector(key); + state = accumulator(state, key, item); - state = accumulator(state!, key, item); - - if (key != null) - stateByKey[key] = state; - else + if (key is null) nullKeyState = (true, state); + else + stateByKey[key] = state; yield return new KeyValuePair(key, state); - prevKey = (true, key); + prev = (true, key, state); + } + } + } + + // Move this back to a local function of the iterator block once + // attributes on local functions become legal with C# 9: + // https://github.com/dotnet/csharplang/issues/1888 + + static bool TryGetState(Dictionary stateByKey, + (bool, TState) nullKeyState, + TKey key, + [MaybeNullWhen(false)] out TState value) + { + if (key is null) + { + switch (nullKeyState) + { + case (true, {} v): + value = v; + return true; + case (false, _): + value = default!; + return false; } } + + return stateByKey.TryGetValue(key, out value); } } } From 948c83c4e6f260a1c55e761060571dec4f168dfe Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 14:34:35 +0200 Subject: [PATCH 33/53] Add minimal null-aware dictionary wrapper --- MoreLinq/ScanBy.cs | 76 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index a1825cd6d..c51afad38 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -90,8 +90,7 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { - var stateByKey = new Dictionary(comparer); - var nullKeyState = (false, default(TState)); + var stateByKey = new Dict(comparer); (bool, TKey, TState) prev = default; @@ -104,15 +103,12 @@ prev is (true, {} pk, {} ps) && comparer.GetHashCode(pk) == comparer.GetHashCode(key) && comparer.Equals(pk, key) ? ps : // otherwise try & find state of the key - TryGetState(stateByKey, nullKeyState, key, out var ns) ? ns + stateByKey.TryGetValue(key, out var ns) ? ns : seedSelector(key); state = accumulator(state, key, item); - if (key is null) - nullKeyState = (true, state); - else - stateByKey[key] = state; + stateByKey[key] = state; yield return new KeyValuePair(key, state); @@ -121,29 +117,63 @@ prev is (true, {} pk, {} ps) } } - // Move this back to a local function of the iterator block once - // attributes on local functions become legal with C# 9: - // https://github.com/dotnet/csharplang/issues/1888 + /// + /// A minimal wrapper that + /// allows null keys when is a + /// reference type. + /// + + // Add members if and when needed to keep coverage. - static bool TryGetState(Dictionary stateByKey, - (bool, TState) nullKeyState, - TKey key, - [MaybeNullWhen(false)] out TState value) + struct Dict { - if (key is null) + readonly Dictionary _dict; + (bool, TValue) _null; + + public Dict(IEqualityComparer comparer) : this() { - switch (nullKeyState) + _dict = new Dictionary(comparer); + _null = default; + } + + public TValue this[TKey key] + { + set { - case (true, {} v): - value = v; - return true; - case (false, _): - value = default!; - return false; + DefaultGuard(); + + if (key is null) + _null = (true, value); + else + _dict[key] = value; + } + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + DefaultGuard(); + + if (key is null) + { + switch (_null) + { + case (true, {} v): + value = v; + return true; + case (false, _): + value = default!; + return false; + } } + + return _dict.TryGetValue(key, out value); } - return stateByKey.TryGetValue(key, out value); + void DefaultGuard() + { + if (_dict is null) + throw new InvalidOperationException(); + } } } } From 88be3ac7319fda3228ad2ae1358040d1dc33ef3f Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:18:22 +0200 Subject: [PATCH 34/53] Extract & reuse dict wrapper in CountBy --- MoreLinq/CountBy.cs | 21 +++------------ MoreLinq/ScanBy.cs | 62 +-------------------------------------------- 2 files changed, 4 insertions(+), 79 deletions(-) diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 7aa62f502..e671fee63 100644 --- a/MoreLinq/CountBy.cs +++ b/MoreLinq/CountBy.cs @@ -78,19 +78,7 @@ public static IEnumerable> CountBy(this I void Loop(IEqualityComparer cmp) { - var dic = new Dictionary(cmp); - var nullIndex = (int?) null; - - bool TryGetIndex(TKey key, out int i) - { - if (key == null) - { - i = nullIndex.GetValueOrDefault(); - return nullIndex.HasValue; - } - - return dic.TryGetValue(key, out i); - } + var dic = new Collections.Dictionary(cmp); keys = new List(); counts = new List(); @@ -106,17 +94,14 @@ prevKey is (true, {} pk) && cmp.GetHashCode(pk) == cmp.GetHashCode(key) && cmp.Equals(pk, key) // otherwise try & find index of the key - || TryGetIndex(key, out index)) + || dic.TryGetValue(key, out index)) { counts[index]++; } else { index = keys.Count; - if (key != null) - dic[key] = index; - else - nullIndex = index; + dic[key] = index; keys.Add(key); counts.Add(1); } diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index c51afad38..b91a02dc4 100644 --- a/MoreLinq/ScanBy.cs +++ b/MoreLinq/ScanBy.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; static partial class MoreEnumerable { @@ -90,7 +89,7 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { - var stateByKey = new Dict(comparer); + var stateByKey = new Collections.Dictionary(comparer); (bool, TKey, TState) prev = default; @@ -116,64 +115,5 @@ prev is (true, {} pk, {} ps) } } } - - /// - /// A minimal wrapper that - /// allows null keys when is a - /// reference type. - /// - - // Add members if and when needed to keep coverage. - - struct Dict - { - readonly Dictionary _dict; - (bool, TValue) _null; - - public Dict(IEqualityComparer comparer) : this() - { - _dict = new Dictionary(comparer); - _null = default; - } - - public TValue this[TKey key] - { - set - { - DefaultGuard(); - - if (key is null) - _null = (true, value); - else - _dict[key] = value; - } - } - - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) - { - DefaultGuard(); - - if (key is null) - { - switch (_null) - { - case (true, {} v): - value = v; - return true; - case (false, _): - value = default!; - return false; - } - } - - return _dict.TryGetValue(key, out value); - } - - void DefaultGuard() - { - if (_dict is null) - throw new InvalidOperationException(); - } - } } } From 38437b3a74c3ef0ff457bfd038a8a73c35d79aa7 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:21:27 +0200 Subject: [PATCH 35/53] Add missing Collections file --- MoreLinq/Collections/Dictionary.cs | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 MoreLinq/Collections/Dictionary.cs diff --git a/MoreLinq/Collections/Dictionary.cs b/MoreLinq/Collections/Dictionary.cs new file mode 100644 index 000000000..4241ae650 --- /dev/null +++ b/MoreLinq/Collections/Dictionary.cs @@ -0,0 +1,82 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2020 Atif Aziz, Leandro F. Vieira (leandromoh). All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +namespace MoreLinq.Collections +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + + /// + /// A minimal wrapper that + /// allows null keys when is a + /// reference type. + /// + + // Add members if and when needed to keep coverage. + + struct Dictionary + { + readonly System.Collections.Generic.Dictionary _dict; + (bool, TValue) _null; + + public Dictionary(IEqualityComparer comparer) : this() + { + _dict = new System.Collections.Generic.Dictionary(comparer); + _null = default; + } + + public TValue this[TKey key] + { + set + { + DefaultGuard(); + + if (key is null) + _null = (true, value); + else + _dict[key] = value; + } + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + { + DefaultGuard(); + + if (key is null) + { + switch (_null) + { + case (true, {} v): + value = v; + return true; + case (false, _): + value = default!; + return false; + } + } + + return _dict.TryGetValue(key, out value); + } + + void DefaultGuard() + { + if (_dict is null) + throw new InvalidOperationException(); + } + } +} From e4a4922e696b2f60f1dc75bb9e8e6443ae3f1b84 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:22:03 +0200 Subject: [PATCH 36/53] Fix type of DelegatingDisposable field --- MoreLinq/Delegating.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MoreLinq/Delegating.cs b/MoreLinq/Delegating.cs index 4b9e8c189..fcdda4e5a 100644 --- a/MoreLinq/Delegating.cs +++ b/MoreLinq/Delegating.cs @@ -40,7 +40,7 @@ public static IObserver Observer(Action onNext, sealed class DelegatingDisposable : IDisposable { - Action _delegatee; + Action? _delegatee; public DelegatingDisposable(Action delegatee) => _delegatee = delegatee ?? throw new ArgumentNullException(nameof(delegatee)); @@ -48,7 +48,7 @@ public DelegatingDisposable(Action delegatee) => public void Dispose() { var delegatee = _delegatee; - if (delegatee == null || Interlocked.CompareExchange(ref _delegatee, null!, delegatee) != delegatee) + if (delegatee == null || Interlocked.CompareExchange(ref _delegatee, null, delegatee) != delegatee) return; delegatee(); } From 3c6282e4447eb15c8029552befba80cdf6f24369 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:25:03 +0200 Subject: [PATCH 37/53] Revert most EndsWith edits --- MoreLinq/EndsWith.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MoreLinq/EndsWith.cs b/MoreLinq/EndsWith.cs index 76ae22857..4c9664b79 100644 --- a/MoreLinq/EndsWith.cs +++ b/MoreLinq/EndsWith.cs @@ -77,13 +77,13 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second, return second.TryGetCollectionCount() is {} secondCount ? first.TryGetCollectionCount() is {} firstCount && secondCount > firstCount ? false - : Impl(second, secondCount, comparer) - : Impl(secondList = second.ToList(), secondList.Count, comparer); + : Impl(second, secondCount) + : Impl(secondList = second.ToList(), secondList.Count); - bool Impl(IEnumerable snd, int count, IEqualityComparer cmp) + bool Impl(IEnumerable snd, int count) { using var firstIter = first.TakeLast(count).GetEnumerator(); - return snd.All(item => firstIter.MoveNext() && cmp.Equals(firstIter.Current, item)); + return snd.All(item => firstIter.MoveNext() && comparer.Equals(firstIter.Current, item)); } } } From 8439ced2c9e0e79a5992e618c2e6b2e60c661a11 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:27:35 +0200 Subject: [PATCH 38/53] Revert unrelated edit in Await --- MoreLinq/Experimental/Await.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index ba4c962a9..018818f02 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -587,7 +587,7 @@ void PostNotice(Notice notice, } } - if (holds != null && holds.Count > 0) // yield any withheld, which should be in order... + if (holds?.Count > 0) // yield any withheld, which should be in order... { foreach (var (key, x, value) in holds) { From 0c3e2e8e0ee287d0163ab3936443c05e9a4eccea Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:41:09 +0200 Subject: [PATCH 39/53] Note why "#nullable enable" is needed for generated code --- MoreLinq/Extensions.ToDataTable.g.cs | 11 ++++++++++- MoreLinq/Extensions.g.cs | 11 ++++++++++- bld/ExtensionsGenerator/Program.cs | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index e938397a0..9920b9323 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -17,7 +17,16 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. -#nullable enable // TODO(nullable) review why this is needed +#nullable enable // required for auto-generated sources (see below why) + +// > Older code generation strategies may not be nullable aware. Setting the +// > project-level nullable context to "enable" could result in many +// > warnings that a user is unable to fix. To support this scenario any syntax +// > tree that is determined to be generated will have its nullable state +// > implicitly set to "disable", regardless of the overall project state. +// +// Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code + namespace MoreLinq.Extensions { diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 8f2fb18b2..6c5cafbc1 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -17,7 +17,16 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. -#nullable enable // TODO(nullable) review why this is needed +#nullable enable // required for auto-generated sources (see below why) + +// > Older code generation strategies may not be nullable aware. Setting the +// > project-level nullable context to "enable" could result in many +// > warnings that a user is unable to fix. To support this scenario any syntax +// > tree that is determined to be generated will have its nullable state +// > implicitly set to "disable", regardless of the overall project state. +// +// Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code + namespace MoreLinq.Extensions { diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 52ed4105e..8e1ce5cb6 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -303,7 +303,16 @@ public static partial class {m.Name}Extension // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. -#nullable enable // TODO(nullable) review why this is needed +#nullable enable // required for auto-generated sources (see below why) + +// > Older code generation strategies may not be nullable aware. Setting the +// > project-level nullable context to ""enable"" could result in many +// > warnings that a user is unable to fix. To support this scenario any syntax +// > tree that is determined to be generated will have its nullable state +// > implicitly set to ""disable"", regardless of the overall project state. +// +// Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code + namespace MoreLinq.Extensions {{ From d596449d25378dcca79ae60aff7319ec3c26f2b9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:42:56 +0200 Subject: [PATCH 40/53] Remove extra blank line --- MoreLinq/Extensions.ToDataTable.g.cs | 1 - MoreLinq/Extensions.g.cs | 1 - bld/ExtensionsGenerator/Program.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index 9920b9323..fc9ace256 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -27,7 +27,6 @@ // // Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code - namespace MoreLinq.Extensions { using System; diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 6c5cafbc1..3a8650b3c 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -27,7 +27,6 @@ // // Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code - namespace MoreLinq.Extensions { using System; diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 8e1ce5cb6..5ade4e451 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -313,7 +313,6 @@ public static partial class {m.Name}Extension // // Source: https://github.com/dotnet/roslyn/blob/70e158ba6c2c99bd3c3fc0754af0dbf82a6d353d/docs/features/nullable-reference-types.md#generated-code - namespace MoreLinq.Extensions {{ {string.Join("\n", imports)} From b87b38183a4b420b743e96bf068c256349e8506f Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:50:41 +0200 Subject: [PATCH 41/53] Remove need for dammit (!) in FallbackIfEmpty --- MoreLinq/FallbackIfEmpty.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/MoreLinq/FallbackIfEmpty.cs b/MoreLinq/FallbackIfEmpty.cs index f73caa3fe..d872ce192 100644 --- a/MoreLinq/FallbackIfEmpty.cs +++ b/MoreLinq/FallbackIfEmpty.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Diagnostics; static partial class MoreEnumerable { @@ -183,15 +184,12 @@ IEnumerable _() IEnumerable Fallback() { - switch (count) - { - case null: return fallback!; - case int n when n >= 1 && n <= 4: return FallbackOnArgs(); - default: throw new ArgumentOutOfRangeException(nameof(count), count, null); - } + return fallback is {} seq ? seq : FallbackOnArgs(); IEnumerable FallbackOnArgs() { + Debug.Assert(count >= 1 && count <= 4); + yield return fallback1; if (count > 1) yield return fallback2; if (count > 2) yield return fallback3; From 5fbe0f04a0c9770380f7fca6c2786c12c82f213d Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 15:53:00 +0200 Subject: [PATCH 42/53] Revert unneeded change in FillBackward --- MoreLinq/FillBackward.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MoreLinq/FillBackward.cs b/MoreLinq/FillBackward.cs index bef75ce1c..b7edd86b6 100644 --- a/MoreLinq/FillBackward.cs +++ b/MoreLinq/FillBackward.cs @@ -133,7 +133,7 @@ static IEnumerable FillBackwardImpl(IEnumerable source, Func p } } - if (blanks != null && blanks.Count > 0) + if (blanks?.Count > 0) { foreach (var blank in blanks) yield return blank; From b2486f23ad8de04b4083974c48dfd171f744445b Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 16:37:17 +0200 Subject: [PATCH 43/53] More honest "Extrema<,>" in light of nullability --- MoreLinq/MaxBy.cs | 78 ++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/MoreLinq/MaxBy.cs b/MoreLinq/MaxBy.cs index c32812208..050240334 100644 --- a/MoreLinq/MaxBy.cs +++ b/MoreLinq/MaxBy.cs @@ -253,27 +253,38 @@ public IEnumerable TakeLast(int count) => static class Extrema { - public static readonly Extrema , T> First = new FirstExtrema(); - public static readonly Extrema, T> Last = new LastExtrema(); + public static readonly Extrema? , T> First = new FirstExtrema(); + public static readonly Extrema?, T> Last = new LastExtrema(); - sealed class FirstExtrema : Extrema, T> + sealed class FirstExtrema : Extrema?, T> { - protected override IEnumerable GetSomeEnumerable(List store) => store; - protected override int Count(List store) => store?.Count ?? 0; - protected override void Push(ref List store, T item) => (store ??= new List()).Add(item); - protected override bool TryPop(ref List store) => false; + public override List? New() => null; + public override void Restart(ref List? store) => store = null; + public override IEnumerable GetEnumerable(List? store) => store ?? Enumerable.Empty(); + + public override void Add(ref List? store, int? limit, T item) + { + if (limit == null || store is null || store.Count < limit) + (store ??= new List()).Add(item); + } } - sealed class LastExtrema : Extrema, T> + sealed class LastExtrema : Extrema?, T> { - protected override IEnumerable GetSomeEnumerable(Queue store) => store; - protected override int Count(Queue store) => store?.Count ?? 0; - protected override void Push(ref Queue store, T item) => (store ??= new Queue()).Enqueue(item); - protected override bool TryPop(ref Queue store) { store.Dequeue(); return true; } + public override Queue? New() => null; + public override void Restart(ref Queue? store) => store = null; + public override IEnumerable GetEnumerable(Queue? store) => store ?? Enumerable.Empty(); + + public override void Add(ref Queue? store, int? limit, T item) + { + if (limit is {} n && store is {} queue && queue.Count == n) + queue.Dequeue(); + (store ??= new Queue()).Enqueue(item); + } } } - sealed class Extremum : Extrema<(bool HasValue, T Value), T> + sealed class Extremum : Extrema<(bool, T), T> { public static readonly Extrema<(bool, T), T> First = new Extremum(false); public static readonly Extrema<(bool, T), T> Last = new Extremum(true); @@ -281,19 +292,17 @@ sealed class Extremum : Extrema<(bool HasValue, T Value), T> readonly bool _poppable; Extremum(bool poppable) => _poppable = poppable; - protected override IEnumerable GetSomeEnumerable((bool HasValue, T Value) store) => - Enumerable.Repeat(store.Value, 1); + public override (bool, T) New() => default; + public override void Restart(ref (bool, T) store) => store = default; - protected override int Count((bool HasValue, T Value) store) => store.HasValue ? 1 : 0; - protected override void Push(ref (bool, T) store, T item) => store = (true, item); + public override IEnumerable GetEnumerable((bool, T) store) => + store is (true, var item) ? Enumerable.Repeat(item, 1) : Enumerable.Empty(); - protected override bool TryPop(ref (bool, T) store) + public override void Add(ref (bool, T) store, int? limit, T item) { - if (!_poppable) - return false; - - Restart(ref store); - return true; + if (!_poppable && store is (true, _)) + return; + store = (true, item); } } } @@ -346,25 +355,10 @@ IEnumerable Extrema() abstract class Extrema { - public virtual TStore New() => default!; - public virtual void Restart(ref TStore store) => store = default!; - - public void Add(ref TStore store, int? limit, T item) - { - if (limit == null || Count(store) < limit || TryPop(ref store)) - Push(ref store, item); - } - - protected abstract int Count(TStore store); - protected abstract void Push(ref TStore store, T item); - protected abstract bool TryPop(ref TStore store); - - public virtual IEnumerable GetEnumerable(TStore store) => - Count(store) > 0 - ? GetSomeEnumerable(store) - : Enumerable.Empty(); - - protected abstract IEnumerable GetSomeEnumerable(TStore store); + public abstract TStore New(); + public abstract void Restart(ref TStore store); + public abstract IEnumerable GetEnumerable(TStore store); + public abstract void Add(ref TStore store, int? limit, T item); } } } From cda81f0e4c37266d2c6d3913831bbf2bfc246b99 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 16:39:18 +0200 Subject: [PATCH 44/53] Revert unrelated style changes in OrderBy --- MoreLinq/OrderBy.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MoreLinq/OrderBy.cs b/MoreLinq/OrderBy.cs index 9b5c97e18..02820c4bb 100644 --- a/MoreLinq/OrderBy.cs +++ b/MoreLinq/OrderBy.cs @@ -54,8 +54,8 @@ public static IOrderedEnumerable OrderBy(this IEnumerable source, if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return direction == OrderByDirection.Ascending - ? source.OrderBy(keySelector, comparer) - : source.OrderByDescending(keySelector, comparer); + ? source.OrderBy(keySelector, comparer) + : source.OrderByDescending(keySelector, comparer); } /// @@ -89,8 +89,8 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnumerable s if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return direction == OrderByDirection.Ascending - ? source.ThenBy(keySelector, comparer) - : source.ThenByDescending(keySelector, comparer); + ? source.ThenBy(keySelector, comparer) + : source.ThenByDescending(keySelector, comparer); } } } From c6d59ff78fba0b24778a5b97504bfa0b3211564b Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:27:21 +0200 Subject: [PATCH 45/53] Reduce one dammit (!) via better PartialSort/By --- MoreLinq/PartialSort.cs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index 0b6039780..60076133a 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -227,33 +227,35 @@ static IEnumerable PartialSortByImpl( var keys = keySelector != null ? new List(count) : null; var top = new List(count); + int? Insert(List list, T item, IComparer? comparer) + { + var i = list.BinarySearch(item, comparer); + if (i < 0 && (i = ~i) >= count) + return null; + if (list.Count == count) + list.RemoveAt(count - 1); + list.Insert(i, item); + return i; + } + foreach (var item in source) { - int i; - var key = default(TKey); if (keys != null) { - key = keySelector!(item); - i = keys.BinarySearch(key, keyComparer); + var key = keySelector!(item); + if (Insert(keys, key, keyComparer) is {} i) + { + if (top.Count == count) + top.RemoveAt(count - 1); + top.Insert(i, item); + } } else { - i = top.BinarySearch(item, comparer); - } - - if (i < 0 && (i = ~i) >= count) - continue; - - if (top.Count == count) - { - keys?.RemoveAt(top.Count - 1); - top.RemoveAt(top.Count - 1); + _ = Insert(top, item, comparer); } // TODO Stable sorting - - keys?.Insert(i, key!); - top.Insert(i, item); } // ReSharper disable once LoopCanBeConvertedToQuery From 4d27c75f1d170e1d50b4bb79f060d6a1f6da67a1 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:30:39 +0200 Subject: [PATCH 46/53] Move empty array class to own file --- MoreLinq/EmptyArray.cs | 7 +++++++ MoreLinq/ToDataTable.cs | 7 +------ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 MoreLinq/EmptyArray.cs diff --git a/MoreLinq/EmptyArray.cs b/MoreLinq/EmptyArray.cs new file mode 100644 index 000000000..209912ee0 --- /dev/null +++ b/MoreLinq/EmptyArray.cs @@ -0,0 +1,7 @@ +namespace MoreLinq +{ + static class EmptyArray + { + public static readonly T[] Value = new T[0]; + } +} diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index b3bda9498..65b5afc11 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -24,11 +24,6 @@ namespace MoreLinq using System.Linq.Expressions; using System.Reflection; - static class Array - { - public static readonly T[] Empty = new T[0]; - } - static partial class MoreEnumerable { /// @@ -46,7 +41,7 @@ static partial class MoreEnumerable public static TTable ToDataTable(this IEnumerable source, TTable table) where TTable : DataTable { - return ToDataTable(source, table, Array>>.Empty); + return ToDataTable(source, table, EmptyArray>>.Value); } /// From a534a9bb337d34dbd04735d2a633abc7a95ae4f9 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:37:14 +0200 Subject: [PATCH 47/53] Revert changes to generator project --- bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj index 1f7d04d1f..f7eb51e48 100644 --- a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj +++ b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj @@ -6,7 +6,7 @@ false - - + + From 2e5edfcd8ed902711cf0e5441ca6a0a065e5d4bb Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:47:29 +0200 Subject: [PATCH 48/53] Remove need for dammit (!) in ZipImpl --- MoreLinq/ZipImpl.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MoreLinq/ZipImpl.cs b/MoreLinq/ZipImpl.cs index 1f008a1dc..3bf674acf 100644 --- a/MoreLinq/ZipImpl.cs +++ b/MoreLinq/ZipImpl.cs @@ -50,10 +50,10 @@ static IEnumerable ZipImpl( while (true) { var n = 0; - var v1 = Read(ref e1, ++n); - var v2 = Read(ref e2, ++n); - var v3 = Read(ref e3, ++n); - var v4 = Read(ref e4, ++n); + var (_, v1) = Read(ref e1, ++n); + var (_, v2) = Read(ref e2, ++n); + var (_, v3) = Read(ref e3, ++n); + var (_, v4) = Read(ref e4, ++n); if (terminations <= limit) yield return resultSelector(v1, v2, v3, v4); @@ -69,28 +69,28 @@ static IEnumerable ZipImpl( e4?.Dispose(); } - T Read(ref IEnumerator? e, int n) + (bool, T) Read(ref IEnumerator? e, int n) { if (e == null || terminations > limit) - return default!; + return default; - T value; + (bool, T) result; if (e.MoveNext()) { - value = e.Current; + result = (true, e.Current); } else { e.Dispose(); e = null; terminations++; - value = default!; + result = default; } if (errorSelector != null && terminations > 0 && terminations < n) throw errorSelector(e1, e2, e3, e4); - return value; + return result; } } } From 1ae52759a31af1dede1d7a043c7255c136149a1c Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:47:40 +0200 Subject: [PATCH 49/53] Revert "Remove need for dammit (!) in ZipImpl" This reverts commit 2e5edfcd8ed902711cf0e5441ca6a0a065e5d4bb. --- MoreLinq/ZipImpl.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/MoreLinq/ZipImpl.cs b/MoreLinq/ZipImpl.cs index 3bf674acf..1f008a1dc 100644 --- a/MoreLinq/ZipImpl.cs +++ b/MoreLinq/ZipImpl.cs @@ -50,10 +50,10 @@ static IEnumerable ZipImpl( while (true) { var n = 0; - var (_, v1) = Read(ref e1, ++n); - var (_, v2) = Read(ref e2, ++n); - var (_, v3) = Read(ref e3, ++n); - var (_, v4) = Read(ref e4, ++n); + var v1 = Read(ref e1, ++n); + var v2 = Read(ref e2, ++n); + var v3 = Read(ref e3, ++n); + var v4 = Read(ref e4, ++n); if (terminations <= limit) yield return resultSelector(v1, v2, v3, v4); @@ -69,28 +69,28 @@ static IEnumerable ZipImpl( e4?.Dispose(); } - (bool, T) Read(ref IEnumerator? e, int n) + T Read(ref IEnumerator? e, int n) { if (e == null || terminations > limit) - return default; + return default!; - (bool, T) result; + T value; if (e.MoveNext()) { - result = (true, e.Current); + value = e.Current; } else { e.Dispose(); e = null; terminations++; - result = default; + value = default!; } if (errorSelector != null && terminations > 0 && terminations < n) throw errorSelector(e1, e2, e3, e4); - return result; + return value; } } } From 7644df2e857093f8bda9348fffecfa9079107e68 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 17:53:28 +0200 Subject: [PATCH 50/53] Revert some accidental changes in 3d602b9 --- MoreLinq/PendNode.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index 34dcdbb7b..9da48e859 100644 --- a/MoreLinq/PendNode.cs +++ b/MoreLinq/PendNode.cs @@ -62,11 +62,11 @@ sealed class Source : PendNode public IEnumerator GetEnumerator() { var i = 0; - T[]? concats = null; // Array for > 4 concatenations - (bool, T Value) concat1 = default; // Slots for up to 4 concatenations - (bool, T Value) concat2 = default; - (bool, T Value) concat3 = default; - (bool, T Value) concat4 = default; + T[]? concats = null; // Array for > 4 concatenations + var concat1 = default(T); // Slots for up to 4 concatenations + var concat2 = default(T); + var concat3 = default(T); + var concat4 = default(T); var current = this; for (; current is Item item; current = item.Next) @@ -87,10 +87,10 @@ public IEnumerator GetEnumerator() { switch (i++) { - case 0: concat1 = (true, item.Value); break; - case 1: concat2 = (true, item.Value); break; - case 2: concat3 = (true, item.Value); break; - case 3: concat4 = (true, item.Value); break; + case 0: concat1 = item.Value; break; + case 1: concat2 = item.Value; break; + case 2: concat3 = item.Value; break; + case 3: concat4 = item.Value; break; default: throw new IndexOutOfRangeException(); } continue; @@ -108,10 +108,10 @@ public IEnumerator GetEnumerator() if (concats == null) { - if (i == 4) { yield return concat4.Value; i--; } - if (i == 3) { yield return concat3.Value; i--; } - if (i == 2) { yield return concat2.Value; i--; } - if (i == 1) { yield return concat1.Value; i--; } + if (i == 4) { yield return concat4!; i--; } + if (i == 3) { yield return concat3!; i--; } + if (i == 2) { yield return concat2!; i--; } + if (i == 1) { yield return concat1!; i--; } yield break; } From baade1170432b25f9b8f6845bad7c60a6f5a6e24 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 13 Aug 2020 20:02:05 +0200 Subject: [PATCH 51/53] Note about disallowing ToDataTable expressions to be null --- MoreLinq/ToDataTable.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index 65b5afc11..fb69640e4 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -98,6 +98,10 @@ public static TTable ToDataTable(this IEnumerable source, TTable t if (source == null) throw new ArgumentNullException(nameof(source)); if (table == null) throw new ArgumentNullException(nameof(table)); + // TODO disallow null for "expressions" in next major update + + expressions ??= EmptyArray>>.Value; + var members = PrepareMemberInfos(expressions).ToArray(); members = BuildOrBindSchema(table, members); var shredder = CreateShredder(members); @@ -132,7 +136,7 @@ static IEnumerable PrepareMemberInfos(ICollection Date: Fri, 14 Aug 2020 09:02:43 +0200 Subject: [PATCH 52/53] First/Single/LastOrDefault may return nullable --- MoreLinq/Extensions.ToDataTable.g.cs | 1 + MoreLinq/Extensions.g.cs | 4 ++++ MoreLinq/MaxBy.cs | 4 ++++ bld/ExtensionsGenerator/Program.cs | 1 + 4 files changed, 10 insertions(+) diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index fc9ace256..66c6f8931 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -32,6 +32,7 @@ namespace MoreLinq.Extensions using System; using System.CodeDom.Compiler; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Data; using System.Linq.Expressions; diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 3a8650b3c..2bab62b83 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -32,6 +32,7 @@ namespace MoreLinq.Extensions using System; using System.CodeDom.Compiler; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Collections; @@ -1904,6 +1905,7 @@ public static partial class FirstOrDefaultExtension /// otherwise, the first element in source. /// + [return: MaybeNull] public static T FirstOrDefault(this IExtremaEnumerable source) => MoreEnumerable.FirstOrDefault(source); @@ -3086,6 +3088,7 @@ public static partial class LastOrDefaultExtension /// otherwise, the last element in source. /// + [return: MaybeNull] public static T LastOrDefault(this IExtremaEnumerable source) => MoreEnumerable.LastOrDefault(source); @@ -5149,6 +5152,7 @@ public static partial class SingleOrDefaultExtension /// if the sequence contains no elements. /// + [return: MaybeNull] public static T SingleOrDefault(this IExtremaEnumerable source) => MoreEnumerable.SingleOrDefault(source); diff --git a/MoreLinq/MaxBy.cs b/MoreLinq/MaxBy.cs index 050240334..074ffbb96 100644 --- a/MoreLinq/MaxBy.cs +++ b/MoreLinq/MaxBy.cs @@ -20,6 +20,7 @@ namespace MoreLinq using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; /// @@ -87,6 +88,7 @@ public static T First(this IExtremaEnumerable source) /// otherwise, the first element in source. /// + [return: MaybeNull] public static T FirstOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -123,6 +125,7 @@ public static T Last(this IExtremaEnumerable source) /// otherwise, the last element in source. /// + [return: MaybeNull] public static T LastOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -161,6 +164,7 @@ public static T Single(this IExtremaEnumerable source) /// if the sequence contains no elements. /// + [return: MaybeNull] public static T SingleOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 5ade4e451..d047e7a4a 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -234,6 +234,7 @@ into e "System", "System.CodeDom.Compiler", "System.Collections.Generic", + "System.Diagnostics.CodeAnalysis", }; var imports = From 861585cbb9de86ab4136080698e34a25a186b941 Mon Sep 17 00:00:00 2001 From: Atif Aziz Date: Thu, 20 Aug 2020 17:42:17 +0200 Subject: [PATCH 53/53] Make Dictionary more honest (convert to class) --- MoreLinq/Collections/Dictionary.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/MoreLinq/Collections/Dictionary.cs b/MoreLinq/Collections/Dictionary.cs index 4241ae650..fad4e1a03 100644 --- a/MoreLinq/Collections/Dictionary.cs +++ b/MoreLinq/Collections/Dictionary.cs @@ -29,12 +29,12 @@ namespace MoreLinq.Collections // Add members if and when needed to keep coverage. - struct Dictionary + sealed class Dictionary { readonly System.Collections.Generic.Dictionary _dict; (bool, TValue) _null; - public Dictionary(IEqualityComparer comparer) : this() + public Dictionary(IEqualityComparer comparer) { _dict = new System.Collections.Generic.Dictionary(comparer); _null = default; @@ -44,8 +44,6 @@ public TValue this[TKey key] { set { - DefaultGuard(); - if (key is null) _null = (true, value); else @@ -55,8 +53,6 @@ public TValue this[TKey key] public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - DefaultGuard(); - if (key is null) { switch (_null) @@ -72,11 +68,5 @@ public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) return _dict.TryGetValue(key, out value); } - - void DefaultGuard() - { - if (_dict is null) - throw new InvalidOperationException(); - } } }