diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index dab16c723..1434359fd 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,16 @@ "isRoot": true, "tools": { "dotnet-t4": { - "version": "2.0.5", + "version": "2.3.0", "commands": [ "t4" ] + }, + "dotnet-reportgenerator-globaltool": { + "version": "5.1.11", + "commands": [ + "reportgenerator" + ] } } } \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 43ffc671d..46779ac64 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,59 @@ +# http://editorconfig.org/ + root = true [*] +indent_style = space insert_final_newline = true trim_trailing_whitespace = true -[*.xml] -indent_style = space +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +[*.Build.{props,targets}] +indent_size = 2 + +[*.{sln}] +indent_style = tab + +[*.{json,yml}] +indent_size = 2 [*.{cs,tt}] charset = utf-8 indent_style = space indent_size = 4 +max_line_length = 100 + +[*.cs] +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion + +# Spacing +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false + +# Wrapping +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true diff --git a/.gitignore b/.gitignore index cc7e1a503..68976a434 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +*.opencover.xml +**/TestResults/ + ### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7bafdbf6b..000000000 --- a/.travis.yml +++ /dev/null @@ -1,51 +0,0 @@ -language: csharp -os: - - linux - - osx -osx_image: xcode9.4 -solution: MoreLinq.sln -mono: 5.0.1 -dist: xenial -sudo: required -dotnet: 3.0.100 -env: - - CONFIGURATION=Debug - - CONFIGURATION=Release -addons: - apt: - sources: - - 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-runtime-2.1 - -before_install: - - | - if [ "$TRAVIS_OS_NAME" == "osx" ] || [ `uname` == "Darwin" ]; then - # Handle too many files on OS X - ulimit -n 4096 - # Install dotnet core 2.1 runtime - wget --retry-connrefused --waitretry=1 -O /tmp/dn21.pkg 'https://download.visualstudio.microsoft.com/download/pr/9314da31-774c-4d2b-8743-998f2a21f5ab/bc918ca05ab6b650f2991b205c04f623/dotnet-runtime-2.1.13-osx-x64.pkg' - sudo installer -pkg /tmp/dn21.pkg -target / - fi - - dotnet --info - -install: - - npm install -g eclint - -before_script: - - git rm .editorconfig - - 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 - -script: - - | - if grep --extended-regexp '^[[:space:]]*using[[:space:]]+System\.Linq;' $(ls MoreLinq.Test/*Test.cs); then - echo "System.Linq import found, failing the build!" >&2 - exit 1 - fi - - ./build.sh $CONFIGURATION - - dotnet exec MoreLinq.Test/bin/$CONFIGURATION/netcoreapp2.1/MoreLinq.Test.dll - - dotnet exec MoreLinq.Test/bin/$CONFIGURATION/netcoreapp3.0/MoreLinq.Test.dll - - mono MoreLinq.Test/bin/$CONFIGURATION/net451/MoreLinq.Test.exe diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..0b85fe477 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + 11 + enable + true + 7.0-all + true + + diff --git a/MoreLinq.Test/.editorconfig b/MoreLinq.Test/.editorconfig new file mode 100644 index 000000000..cc328a0d1 --- /dev/null +++ b/MoreLinq.Test/.editorconfig @@ -0,0 +1,28 @@ +[*.cs] + +# CA1034: Nested types should not be visible +dotnet_diagnostic.CA1034.severity = none + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = none + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = suggestion + +# CA1032: Implement standard exception constructors +dotnet_diagnostic.CA1032.severity = none + +# CA1064: Exceptions should be public +dotnet_diagnostic.CA1064.severity = none + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = none + +# CA5394: Do not use insecure randomness +dotnet_diagnostic.CA5394.severity = none + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = none + +# CA1308: Normalize strings to uppercase +dotnet_diagnostic.CA1308.severity = none diff --git a/MoreLinq.Test/AcquireTest.cs b/MoreLinq.Test/AcquireTest.cs index 119d4c3e4..18050ed78 100644 --- a/MoreLinq.Test/AcquireTest.cs +++ b/MoreLinq.Test/AcquireTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2012 Atif Aziz. 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. @@ -26,9 +26,9 @@ public class AcquireTest [Test] public void AcquireAll() { - Disposable a = null; - Disposable b = null; - Disposable c = null; + Disposable? a = null; + Disposable? b = null; + Disposable? c = null; var allocators = MoreEnumerable.From(() => a = new Disposable(), () => b = new Disposable(), @@ -48,16 +48,16 @@ public void AcquireAll() [Test] public void AcquireSome() { - Disposable a = null; - Disposable b = null; - Disposable c = null; + Disposable? a = null; + Disposable? b = null; + Disposable? c = null; var allocators = MoreEnumerable.From(() => a = new Disposable(), () => b = new Disposable(), () => throw new TestException(), () => c = new Disposable()); - Assert.Throws(() => allocators.Acquire()); + Assert.That(allocators.Acquire, Throws.TypeOf()); Assert.That(a, Is.Not.Null); Assert.That(a.Disposed, Is.True); @@ -66,7 +66,7 @@ public void AcquireSome() Assert.That(c, Is.Null); } - class Disposable : IDisposable + sealed class Disposable : IDisposable { public bool Disposed { get; private set; } public void Dispose() { Disposed = true; } diff --git a/MoreLinq.Test/AggregateRightTest.cs b/MoreLinq.Test/AggregateRightTest.cs index 0d3aa62af..7ecd4825e 100644 --- a/MoreLinq.Test/AggregateRightTest.cs +++ b/MoreLinq.Test/AggregateRightTest.cs @@ -17,7 +17,6 @@ namespace MoreLinq.Test { - using System; using NUnit.Framework; [TestFixture] @@ -28,8 +27,8 @@ public class AggregateRightTest [Test] public void AggregateRightWithEmptySequence() { - Assert.Throws( - () => new int[0].AggregateRight((a, b) => a + b)); + Assert.That(() => new int[0].AggregateRight((a, b) => a + b), + Throws.InvalidOperationException); } [Test] @@ -47,9 +46,9 @@ public void AggregateRightFuncIsNotInvokedOnSingleElementSequence() [TestCase(SourceKind.Sequence)] public void AggregateRight(SourceKind sourceKind) { - var enumerable = Enumerable.Range(1, 5).Select(x => x.ToString()).ToSourceKind(sourceKind); + var enumerable = Enumerable.Range(1, 5).Select(x => x.ToInvariantString()).ToSourceKind(sourceKind); - var result = enumerable.AggregateRight((a, b) => string.Format("({0}+{1})", a, b)); + var result = enumerable.AggregateRight((a, b) => $"({a}+{b})"); Assert.That(result, Is.EqualTo("(1+(2+(3+(4+5))))")); } @@ -61,7 +60,7 @@ public void AggregateRight(SourceKind sourceKind) [TestCase(true)] public void AggregateRightSeedWithEmptySequence(object defaultValue) { - Assert.That(new int[0].AggregateRight(defaultValue, (a, b) => b), Is.EqualTo(defaultValue)); + Assert.That(new int[0].AggregateRight(defaultValue, (_, b) => b), Is.EqualTo(defaultValue)); } [Test] @@ -78,7 +77,7 @@ public void AggregateRightSeedFuncIsNotInvokedOnEmptySequence() public void AggregateRightSeed() { var result = Enumerable.Range(1, 4) - .AggregateRight("5", (a, b) => string.Format("({0}+{1})", a, b)); + .AggregateRight("5", (a, b) => $"({a}+{b})"); Assert.That(result, Is.EqualTo("(1+(2+(3+(4+5))))")); } @@ -90,14 +89,14 @@ public void AggregateRightSeed() [TestCase(true)] public void AggregateRightResultorWithEmptySequence(object defaultValue) { - Assert.That(new int[0].AggregateRight(defaultValue, (a, b) => b, a => a == defaultValue), Is.EqualTo(true)); + Assert.That(new int[0].AggregateRight(defaultValue, (_, b) => b, a => a == defaultValue), Is.EqualTo(true)); } [Test] public void AggregateRightResultor() { var result = Enumerable.Range(1, 4) - .AggregateRight("5", (a, b) => string.Format("({0}+{1})", a, b), a => a.Length); + .AggregateRight("5", (a, b) => $"({a}+{b})", a => a.Length); Assert.That(result, Is.EqualTo("(1+(2+(3+(4+5))))".Length)); } diff --git a/MoreLinq.Test/AggregateTest.cs b/MoreLinq.Test/AggregateTest.cs index 55b531658..d9cf0e6fb 100644 --- a/MoreLinq.Test/AggregateTest.cs +++ b/MoreLinq.Test/AggregateTest.cs @@ -26,6 +26,7 @@ namespace MoreLinq.Test using System.Reactive.Linq; using System.Reflection; using NUnit.Framework.Interfaces; + using static FuncModule; [TestFixture] public class AggregateTest @@ -51,8 +52,8 @@ from m in typeof(MoreEnumerable).GetMethods(BindingFlags.Public | BindingFlags.S Source = source, Expectation = sum, Instantiation = m.MakeGenericMethod(Enumerable.Repeat(typeof(int), m.GetGenericArguments().Length - 1) - .Append(typeof(int[])) // TResult - .ToArray()), + .Append(typeof(int[])) // TResult + .ToArray()), } into m let rst = m.Instantiation.GetParameters().Last().ParameterType @@ -64,10 +65,11 @@ into m AccumulatorCount = (m.Instantiation.GetParameters().Length - 2 /* source + resultSelector */) / 2 /* seed + accumulator */, ResultSelectorType = rst, Parameters = - rst.GetMethod("Invoke") - .GetParameters() - .Select(p => Expression.Parameter(p.ParameterType)) - .ToArray(), + rst.GetMethod("Invoke") is { } invoke + ? invoke.GetParameters() + .Select(p => Expression.Parameter(p.ParameterType)) + .ToArray() + : throw new MissingMethodException("""Method "Invoke" not found."""), } into m let resultSelector = @@ -75,7 +77,7 @@ into m Expression.NewArrayInit(typeof(int), m.Parameters), m.Parameters) .Compile() - let accumulator = new Func((s, n) => s + n) + let accumulator = Func((int s, int n) => s + n) select new { Name = $"{name}({m.AccumulatorCount})", @@ -95,7 +97,7 @@ into t select new TestCaseData(t.Method, t.Args).SetName(t.Name).Returns(t.Expectation); [TestCaseSource(nameof(AccumulatorsTestSource), new object[] { nameof(Accumulators), 10 })] - public object Accumulators(MethodInfo method, object[] args) => + public object? Accumulators(MethodInfo method, object[] args) => method.Invoke(null, args); [Test] @@ -110,8 +112,8 @@ public void SevenUniqueAccumulators() 0, (s, e) => s + e.Num, 0, (s, e) => e.Num % 2 == 0 ? s + e.Num : s, 0, (s, _) => s + 1, - (int?)null, (s, e) => s is int n ? Math.Min(n, e.Num) : e.Num, - (int?)null, (s, e) => s is int n ? Math.Max(n, e.Num) : e.Num, + (int?)null, (s, e) => s is {} n ? Math.Min(n, e.Num) : e.Num, + (int?)null, (s, e) => s is {} n ? Math.Max(n, e.Num) : e.Num, new HashSet(), (s, e) => { s.Add(e.Str.Length); return s; }, new List<(int Num, string Str)>(), (s, e) => { s.Add((e.Num, e.Str)); return s; }, (sum, esum, count, min, max, lengths, items) => new @@ -120,8 +122,8 @@ public void SevenUniqueAccumulators() EvenSum = esum, Count = count, Average = (double)sum / count, - Min = min is int mn ? mn : throw new InvalidOperationException(), - Max = max is int mx ? mx : throw new InvalidOperationException(), + Min = min ?? throw new InvalidOperationException(), + Max = max ?? throw new InvalidOperationException(), UniqueLengths = lengths, Items = items, } diff --git a/MoreLinq.Test/AppendTest.cs b/MoreLinq.Test/AppendTest.cs index df46b13c7..580a74875 100644 --- a/MoreLinq.Test/AppendTest.cs +++ b/MoreLinq.Test/AppendTest.cs @@ -46,7 +46,7 @@ public void AppendWithEmptyHeadSequence() public void AppendWithNullTail() { var head = new[] { "first", "second" }; - string tail = null; + string? tail = null; var whole = head.Append(tail); whole.AssertSequenceEqual("first", "second", null); } diff --git a/MoreLinq.Test/AssertCountTest.cs b/MoreLinq.Test/AssertCountTest.cs index 2a05912f8..2a6e5ddf3 100644 --- a/MoreLinq.Test/AssertCountTest.cs +++ b/MoreLinq.Test/AssertCountTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2009 Atif Aziz. 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. @@ -27,10 +27,10 @@ public class AssertCountTest public void AssertCountNegativeCount() { var source = new object[0]; - AssertThrowsArgument.OutOfRangeException("count", () => - source.AssertCount(-1)); - AssertThrowsArgument.OutOfRangeException("count", () => - source.AssertCount(-1, BreakingFunc.Of())); + Assert.That(() => source.AssertCount(-1), + Throws.ArgumentOutOfRangeException("count")); + Assert.That(() => source.AssertCount(-1, BreakingFunc.Of()), + Throws.ArgumentOutOfRangeException("count")); } [Test] @@ -42,46 +42,51 @@ public void AssertCountSequenceWithMatchingLength() [Test] public void AssertCountShortSequence() { - Assert.Throws(() => - "foo,bar,baz".GenerateSplits(',').AssertCount(4).Consume()); + Assert.That(() => "foo,bar,baz".GenerateSplits(',').AssertCount(4).Consume(), + Throws.TypeOf()); } [Test] public void AssertCountLongSequence() { - Assert.Throws(() => - "foo,bar,baz".GenerateSplits(',').AssertCount(2).Consume()); + Assert.That(() => "foo,bar,baz".GenerateSplits(',').AssertCount(2).Consume(), + Throws.TypeOf()); } - [Test] - public void AssertCountDefaultExceptionMessageVariesWithCase() + [TestCase("", 1, "Sequence contains too few elements when exactly 1 was expected.")] + [TestCase("foo,bar,baz", 1, "Sequence contains too many elements when exactly 1 was expected.")] + [TestCase("foo,bar,baz", 4, "Sequence contains too few elements when exactly 4 were expected.")] + [TestCase("foo,bar,baz", 2, "Sequence contains too many elements when exactly 2 were expected.")] + public void AssertCountDefaultExceptionMessageVariesWithCase(string str, int count, string expectedMessage) { - var tokens = "foo,bar,baz".GenerateSplits(','); - var e1 = Assert.Throws(() => tokens.AssertCount(4).Consume()); - var e2 = Assert.Throws(() => tokens.AssertCount(2).Consume()); - Assert.That(e1.Message, Is.Not.EqualTo(e2.Message)); + var tokens = str.GenerateSplits(',') + .Where(t => t.Length > 0) + .AssertCount(count); + + Assert.That(() => tokens.Consume(), + Throws.TypeOf().With.Message.EqualTo(expectedMessage)); } [Test] public void AssertCountLongSequenceWithErrorSelector() { - var e = - Assert.Throws(() => - "foo,bar,baz".GenerateSplits(',').AssertCount(2, (cmp, count) => new TestException(cmp, count)) - .Consume()); - Assert.That(e.Cmp, Is.GreaterThan(0)); - Assert.That(e.Count, Is.EqualTo(2)); + Assert.That(() => + "foo,bar,baz".GenerateSplits(',').AssertCount(2, (cmp, count) => new TestException(cmp, count)) + .Consume(), + Throws.TypeOf() + .With.Property(nameof(TestException.Cmp)).GreaterThan(0) + .And.Count.EqualTo(2)); } [Test] public void AssertCountShortSequenceWithErrorSelector() { - var e = - Assert.Throws(() => - "foo,bar,baz".GenerateSplits(',').AssertCount(4, (cmp, count) => new TestException(cmp, count)) - .Consume()); - Assert.That(e.Cmp, Is.LessThan(0)); - Assert.That(e.Count, Is.EqualTo(4)); + Assert.That(() => + "foo,bar,baz".GenerateSplits(',').AssertCount(4, (cmp, count) => new TestException(cmp, count)) + .Consume(), + Throws.TypeOf() + .With.Property(nameof(TestException.Cmp)).LessThan(0) + .And.Count.EqualTo(4)); } sealed class TestException : Exception @@ -105,14 +110,14 @@ public void AssertCountIsLazy() [Test] public void AssertCountWithCollectionIsLazy() { - new BreakingCollection(5).AssertCount(0); + new BreakingCollection(new int[5]).AssertCount(0); } [Test] public void AssertCountWithMatchingCollectionCount() { var xs = new[] { 123, 456, 789 }; - Assert.AreSame(xs, xs.AssertCount(3)); + Assert.That(xs, Is.SameAs(xs.AssertCount(3))); } [TestCase(3, 2, "Sequence contains too many elements when exactly 2 were expected.")] @@ -120,9 +125,9 @@ public void AssertCountWithMatchingCollectionCount() public void AssertCountWithMismatchingCollectionCount(int sourceCount, int count, string message) { var xs = new int[sourceCount]; - var enumerator = xs.AssertCount(count).GetEnumerator(); - var e = Assert.Throws(() => enumerator.MoveNext()); - Assert.AreEqual(e.Message, message); + using var enumerator = xs.AssertCount(count).GetEnumerator(); + Assert.That(enumerator.MoveNext, + Throws.TypeOf().With.Message.EqualTo(message)); } [Test] diff --git a/MoreLinq.Test/AssertTest.cs b/MoreLinq.Test/AssertTest.cs index b43d53a92..553701ff2 100644 --- a/MoreLinq.Test/AssertTest.cs +++ b/MoreLinq.Test/AssertTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2013 Atif Aziz. 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. @@ -40,27 +40,30 @@ public void AssertSequenceWithValidAllElements() [Test] public void AssertSequenceWithValidSomeInvalidElements() { - Assert.Throws(() => - new[] { 2, 4, 6, 7, 8, 9 }.Assert(n => n % 2 == 0).Consume()); + var source = new[] { 2, 4, 6, 7, 8, 9 }; + Assert.That(() => source.Assert(n => n % 2 == 0).Consume(), + Throws.InvalidOperationException); } [Test] public void AssertSequenceWithInvalidElementsAndCustomErrorReturningNull() { - Assert.Throws(() => - new[] { 2, 4, 6, 7, 8, 9 }.Assert(n => n % 2 == 0, _ => null).Consume()); + var source = new[] { 2, 4, 6, 7, 8, 9 }; + Assert.That(() => source.Assert(n => n % 2 == 0, _ => null!).Consume(), + Throws.InvalidOperationException); } [Test] public void AssertSequenceWithInvalidElementsAndCustomError() { - var e = - Assert.Throws(() => - new[] { 2, 4, 6, 7, 8, 9 }.Assert(n => n % 2 == 0, n => new ValueException(n)).Consume()); - Assert.AreEqual(7, e.Value); + var source = new[] { 2, 4, 6, 7, 8, 9 }; + Assert.That(() => + source.Assert(n => n % 2 == 0, n => new ValueException(n)).Consume(), + Throws.TypeOf() + .With.Property(nameof(ValueException.Value)).EqualTo(7)); } - class ValueException : Exception + sealed class ValueException : Exception { public object Value { get; } public ValueException(object value) { Value = value; } diff --git a/MoreLinq.Test/AssertThrowsArgument.cs b/MoreLinq.Test/AssertThrowsArgument.cs deleted file mode 100644 index 01aa281a1..000000000 --- a/MoreLinq.Test/AssertThrowsArgument.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace MoreLinq.Test -{ - using System; - using NUnit.Framework; - - sealed class AssertThrowsArgument - { - [Obsolete("This is redundant with the NullArgumentTest fixture.")] - public static void NullException(string expectedParamName, TestDelegate code) - { - Exception(expectedParamName, code); - } - - public static void Exception(string expectedParamName, TestDelegate code) - { - Exception(expectedParamName, code); - } - - public static void OutOfRangeException(string expectedParamName, TestDelegate code) - { - Exception(expectedParamName, code); - } - - static void Exception(string expectedParamName, TestDelegate code) where TActual : ArgumentException - { - var e = Assert.Throws(code); - - Assert.That(e.ParamName, Is.EqualTo(expectedParamName)); - } - } -} diff --git a/MoreLinq.Test/Async/AsyncEnumerable.cs b/MoreLinq.Test/Async/AsyncEnumerable.cs new file mode 100644 index 000000000..9e34f492a --- /dev/null +++ b/MoreLinq.Test/Async/AsyncEnumerable.cs @@ -0,0 +1,559 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2021 Atif Aziz. 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.Test.Async +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Threading.Tasks; + using LinqEnumerable = System.Linq.AsyncEnumerable; + + [DebuggerStepThrough] + static partial class AsyncEnumerable + { + public static ValueTask AggregateAsync(this IAsyncEnumerable source, Func func) => + LinqEnumerable.AggregateAsync(source, func); + + public static ValueTask AggregateAsync(this IAsyncEnumerable source, TAccumulate seed, Func func) => + LinqEnumerable.AggregateAsync(source, seed, func); + + public static ValueTask AggregateAsync(this IAsyncEnumerable source, TAccumulate seed, Func func, Func resultSelector) => + LinqEnumerable.AggregateAsync(source, seed, func, resultSelector); + + public static ValueTask AllAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.AllAsync(source, predicate); + + public static ValueTask AnyAsync(this IAsyncEnumerable source) => + LinqEnumerable.AnyAsync(source); + + public static ValueTask AnyAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.AnyAsync(source, predicate); + + public static IAsyncEnumerable AsEnumerable(this IAsyncEnumerable source) => + LinqEnumerable.AsAsyncEnumerable(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.AverageAsync(source, selector); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static ValueTask AverageAsync(this IAsyncEnumerable source) => + LinqEnumerable.AverageAsync(source); + + public static IAsyncEnumerable Cast(this IAsyncEnumerable source) => + LinqEnumerable.Cast(source); + + public static IAsyncEnumerable Concat(this IAsyncEnumerable first, IAsyncEnumerable second) => + LinqEnumerable.Concat(first, second); + + public static ValueTask ContainsAsync(this IAsyncEnumerable source, TSource value) => + LinqEnumerable.ContainsAsync(source, value); + + public static ValueTask ContainsAsync(this IAsyncEnumerable source, TSource value, IEqualityComparer comparer) => + LinqEnumerable.ContainsAsync(source, value, comparer); + + public static ValueTask CountAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.CountAsync(source, predicate); + + public static ValueTask CountAsync(this IAsyncEnumerable source) => + LinqEnumerable.CountAsync(source); + + public static IAsyncEnumerable DefaultIfEmpty(this IAsyncEnumerable source) => + LinqEnumerable.DefaultIfEmpty(source); + + public static IAsyncEnumerable DefaultIfEmpty(this IAsyncEnumerable source, TSource defaultValue) => + LinqEnumerable.DefaultIfEmpty(source, defaultValue); + + public static IAsyncEnumerable Distinct(this IAsyncEnumerable source) => + LinqEnumerable.Distinct(source); + + public static IAsyncEnumerable Distinct(this IAsyncEnumerable source, IEqualityComparer comparer) => + LinqEnumerable.Distinct(source, comparer); + + public static ValueTask ElementAtAsync(this IAsyncEnumerable source, int index) => + LinqEnumerable.ElementAtAsync(source, index); + + public static ValueTask ElementAtOrDefaultAsync(this IAsyncEnumerable source, int index) => + LinqEnumerable.ElementAtOrDefaultAsync(source, index); + + public static IAsyncEnumerable Empty() => + LinqEnumerable.Empty(); + + public static IAsyncEnumerable Except(this IAsyncEnumerable first, IAsyncEnumerable second) => + LinqEnumerable.Except(first, second); + + public static IAsyncEnumerable Except(this IAsyncEnumerable first, IAsyncEnumerable second, IEqualityComparer comparer) => + LinqEnumerable.Except(first, second, comparer); + + public static ValueTask FirstAsync(this IAsyncEnumerable source) => + LinqEnumerable.FirstAsync(source); + + public static ValueTask FirstAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.FirstAsync(source, predicate); + + public static ValueTask FirstOrDefaultAsync(this IAsyncEnumerable source) => + LinqEnumerable.FirstOrDefaultAsync(source); + + public static ValueTask FirstOrDefaultAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.FirstOrDefaultAsync(source, predicate); + + public static IAsyncEnumerable GroupBy(this IAsyncEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer comparer) => + LinqEnumerable.GroupBy(source, keySelector, resultSelector, comparer); + + public static IAsyncEnumerable GroupBy(this IAsyncEnumerable source, Func keySelector, Func, TResult> resultSelector) => + LinqEnumerable.GroupBy(source, keySelector, resultSelector); + + public static IAsyncEnumerable> GroupBy(this IAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) => + LinqEnumerable.GroupBy(source, keySelector, elementSelector, comparer); + + public static IAsyncEnumerable GroupBy(this IAsyncEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector) => + LinqEnumerable.GroupBy(source, keySelector, elementSelector, resultSelector); + + public static IAsyncEnumerable> GroupBy(this IAsyncEnumerable source, Func keySelector, IEqualityComparer comparer) => + LinqEnumerable.GroupBy(source, keySelector, comparer); + + public static IAsyncEnumerable> GroupBy(this IAsyncEnumerable source, Func keySelector) => + LinqEnumerable.GroupBy(source, keySelector); + + public static IAsyncEnumerable> GroupBy(this IAsyncEnumerable source, Func keySelector, Func elementSelector) => + LinqEnumerable.GroupBy(source, keySelector, elementSelector); + + public static IAsyncEnumerable GroupBy(this IAsyncEnumerable source, Func keySelector, Func elementSelector, Func, TResult> resultSelector, IEqualityComparer comparer) => + LinqEnumerable.GroupBy(source, keySelector, elementSelector, resultSelector, comparer); + + public static IAsyncEnumerable GroupJoin(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector) => + LinqEnumerable.GroupJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector); + + public static IAsyncEnumerable GroupJoin(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func, TResult> resultSelector, IEqualityComparer comparer) => + LinqEnumerable.GroupJoin(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + + public static IAsyncEnumerable Intersect(this IAsyncEnumerable first, IAsyncEnumerable second) => + LinqEnumerable.Intersect(first, second); + + public static IAsyncEnumerable Intersect(this IAsyncEnumerable first, IAsyncEnumerable second, IEqualityComparer comparer) => + LinqEnumerable.Intersect(first, second, comparer); + + public static IAsyncEnumerable Join(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector) => + LinqEnumerable.Join(outer, inner, outerKeySelector, innerKeySelector, resultSelector); + + public static IAsyncEnumerable Join(this IAsyncEnumerable outer, IAsyncEnumerable inner, Func outerKeySelector, Func innerKeySelector, Func resultSelector, IEqualityComparer comparer) => + LinqEnumerable.Join(outer, inner, outerKeySelector, innerKeySelector, resultSelector, comparer); + + public static ValueTask LastAsync(this IAsyncEnumerable source) => + LinqEnumerable.LastAsync(source); + + public static ValueTask LastAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.LastAsync(source, predicate); + + public static ValueTask LastOrDefaultAsync(this IAsyncEnumerable source) => + LinqEnumerable.LastOrDefaultAsync(source); + + public static ValueTask LastOrDefaultAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.LastOrDefaultAsync(source, predicate); + + public static ValueTask LongCountAsync(this IAsyncEnumerable source) => + LinqEnumerable.LongCountAsync(source); + + public static ValueTask LongCountAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.LongCountAsync(source, predicate); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MaxAsync(source, selector); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MaxAsync(this IAsyncEnumerable source) => + LinqEnumerable.MaxAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.MinAsync(source, selector); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static ValueTask MinAsync(this IAsyncEnumerable source) => + LinqEnumerable.MinAsync(source); + + public static IAsyncEnumerable OfType(this IAsyncEnumerable source) => + LinqEnumerable.OfType(source); + + public static IOrderedAsyncEnumerable OrderBy(this IAsyncEnumerable source, Func keySelector, IComparer comparer) => + LinqEnumerable.OrderBy(source, keySelector, comparer); + + public static IOrderedAsyncEnumerable OrderBy(this IAsyncEnumerable source, Func keySelector) => + LinqEnumerable.OrderBy(source, keySelector); + + public static IOrderedAsyncEnumerable OrderByDescending(this IAsyncEnumerable source, Func keySelector) => + LinqEnumerable.OrderByDescending(source, keySelector); + + public static IOrderedAsyncEnumerable OrderByDescending(this IAsyncEnumerable source, Func keySelector, IComparer comparer) => + LinqEnumerable.OrderByDescending(source, keySelector, comparer); + + public static IAsyncEnumerable Range(int start, int count) => + LinqEnumerable.Range(start, count); + + public static IAsyncEnumerable Repeat(TResult element, int count) => + LinqEnumerable.Repeat(element, count); + + public static IAsyncEnumerable Reverse(this IAsyncEnumerable source) => + LinqEnumerable.Reverse(source); + + public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.Select(source, selector); + + public static IAsyncEnumerable Select(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.Select(source, selector); + + public static IAsyncEnumerable SelectMany(this IAsyncEnumerable source, Func> collectionSelector, Func resultSelector) => + LinqEnumerable.SelectMany(source, collectionSelector, resultSelector); + + public static IAsyncEnumerable SelectMany(this IAsyncEnumerable source, Func> selector) => + LinqEnumerable.SelectMany(source, selector); + + public static IAsyncEnumerable SelectMany(this IAsyncEnumerable source, Func> selector) => + LinqEnumerable.SelectMany(source, selector); + + public static IAsyncEnumerable SelectMany(this IAsyncEnumerable source, Func> collectionSelector, Func resultSelector) => + LinqEnumerable.SelectMany(source, collectionSelector, resultSelector); + + public static ValueTask SequenceEqualAsync(this IAsyncEnumerable first, IAsyncEnumerable second, IEqualityComparer comparer) => + LinqEnumerable.SequenceEqualAsync(first, second, comparer); + + public static ValueTask SequenceEqualAsync(this IAsyncEnumerable first, IAsyncEnumerable second) => + LinqEnumerable.SequenceEqualAsync(first, second); + + public static ValueTask SingleAsync(this IAsyncEnumerable source) => + LinqEnumerable.SingleAsync(source); + + public static ValueTask SingleAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.SingleAsync(source, predicate); + + public static ValueTask SingleOrDefaultAsync(this IAsyncEnumerable source) => + LinqEnumerable.SingleOrDefaultAsync(source); + + public static ValueTask SingleOrDefaultAsync(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.SingleOrDefaultAsync(source, predicate); + + public static IAsyncEnumerable Skip(this IAsyncEnumerable source, int count) => + LinqEnumerable.Skip(source, count); + + public static IAsyncEnumerable SkipWhile(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.SkipWhile(source, predicate); + + public static IAsyncEnumerable SkipWhile(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.SkipWhile(source, predicate); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source, Func selector) => + LinqEnumerable.SumAsync(source, selector); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static ValueTask SumAsync(this IAsyncEnumerable source) => + LinqEnumerable.SumAsync(source); + + public static IAsyncEnumerable Take(this IAsyncEnumerable source, int count) => + LinqEnumerable.Take(source, count); + + public static IAsyncEnumerable TakeWhile(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.TakeWhile(source, predicate); + + public static IAsyncEnumerable TakeWhile(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.TakeWhile(source, predicate); + + public static IOrderedAsyncEnumerable ThenBy(this IOrderedAsyncEnumerable source, Func keySelector, IComparer comparer) => + LinqEnumerable.ThenBy(source, keySelector, comparer); + + public static IOrderedAsyncEnumerable ThenBy(this IOrderedAsyncEnumerable source, Func keySelector) => + LinqEnumerable.ThenBy(source, keySelector); + + public static IOrderedAsyncEnumerable ThenByDescending(this IOrderedAsyncEnumerable source, Func keySelector) => + LinqEnumerable.ThenByDescending(source, keySelector); + + public static IOrderedAsyncEnumerable ThenByDescending(this IOrderedAsyncEnumerable source, Func keySelector, IComparer comparer) => + LinqEnumerable.ThenByDescending(source, keySelector, comparer); + + public static ValueTask ToArrayAsync(this IAsyncEnumerable source) => + LinqEnumerable.ToArrayAsync(source); + + public static ValueTask> ToDictionaryAsync(this IAsyncEnumerable source, Func keySelector) + where TKey: notnull => + LinqEnumerable.ToDictionaryAsync(source, keySelector); + + public static ValueTask> ToDictionaryAsync(this IAsyncEnumerable source, Func keySelector, IEqualityComparer comparer) + where TKey: notnull => + LinqEnumerable.ToDictionaryAsync(source, keySelector, comparer); + + public static ValueTask> ToDictionaryAsync(this IAsyncEnumerable source, Func keySelector, Func elementSelector) + where TKey: notnull => + LinqEnumerable.ToDictionaryAsync(source, keySelector, elementSelector); + + public static ValueTask> ToDictionaryAsync(this IAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + where TKey: notnull => + LinqEnumerable.ToDictionaryAsync(source, keySelector, elementSelector, comparer); + + public static ValueTask> ToListAsync(this IAsyncEnumerable source) => + LinqEnumerable.ToListAsync(source); + + public static ValueTask> ToLookupAsync(this IAsyncEnumerable source, Func keySelector) => + LinqEnumerable.ToLookupAsync(source, keySelector); + + public static ValueTask> ToLookupAsync(this IAsyncEnumerable source, Func keySelector, IEqualityComparer comparer) => + LinqEnumerable.ToLookupAsync(source, keySelector, comparer); + + public static ValueTask> ToLookupAsync(this IAsyncEnumerable source, Func keySelector, Func elementSelector) => + LinqEnumerable.ToLookupAsync(source, keySelector, elementSelector); + + public static ValueTask> ToLookupAsync(this IAsyncEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) => + LinqEnumerable.ToLookupAsync(source, keySelector, elementSelector, comparer); + + public static IAsyncEnumerable Union(this IAsyncEnumerable first, IAsyncEnumerable second) => + LinqEnumerable.Union(first, second); + + public static IAsyncEnumerable Union(this IAsyncEnumerable first, IAsyncEnumerable second, IEqualityComparer comparer) => + LinqEnumerable.Union(first, second, comparer); + + public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.Where(source, predicate); + + public static IAsyncEnumerable Where(this IAsyncEnumerable source, Func predicate) => + LinqEnumerable.Where(source, predicate); + + public static IAsyncEnumerable Zip(this IAsyncEnumerable first, IAsyncEnumerable second, Func resultSelector) => + LinqEnumerable.Zip(first, second, resultSelector); + } +} diff --git a/MoreLinq.Test/AtLeastTest.cs b/MoreLinq.Test/AtLeastTest.cs index 3399d2b20..f4ad24f88 100644 --- a/MoreLinq.Test/AtLeastTest.cs +++ b/MoreLinq.Test/AtLeastTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2015 "sholland". 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. @@ -25,71 +25,71 @@ public class AtLeastTest [Test] public void AtLeastWithNegativeCount() { - AssertThrowsArgument.OutOfRangeException("count", () => - new[] { 1 }.AtLeast(-1)); + Assert.That(() => new[] { 1 }.AtLeast(-1), + Throws.ArgumentOutOfRangeException("count")); } [Test] public void AtLeastWithEmptySequenceHasAtLeastZeroElements() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(0)); + Assert.That(xs.AtLeast(0), Is.True); } [Test] public void AtLeastWithEmptySequenceHasAtLeastOneElement() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsFalse(xs.AtLeast(1)); + Assert.That(xs.AtLeast(1), Is.False); } [Test] public void AtLeastWithEmptySequenceHasAtLeastManyElements() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsFalse(xs.AtLeast(2)); + Assert.That(xs.AtLeast(2), Is.False); } [Test] public void AtLeastWithSingleElementHasAtLeastZeroElements() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(0)); + Assert.That(xs.AtLeast(0), Is.True); } [Test] public void AtLeastWithSingleElementHasAtLeastOneElement() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(1)); + Assert.That(xs.AtLeast(1), Is.True); } [Test] public void AtLeastWithSingleElementHasAtLeastManyElements() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsFalse(xs.AtLeast(2)); + Assert.That(xs.AtLeast(2), Is.False); } [Test] public void AtLeastWithManyElementsHasAtLeastZeroElements() { foreach (var xs in new[] { 1, 2, 3 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(0)); + Assert.That(xs.AtLeast(0), Is.True); } [Test] public void AtLeastWithManyElementsHasAtLeastOneElement() { foreach (var xs in new[] { 1, 2, 3 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(1)); + Assert.That(xs.AtLeast(1), Is.True); } [Test] public void AtLeastWithManyElementsHasAtLeastManyElements() { foreach (var xs in new[] { 1, 2, 3 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtLeast(2)); + Assert.That(xs.AtLeast(2), Is.True); } [Test] @@ -98,7 +98,7 @@ public void AtLeastDoesNotIterateUnnecessaryElements() var source = MoreEnumerable.From(() => 1, () => 2, () => throw new TestException()); - Assert.IsTrue(source.AtLeast(2)); + Assert.That(source.AtLeast(2), Is.True); } } } diff --git a/MoreLinq.Test/AtMostTest.cs b/MoreLinq.Test/AtMostTest.cs index f16b9e41c..14135b930 100644 --- a/MoreLinq.Test/AtMostTest.cs +++ b/MoreLinq.Test/AtMostTest.cs @@ -25,50 +25,50 @@ public class AtMostTest [Test] public void AtMostWithNegativeCount() { - AssertThrowsArgument.OutOfRangeException("count", - () => new[] { 1 }.AtMost(-1)); + Assert.That(() => new[] { 1 }.AtMost(-1), + Throws.ArgumentOutOfRangeException("count")); } [Test] public void AtMostWithEmptySequenceHasAtMostZeroElements() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtMost(0)); + Assert.That(xs.AtMost(0), Is.True); } [Test] public void AtMostWithEmptySequenceHasAtMostOneElement() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtMost(1)); + Assert.That(xs.AtMost(1), Is.True); } [Test] public void AtMostWithSingleElementHasAtMostZeroElements() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsFalse(xs.AtMost(0)); + Assert.That(xs.AtMost(0), Is.False); } [Test] public void AtMostWithSingleElementHasAtMostOneElement() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtMost(1)); + Assert.That(xs.AtMost(1), Is.True); } [Test] public void AtMostWithSingleElementHasAtMostManyElements() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.AtMost(2)); + Assert.That(xs.AtMost(2), Is.True); } [Test] public void AtMostWithManyElementsHasAtMostOneElements() { foreach (var xs in new[] { 1, 2, 3 }.ArrangeCollectionTestCases()) - Assert.IsFalse(xs.AtMost(1)); + Assert.That(xs.AtMost(1), Is.False); } [Test] @@ -78,7 +78,7 @@ public void AtMostDoesNotIterateUnnecessaryElements() () => 2, () => 3, () => throw new TestException()); - Assert.IsFalse(source.AtMost(2)); + Assert.That(source.AtMost(2), Is.False); } } } diff --git a/MoreLinq.Test/BacksertTest.cs b/MoreLinq.Test/BacksertTest.cs index a3c4a3dd6..a26e6ac8f 100644 --- a/MoreLinq.Test/BacksertTest.cs +++ b/MoreLinq.Test/BacksertTest.cs @@ -17,7 +17,6 @@ namespace MoreLinq.Test { - using System; using System.Collections.Generic; using NUnit.Framework; @@ -33,20 +32,20 @@ public void BacksertIsLazy() [Test] public void BacksertWithNegativeIndex() { - AssertThrowsArgument.OutOfRangeException("index", () => - Enumerable.Range(1, 10).Backsert(new[] { 97, 98, 99 }, -1)); + Assert.That(() => Enumerable.Range(1, 10).Backsert(new[] { 97, 98, 99 }, -1), + Throws.ArgumentOutOfRangeException("index")); } [TestCase(new[] { 1, 2, 3 }, 4, new[] { 9 })] public void BacksertWithIndexGreaterThanSourceLength(int[] seq1, int index, int[] seq2) { - using (var test1 = seq1.AsTestingSequence()) - using (var test2 = seq2.AsTestingSequence()) - { - var result = test1.Backsert(test2, index); + using var test1 = seq1.AsTestingSequence(); + using var test2 = seq2.AsTestingSequence(); - Assert.Throws(() => result.ElementAt(0)); - } + var result = test1.Backsert(test2, index); + + Assert.That(() => result.ElementAt(0), + Throws.ArgumentOutOfRangeException()); } [TestCase(new[] { 1, 2, 3 }, 0, new[] { 8, 9 }, ExpectedResult = new[] { 1, 2, 3, 8, 9 })] @@ -55,11 +54,10 @@ public void BacksertWithIndexGreaterThanSourceLength(int[] seq1, int index, int[ [TestCase(new[] { 1, 2, 3 }, 3, new[] { 8, 9 }, ExpectedResult = new[] { 8, 9, 1, 2, 3 })] public IEnumerable Backsert(int[] seq1, int index, int[] seq2) { - using (var test1 = seq1.AsTestingSequence()) - using (var test2 = seq2.AsTestingSequence()) - { - return test1.Backsert(test2, index).ToArray(); - } + using var test1 = seq1.AsTestingSequence(); + using var test2 = seq2.AsTestingSequence(); + + return test1.Backsert(test2, index).ToArray(); } } } diff --git a/MoreLinq.Test/BatchTest.cs b/MoreLinq.Test/BatchTest.cs index 09cdadbae..e2b46ac65 100644 --- a/MoreLinq.Test/BatchTest.cs +++ b/MoreLinq.Test/BatchTest.cs @@ -23,44 +23,36 @@ namespace MoreLinq.Test [TestFixture] public class BatchTest { - [Test] - public void BatchZeroSize() - { - AssertThrowsArgument.OutOfRangeException("size",() => - new object[0].Batch(0)); - } - - [Test] - public void BatchNegativeSize() + [TestCase(0)] + [TestCase(-1)] + public void BatchBadSize(int size) { - AssertThrowsArgument.OutOfRangeException("size",() => - new object[0].Batch(-1)); + Assert.That(() => new object[0].Batch(size), + Throws.ArgumentOutOfRangeException("size")); } [Test] public void BatchEvenlyDivisibleSequence() { var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(3); - using (var reader = result.Read()) - { - reader.Read().AssertSequenceEqual(1, 2, 3); - reader.Read().AssertSequenceEqual(4, 5, 6); - reader.Read().AssertSequenceEqual(7, 8, 9); - reader.ReadEnd(); - } + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3); + reader.Read().AssertSequenceEqual(4, 5, 6); + reader.Read().AssertSequenceEqual(7, 8, 9); + reader.ReadEnd(); } [Test] - public void BatchUnevenlyDivisbleSequence() + public void BatchUnevenlyDivisibleSequence() { var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(4); - using (var reader = result.Read()) - { - reader.Read().AssertSequenceEqual(1, 2, 3, 4); - reader.Read().AssertSequenceEqual(5, 6, 7, 8); - reader.Read().AssertSequenceEqual(9); - reader.ReadEnd(); - } + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3, 4); + reader.Read().AssertSequenceEqual(5, 6, 7, 8); + reader.Read().AssertSequenceEqual(9); + reader.ReadEnd(); } [Test] @@ -74,12 +66,27 @@ public void BatchSequenceTransformingResult() public void BatchSequenceYieldsListsOfBatches() { var result = new[] { 1, 2, 3 }.Batch(2); - using (var reader = result.Read()) - { - Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); - Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); - reader.ReadEnd(); - } + + using var reader = result.Read(); + Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); + Assert.That(reader.Read(), Is.InstanceOf(typeof(IList))); + reader.ReadEnd(); + } + + [Test] + public void BatchSequencesAreIndependentInstances() + { + var result = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Batch(4); + + using var reader = result.Read(); + var first = reader.Read(); + var second = reader.Read(); + var third = reader.Read(); + reader.ReadEnd(); + + first.AssertSequenceEqual(1, 2, 3, 4); + second.AssertSequenceEqual(5, 6, 7, 8); + third.AssertSequenceEqual(9); } [Test] @@ -106,10 +113,8 @@ public void BatchCollectionSmallerThanSize(SourceKind kind, int oversize) reader.ReadEnd(); } - [TestCase(0)] - [TestCase(1)] - [TestCase(2)] - public void BatchReadOnlyCollectionSmallerThanSize(int oversize) + [Test] + public void BatchReadOnlyCollectionSmallerThanSize() { var collection = ReadOnlyCollection.From(1, 2, 3, 4, 5); var result = collection.Batch(collection.Count * 2); @@ -117,5 +122,284 @@ public void BatchReadOnlyCollectionSmallerThanSize(int oversize) reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); reader.ReadEnd(); } + + [TestCase(SourceKind.Sequence)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] + [TestCase(SourceKind.BreakingReadOnlyCollection)] + [TestCase(SourceKind.BreakingCollection)] + public void BatchEmptySource(SourceKind kind) + { + var batches = Enumerable.Empty().ToSourceKind(kind).Batch(100); + Assert.That(batches, Is.Empty); + } } } + +#if NETCOREAPP3_1_OR_GREATER + +namespace MoreLinq.Test +{ + using System; + using System.Buffers; + using System.Collections.Generic; + using MoreLinq.Experimental; + using NUnit.Framework; + + [TestFixture] + public class BatchPoolTest + { + [TestCase(0)] + [TestCase(-1)] + public void BatchBadSize(int size) + { + Assert.That(() => new object[0].Batch(size, ArrayPool.Shared, + BreakingFunc.Of, IEnumerable>(), + BreakingFunc.Of, object>()), + Throws.ArgumentOutOfRangeException("size")); + } + + [Test] + public void BatchEvenlyDivisibleSequence() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(3, pool, Enumerable.ToArray); + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3); + reader.Read().AssertSequenceEqual(4, 5, 6); + reader.Read().AssertSequenceEqual(7, 8, 9); + reader.ReadEnd(); + } + + [Test] + public void BatchUnevenlyDivisibleSequence() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(4, pool, Enumerable.ToArray); + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3, 4); + reader.Read().AssertSequenceEqual(5, 6, 7, 8); + reader.Read().AssertSequenceEqual(9); + reader.ReadEnd(); + } + + [Test] + public void BatchIsLazy() + { + var input = new BreakingSequence(); + _ = input.Batch(1, ArrayPool.Shared, + BreakingFunc.Of, IEnumerable>(), + BreakingFunc.Of, object>()); + } + + [TestCase(SourceKind.BreakingList , 0)] + [TestCase(SourceKind.BreakingReadOnlyList, 0)] + [TestCase(SourceKind.BreakingList , 1)] + [TestCase(SourceKind.BreakingReadOnlyList, 1)] + [TestCase(SourceKind.BreakingList , 2)] + [TestCase(SourceKind.BreakingReadOnlyList, 2)] + public void BatchCollectionSmallerThanSize(SourceKind kind, int oversize) + { + var xs = new[] { 1, 2, 3, 4, 5 }; + using var pool = new TestArrayPool(); + + var result = xs.ToSourceKind(kind) + .Batch(xs.Length + oversize, pool, Enumerable.ToArray); + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); + reader.ReadEnd(); + } + + [Test] + public void BatchReadOnlyCollectionSmallerThanSize() + { + var collection = ReadOnlyCollection.From(1, 2, 3, 4, 5); + using var pool = new TestArrayPool(); + + var result = collection.Batch(collection.Count * 2, pool, + Enumerable.ToArray); + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); + reader.ReadEnd(); + } + + [TestCase(SourceKind.Sequence)] + [TestCase(SourceKind.BreakingList)] + [TestCase(SourceKind.BreakingReadOnlyList)] + [TestCase(SourceKind.BreakingReadOnlyCollection)] + [TestCase(SourceKind.BreakingCollection)] + public void BatchEmptySource(SourceKind kind) + { + using var pool = new TestArrayPool(); + + var result = Enumerable.Empty() + .ToSourceKind(kind) + .Batch(100, pool, Enumerable.ToArray); + + Assert.That(result, Is.Empty); + } + + [Test] + public void BatchFilterBucket() + { + const int scale = 2; + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(3, pool, + current => from n in current + where n % 2 == 0 + select n * scale, + Enumerable.ToArray); + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(2 * scale); + reader.Read().AssertSequenceEqual(4 * scale, 6 * scale); + reader.Read().AssertSequenceEqual(8 * scale); + reader.ReadEnd(); + } + + [Test] + public void BatchSumBucket() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(3, pool, Enumerable.Sum); + + using var reader = result.Read(); + Assert.That(reader.Read(), Is.EqualTo(1 + 2 + 3)); + Assert.That(reader.Read(), Is.EqualTo(4 + 5 + 6)); + Assert.That(reader.Read(), Is.EqualTo(7 + 8 + 9)); + reader.ReadEnd(); + } + + /// + /// This test does not exercise the intended usage! + /// + + [Test] + public void BatchUpdatesCurrentListInPlace() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(4, pool, current => current, current => (ICurrentBuffer)current); + + using var reader = result.Read(); + var current = reader.Read(); + current.AssertSequenceEqual(1, 2, 3, 4); + _ = reader.Read(); + current.AssertSequenceEqual(5, 6, 7, 8); + _ = reader.Read(); + current.AssertSequenceEqual(9); + + reader.ReadEnd(); + + Assert.That(current, Is.Empty); + } + + [Test] + public void BatchCurrentListIndexerWithBadIndexThrowsArgumentOutOfRangeException() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + + var result = input.Batch(4, pool, current => current, current => (ICurrentBuffer)current); + + using var reader = result.Read(); + var current = reader.Read(); + + Assert.That(() => current[100], Throws.ArgumentOutOfRangeException("index")); + } + + [Test] + public void BatchCallsBucketSelectorBeforeIteratingSource() + { + var iterations = 0; + IEnumerable Source() + { + iterations++; + yield break; + } + + var input = Source(); + using var pool = new TestArrayPool(); + var initIterations = -1; + + var result = input.Batch(4, pool, + current => + { + initIterations = iterations; + return current; + }, + _ => 0); + + using var enumerator = result.GetEnumerator(); + Assert.That(enumerator.MoveNext(), Is.False); + Assert.That(initIterations, Is.Zero); + } + + [Test] + public void BatchBucketSelectorCurrentList() + { + using var input = TestingSequence.Of(1, 2, 3, 4, 5, 6, 7, 8, 9); + using var pool = new TestArrayPool(); + int[]? bucketSelectorItems = null; + + var result = input.Batch(4, pool, current => bucketSelectorItems = current.ToArray(), _ => 0); + + using var reader = result.Read(); + _ = reader.Read(); + Assert.That(bucketSelectorItems, Is.Not.Null); + Assert.That(bucketSelectorItems, Is.Empty); + } + + /// + /// An implementation for testing purposes that holds only + /// one array in the pool and ensures that it is returned when the pool is disposed. + /// + + sealed class TestArrayPool : ArrayPool, IDisposable + { + T[]? _pooledArray; + T[]? _rentedArray; + + public override T[] Rent(int minimumLength) + { + if (_pooledArray is null && _rentedArray is null) + _pooledArray = new T[minimumLength * 2]; + + (_pooledArray, _rentedArray) = + (null, _pooledArray ?? throw new InvalidOperationException("The pool is exhausted.")); + + return _rentedArray; + } + + public override void Return(T[] array, bool clearArray = false) + { + if (_rentedArray is null) + throw new InvalidOperationException("Cannot return when nothing has been rented from this pool."); + + if (array != _rentedArray) + throw new InvalidOperationException("Cannot return what has not been rented from this pool."); + + _pooledArray = array; + _rentedArray = null; + } + + public void Dispose() => + Assert.That(_rentedArray, Is.Null); + } + } +} + +#endif // NETCOREAPP3_1_OR_GREATER diff --git a/MoreLinq.Test/BreakingAction.cs b/MoreLinq.Test/BreakingAction.cs index 52dd2ab28..f4305835d 100644 --- a/MoreLinq.Test/BreakingAction.cs +++ b/MoreLinq.Test/BreakingAction.cs @@ -28,15 +28,15 @@ static class BreakingAction () => throw new NotImplementedException(); internal static Action Of() => - t => throw new NotImplementedException(); + _ => throw new NotImplementedException(); internal static Action Of() => - (t1, t2) => throw new NotImplementedException(); + (_, _) => throw new NotImplementedException(); internal static Action Of() => - (t1, t2, t3) => throw new NotImplementedException(); + (_, _, _) => throw new NotImplementedException(); internal static Action Of() => - (t1, t2, t3, t4) => throw new NotImplementedException(); + (_, _, _, _) => throw new NotImplementedException(); } } diff --git a/MoreLinq.Test/BreakingCollection.cs b/MoreLinq.Test/BreakingCollection.cs index 9a390fd6a..7cb34e6b9 100644 --- a/MoreLinq.Test/BreakingCollection.cs +++ b/MoreLinq.Test/BreakingCollection.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2018 Jonas Nyrup. 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. @@ -26,8 +26,6 @@ class BreakingCollection : BreakingSequence, ICollection public BreakingCollection(params T[] values) : this ((IList) values) {} public BreakingCollection(IList list) => List = list; - public BreakingCollection(int count) : - this(Enumerable.Repeat(default(T), count).ToList()) {} public int Count => List.Count; diff --git a/MoreLinq.Test/BreakingFunc.cs b/MoreLinq.Test/BreakingFunc.cs index 16d6fb794..99b1a5692 100644 --- a/MoreLinq.Test/BreakingFunc.cs +++ b/MoreLinq.Test/BreakingFunc.cs @@ -28,15 +28,15 @@ internal static Func Of() => () => throw new NotImplementedException(); internal static Func Of() => - t => throw new NotImplementedException(); + _ => throw new NotImplementedException(); internal static Func Of() => - (t1, t2) => throw new NotImplementedException(); + (_, _) => throw new NotImplementedException(); internal static Func Of() => - (t1, t2, t3) => throw new NotImplementedException(); + (_, _, _) => throw new NotImplementedException(); internal static Func Of() => - (t1, t2, t3, t4) => throw new NotImplementedException(); + (_, _, _, _) => throw new NotImplementedException(); } } diff --git a/MoreLinq.Test/BreakingList.cs b/MoreLinq.Test/BreakingList.cs index a9b72e22e..e2cb32936 100644 --- a/MoreLinq.Test/BreakingList.cs +++ b/MoreLinq.Test/BreakingList.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 Avi Levin. 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.Test { using System; diff --git a/MoreLinq.Test/BreakingReadOnlyCollection.cs b/MoreLinq.Test/BreakingReadOnlyCollection.cs index a05e36607..5a13ed7b6 100644 --- a/MoreLinq.Test/BreakingReadOnlyCollection.cs +++ b/MoreLinq.Test/BreakingReadOnlyCollection.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2018 Leandro F. Vieira. 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. diff --git a/MoreLinq.Test/BreakingReadOnlyList.cs b/MoreLinq.Test/BreakingReadOnlyList.cs index 216238b91..5fb25c193 100644 --- a/MoreLinq.Test/BreakingReadOnlyList.cs +++ b/MoreLinq.Test/BreakingReadOnlyList.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 Avi Levin. 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.Test { using System.Collections; diff --git a/MoreLinq.Test/BreakingSequence.cs b/MoreLinq.Test/BreakingSequence.cs index 9ed0ccaae..c3c223311 100644 --- a/MoreLinq.Test/BreakingSequence.cs +++ b/MoreLinq.Test/BreakingSequence.cs @@ -27,7 +27,14 @@ namespace MoreLinq.Test /// class BreakingSequence : IEnumerable { - public IEnumerator GetEnumerator() => throw new InvalidOperationException(); + public IEnumerator GetEnumerator() => throw new BreakException(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } + + sealed class BreakException : Exception + { + public BreakException() { } + public BreakException(string message) : base(message) { } + public BreakException(string message, Exception inner) : base(message, inner) { } + } } diff --git a/MoreLinq.Test/CartesianTest.cs b/MoreLinq.Test/CartesianTest.cs index bdbcea332..4277eb7bf 100644 --- a/MoreLinq.Test/CartesianTest.cs +++ b/MoreLinq.Test/CartesianTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -25,12 +42,12 @@ public void TestCartesianIsLazy() [Test] public void TestCartesianOfEmptySequences() { - using (var sequenceA = Enumerable.Empty().AsTestingSequence()) - using (var sequenceB = Enumerable.Empty().AsTestingSequence()) - { - var result = sequenceA.Cartesian(sequenceB, (a, b) => a + b); - Assert.That(result, Is.Empty); - } + using var sequenceA = Enumerable.Empty().AsTestingSequence(); + using var sequenceB = Enumerable.Empty().AsTestingSequence(); + + var result = sequenceA.Cartesian(sequenceB, (a, b) => a + b); + + Assert.That(result, Is.Empty); } /// @@ -66,12 +83,12 @@ public void TestCartesianProductCount() const int countA = 100; const int countB = 75; const int expectedCount = countA*countB; - using (var sequenceA = Enumerable.Range(1, countA).AsTestingSequence()) - using (var sequenceB = Enumerable.Range(1, countB).AsTestingSequence()) - { - var result = sequenceA.Cartesian(sequenceB, (a, b) => a + b); - Assert.AreEqual( expectedCount, result.Count() ); - } + using var sequenceA = Enumerable.Range(1, countA).AsTestingSequence(); + using var sequenceB = Enumerable.Range(1, countB).AsTestingSequence(); + + var result = sequenceA.Cartesian(sequenceB, (a, b) => a + b); + + Assert.That(result.Count(), Is.EqualTo(expectedCount)); } /// @@ -86,15 +103,15 @@ public void TestCartesianProductCount_Multidimensional() const int countC = 8; const int countD = 7; + using var sequenceA = Enumerable.Range(1, countA).AsTestingSequence(); + using var sequenceB = Enumerable.Range(1, countB).AsTestingSequence(); + using var sequenceC = Enumerable.Range(1, countC).AsTestingSequence(); + using var sequenceD = Enumerable.Range(1, countD).AsTestingSequence(); + + var result = sequenceA.Cartesian(sequenceB, sequenceC, sequenceD, (a, b, c, d) => a + b + c + d); + const int expectedCount = countA * countB * countC * countD; - using (var sequenceA = Enumerable.Range(1, countA).AsTestingSequence()) - using (var sequenceB = Enumerable.Range(1, countB).AsTestingSequence()) - using (var sequenceC = Enumerable.Range(1, countC).AsTestingSequence()) - using (var sequenceD = Enumerable.Range(1, countD).AsTestingSequence()) - { - var result = sequenceA.Cartesian(sequenceB, sequenceC, sequenceD, (a, b, c, d) => a + b + c + d); - Assert.AreEqual(expectedCount, result.Count()); - } + Assert.That(result.Count(), Is.EqualTo(expectedCount)); } /// @@ -115,20 +132,19 @@ public void TestCartesianProductCombinations() Enumerable.Repeat(false, 5).ToArray() }; - using (var tsA = sequenceA.AsTestingSequence()) - using (var tsB = sequenceB.AsTestingSequence()) - { - var result = tsA.Cartesian(tsB, (a, b) => new { A = a, B = b }) - .ToArray(); + using var tsA = sequenceA.AsTestingSequence(); + using var tsB = sequenceB.AsTestingSequence(); - // verify that the expected number of results is correct - Assert.AreEqual(sequenceA.Count() * sequenceB.Count(), result.Count()); + var result = tsA.Cartesian(tsB, (a, b) => new { A = a, B = b }) + .ToArray(); - // ensure that all "cells" were visited by the cartesian product - foreach (var coord in result) - expectedSet[coord.A][coord.B] = true; - Assert.IsTrue(expectedSet.SelectMany(x => x).All(z => z)); - } + // verify that the expected number of results is correct + Assert.That(result.Count(), Is.EqualTo(sequenceA.Count() * sequenceB.Count())); + + // ensure that all "cells" were visited by the cartesian product + foreach (var coord in result) + expectedSet[coord.A][coord.B] = true; + Assert.That(expectedSet.SelectMany(x => x).All(z => z), Is.True); } /// @@ -138,16 +154,15 @@ public void TestCartesianProductCombinations() [Test] public void TestEmptyCartesianEvaluation() { - using (var sequence = Enumerable.Range(0, 5).AsTestingSequence()) - { - var resultA = sequence.Cartesian(Enumerable.Empty(), (a, b) => new { A = a, B = b }); - var resultB = Enumerable.Empty().Cartesian(sequence, (a, b) => new { A = a, B = b }); - var resultC = Enumerable.Empty().Cartesian(Enumerable.Empty(), (a, b) => new { A = a, B = b }); + using var sequence = Enumerable.Range(0, 5).AsTestingSequence(); - Assert.AreEqual(0, resultA.Count()); - Assert.AreEqual(0, resultB.Count()); - Assert.AreEqual(0, resultC.Count()); - } + var resultA = sequence.Cartesian(Enumerable.Empty(), (a, b) => new { A = a, B = b }); + var resultB = Enumerable.Empty().Cartesian(sequence, (a, b) => new { A = a, B = b }); + var resultC = Enumerable.Empty().Cartesian(Enumerable.Empty(), (a, b) => new { A = a, B = b }); + + Assert.That(resultA.Count(), Is.Zero); + Assert.That(resultB.Count(), Is.Zero); + Assert.That(resultC.Count(), Is.Zero); } } } diff --git a/MoreLinq.Test/ChooseTest.cs b/MoreLinq.Test/ChooseTest.cs index 95e2b3c8a..138b565c4 100644 --- a/MoreLinq.Test/ChooseTest.cs +++ b/MoreLinq.Test/ChooseTest.cs @@ -33,30 +33,29 @@ public void IsLazy() [Test] public void WithEmptySource() { - using (var xs = Enumerable.Empty().AsTestingSequence()) - Assert.That(xs.Choose(BreakingFunc.Of()), Is.Empty); + using var xs = Enumerable.Empty().AsTestingSequence(); + Assert.That(xs.Choose(BreakingFunc.Of()), Is.Empty); } [Test] public void None() { - using (var xs = Enumerable.Range(1, 10).AsTestingSequence()) - Assert.That(xs.Choose(_ => (false, 0)), Is.Empty); + using var xs = Enumerable.Range(1, 10).AsTestingSequence(); + Assert.That(xs.Choose(_ => (false, 0)), Is.Empty); } [Test] public void ThoseParsable() { - using (var xs = + using var xs = "O,l,2,3,4,S,6,7,B,9" - .Split(',') - .Choose(s => (int.TryParse(s, NumberStyles.Integer, - CultureInfo.InvariantCulture, - out var n), n)) - .AsTestingSequence()) - { - xs.AssertSequenceEqual(2, 3, 4, 6, 7, 9); - } + .Split(',') + .Choose(s => (int.TryParse(s, NumberStyles.Integer, + CultureInfo.InvariantCulture, + out var n), n)) + .AsTestingSequence(); + + xs.AssertSequenceEqual(2, 3, 4, 6, 7, 9); } // A cheap trick to masquerade a tuple as an option @@ -68,14 +67,16 @@ static class Option static class Option { - public static readonly (bool IsSome, T Value) None = (false, default); +#pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649) + public static readonly (bool IsSome, T Value) None = default; +#pragma warning restore CA1805 // Do not initialize unnecessarily } [Test] public void ThoseThatAreIntegers() { new int?[] { 0, 1, 2, null, 4, null, 6, null, null, 9 } - .Choose(e => e is int n ? Option.Some(n) : Option.None) + .Choose(e => e is {} n ? Option.Some(n) : Option.None) .AssertSequenceEqual(0, 1, 2, 4, 6, 9); } diff --git a/MoreLinq.Test/Combinatorics.cs b/MoreLinq.Test/Combinatorics.cs index aa43fae06..0a5d3b903 100644 --- a/MoreLinq.Test/Combinatorics.cs +++ b/MoreLinq.Test/Combinatorics.cs @@ -1,3 +1,19 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { diff --git a/MoreLinq.Test/Comparable.cs b/MoreLinq.Test/Comparable.cs new file mode 100644 index 000000000..4ec8d7a62 --- /dev/null +++ b/MoreLinq.Test/Comparable.cs @@ -0,0 +1,28 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2020 Atif Aziz. 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.Test +{ + using System; + using System.Collections.Generic; + + static class Comparable where T : IComparable + { + public static readonly IComparer DescendingOrderComparer = + Comparer.Create((x, y) => -Math.Sign(x.CompareTo(y))); + } +} diff --git a/MoreLinq.Test/CompareCountTest.cs b/MoreLinq.Test/CompareCountTest.cs index 6deba897c..906749bf6 100644 --- a/MoreLinq.Test/CompareCountTest.cs +++ b/MoreLinq.Test/CompareCountTest.cs @@ -53,13 +53,12 @@ public void CompareCountWithCollectionAndSequence(int collectionCount, int expectedCompareCount, int expectedMoveNextCallCount) { - var collection = new BreakingCollection(collectionCount); + var collection = new BreakingCollection(new int[collectionCount]); - using (var seq = Enumerable.Range(0, sequenceCount).AsTestingSequence()) - { - Assert.AreEqual(expectedCompareCount, collection.CompareCount(seq)); - Assert.AreEqual(expectedMoveNextCallCount, seq.MoveNextCallCount); - } + using var seq = Enumerable.Range(0, sequenceCount).AsTestingSequence(); + + Assert.That(collection.CompareCount(seq), Is.EqualTo(expectedCompareCount)); + Assert.That(seq.MoveNextCallCount, Is.EqualTo(expectedMoveNextCallCount)); } [TestCase(0, 0, 0, 1)] @@ -71,13 +70,12 @@ public void CompareCountWithSequenceAndCollection(int sequenceCount, int expectedCompareCount, int expectedMoveNextCallCount) { - var collection = new BreakingCollection(collectionCount); + var collection = new BreakingCollection(new int[collectionCount]); - using (var seq = Enumerable.Range(0, sequenceCount).AsTestingSequence()) - { - Assert.AreEqual(expectedCompareCount, seq.CompareCount(collection)); - Assert.AreEqual(expectedMoveNextCallCount, seq.MoveNextCallCount); - } + using var seq = Enumerable.Range(0, sequenceCount).AsTestingSequence(); + + Assert.That(seq.CompareCount(collection), Is.EqualTo(expectedCompareCount)); + Assert.That(seq.MoveNextCallCount, Is.EqualTo(expectedMoveNextCallCount)); } [TestCase(0, 0, 0, 1)] @@ -89,45 +87,41 @@ public void CompareCountWithSequenceAndSequence(int sequenceCount1, int expectedCompareCount, int expectedMoveNextCallCount) { - using (var seq1 = Enumerable.Range(0, sequenceCount1).AsTestingSequence()) - using (var seq2 = Enumerable.Range(0, sequenceCount2).AsTestingSequence()) - { - Assert.AreEqual(expectedCompareCount, seq1.CompareCount(seq2)); - Assert.AreEqual(expectedMoveNextCallCount, seq1.MoveNextCallCount); - Assert.AreEqual(expectedMoveNextCallCount, seq2.MoveNextCallCount); - } + using var seq1 = Enumerable.Range(0, sequenceCount1).AsTestingSequence(); + using var seq2 = Enumerable.Range(0, sequenceCount2).AsTestingSequence(); + + Assert.That(seq1.CompareCount(seq2), Is.EqualTo(expectedCompareCount)); + Assert.That(seq1.MoveNextCallCount, Is.EqualTo(expectedMoveNextCallCount)); + Assert.That(seq2.MoveNextCallCount, Is.EqualTo(expectedMoveNextCallCount)); } [Test] public void CompareCountDisposesSequenceEnumerators() { - using (var seq1 = TestingSequence.Of()) - using (var seq2 = TestingSequence.Of()) - { - Assert.AreEqual(0, seq1.CompareCount(seq2)); - } + using var seq1 = TestingSequence.Of(); + using var seq2 = TestingSequence.Of(); + + Assert.That(seq1.CompareCount(seq2), Is.Zero); } [Test] public void CompareCountDisposesFirstEnumerator() { - var collection = new BreakingCollection(0); + var collection = new BreakingCollection(); - using (var seq = TestingSequence.Of()) - { - Assert.AreEqual(0, seq.CompareCount(collection)); - } + using var seq = TestingSequence.Of(); + + Assert.That(seq.CompareCount(collection), Is.Zero); } [Test] public void CompareCountDisposesSecondEnumerator() { - var collection = new BreakingCollection(0); + var collection = new BreakingCollection(); - using (var seq = TestingSequence.Of()) - { - Assert.AreEqual(0, collection.CompareCount(seq)); - } + using var seq = TestingSequence.Of(); + + Assert.That(collection.CompareCount(seq), Is.Zero); } [Test] @@ -141,8 +135,8 @@ public void CompareCountDoesNotIterateUnnecessaryElements() var seq2 = Enumerable.Range(1, 3); - Assert.AreEqual( 1, seq1.CompareCount(seq2)); - Assert.AreEqual(-1, seq2.CompareCount(seq1)); + Assert.That(seq1.CompareCount(seq2), Is.EqualTo( 1)); + Assert.That(seq2.CompareCount(seq1), Is.EqualTo(-1)); } enum SequenceKind diff --git a/MoreLinq.Test/Comparer.cs b/MoreLinq.Test/Comparer.cs deleted file mode 100644 index ea9688004..000000000 --- a/MoreLinq.Test/Comparer.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace MoreLinq.Test -{ - using System; - using System.Collections.Generic; - - sealed class Comparer - { - /// - /// Creates an given a - /// . - /// - - public static IComparer Create(Func compare) => - new DelegatingComparer(compare); - - sealed class DelegatingComparer : IComparer - { - readonly Func _comparer; - - public DelegatingComparer(Func comparer) - { - _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); - } - - public int Compare(T x, T y) => _comparer(x, y); - } - } -} diff --git a/MoreLinq.Test/ConsumeTest.cs b/MoreLinq.Test/ConsumeTest.cs index ebc8f4e88..5d802b344 100644 --- a/MoreLinq.Test/ConsumeTest.cs +++ b/MoreLinq.Test/ConsumeTest.cs @@ -26,9 +26,9 @@ public class ConsumeTest public void ConsumeReallyConsumes() { var counter = 0; - var sequence = Enumerable.Range(0, 10).Pipe(x => counter++); + var sequence = Enumerable.Range(0, 10).Pipe(_ => counter++); sequence.Consume(); - Assert.AreEqual(10, counter); + Assert.That(counter, Is.EqualTo(10)); } } } diff --git a/MoreLinq.Test/CountBetweenTest.cs b/MoreLinq.Test/CountBetweenTest.cs index 2fc5fe4cc..6e57ba32c 100644 --- a/MoreLinq.Test/CountBetweenTest.cs +++ b/MoreLinq.Test/CountBetweenTest.cs @@ -25,29 +25,29 @@ public class CountBetweenTest [Test] public void CountBetweenWithNegativeMin() { - AssertThrowsArgument.OutOfRangeException("min", () => - new[] { 1 }.CountBetween(-1, 0)); + Assert.That(() => new[] { 1 }.CountBetween(-1, 0), + Throws.ArgumentOutOfRangeException("min")); } [Test] public void CountBetweenWithNegativeMax() { - AssertThrowsArgument.OutOfRangeException("max", () => - new[] { 1 }.CountBetween(0, -1)); + Assert.That(() => new[] { 1 }.CountBetween(0, -1), + Throws.ArgumentOutOfRangeException("max")); } [Test] public void CountBetweenWithMaxLesserThanMin() { - AssertThrowsArgument.OutOfRangeException("max", () => - new[] { 1 }.CountBetween(1, 0)); + Assert.That(() => new[] { 1 }.CountBetween(1, 0), + Throws.ArgumentOutOfRangeException("max")); } [Test] public void CountBetweenWithMaxEqualsMin() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.CountBetween(1, 1)); + Assert.That(xs.CountBetween(1, 1), Is.True); } [TestCase(1, 2, 4, false)] @@ -69,7 +69,7 @@ public void CountBetweenDoesNotIterateUnnecessaryElements() () => 3, () => 4, () => throw new TestException()); - Assert.False(source.CountBetween(2, 3)); + Assert.That(source.CountBetween(2, 3), Is.False); } } } diff --git a/MoreLinq.Test/CountByTest.cs b/MoreLinq.Test/CountByTest.cs index 26158260d..f0732a7ba 100644 --- a/MoreLinq.Test/CountByTest.cs +++ b/MoreLinq.Test/CountByTest.cs @@ -98,10 +98,22 @@ public void CountByWithSomeNullKeys() var result = ss.CountBy(s => s); result.AssertSequenceEqual( - KeyValuePair.Create("foo", 2), - KeyValuePair.Create((string) null, 4), - KeyValuePair.Create("bar", 2), - KeyValuePair.Create("baz", 2)); + KeyValuePair.Create((string?)"foo", 2), + KeyValuePair.Create((string?)null, 4), + KeyValuePair.Create((string?)"bar", 2), + KeyValuePair.Create((string?)"baz", 2)); + } + + [Test] + public void CountByWithSomeNullKeysAndEqualityComparer() + { + var result = new[] { "a", "B", null, "c", "A", null, "b", "A" }.CountBy(c => c, StringComparer.OrdinalIgnoreCase); + + result.AssertSequenceEqual( + KeyValuePair.Create((string?)"a", 3), + KeyValuePair.Create((string?)"B", 2), + KeyValuePair.Create((string?)null, 2), + KeyValuePair.Create((string?)"c", 1)); } } } diff --git a/MoreLinq.Test/CountDownTest.cs b/MoreLinq.Test/CountDownTest.cs index 4e6a6d30f..a1862116e 100644 --- a/MoreLinq.Test/CountDownTest.cs +++ b/MoreLinq.Test/CountDownTest.cs @@ -67,11 +67,9 @@ static IEnumerable GetData(Func selector) [TestCaseSource(nameof(SequenceData))] public IEnumerable<(int, int?)> WithSequence(int[] xs, int count) { - using (var ts = xs.Select(x => x).AsTestingSequence()) - { - foreach (var e in ts.CountDown(count, ValueTuple.Create)) - yield return e; - } + using var ts = xs.Select(x => x).AsTestingSequence(); + foreach (var e in ts.CountDown(count, ValueTuple.Create)) + yield return e; } static readonly IEnumerable ListData = @@ -108,7 +106,7 @@ IEnumerator Watch(IEnumerator e) { moves = 0; disposed = false; - var te = e.AsWatchtable(); + var te = e.AsWatchable(); te.Disposed += delegate { disposed = true; }; te.MoveNextCalled += delegate { moves++; }; return te; @@ -135,14 +133,14 @@ static class TestCollection { public static ICollection Create(ICollection collection, - Func, IEnumerator> em = null) + Func, IEnumerator>? em = null) { return new Collection(collection, em); } public static IReadOnlyCollection CreateReadOnly(ICollection collection, - Func, IEnumerator> em = null) + Func, IEnumerator>? em = null) { return new ReadOnlyCollection(collection, em); } @@ -156,7 +154,7 @@ abstract class Sequence : IEnumerable { readonly Func, IEnumerator> _em; - protected Sequence(Func, IEnumerator> em) => + protected Sequence(Func, IEnumerator>? em) => _em = em ?? (e => e); public IEnumerator GetEnumerator() => @@ -177,7 +175,7 @@ sealed class Collection : Sequence, ICollection readonly ICollection _collection; public Collection(ICollection collection, - Func, IEnumerator> em = null) : + Func, IEnumerator>? em = null) : base(em) => _collection = collection ?? throw new ArgumentNullException(nameof(collection)); @@ -204,7 +202,7 @@ sealed class ReadOnlyCollection : Sequence, IReadOnlyCollection readonly ICollection _collection; public ReadOnlyCollection(ICollection collection, - Func, IEnumerator> em = null) : + Func, IEnumerator>? em = null) : base(em) => _collection = collection ?? throw new ArgumentNullException(nameof(collection)); diff --git a/MoreLinq.Test/CurrentThreadCultureScope.cs b/MoreLinq.Test/CurrentThreadCultureScope.cs index 4a5dad3ce..e647a2822 100644 --- a/MoreLinq.Test/CurrentThreadCultureScope.cs +++ b/MoreLinq.Test/CurrentThreadCultureScope.cs @@ -34,11 +34,7 @@ protected override void Restore(CultureInfo old) static void Install(CultureInfo value) { -#if NET451 - System.Threading.Thread.CurrentThread.CurrentCulture = value; -#else CultureInfo.CurrentCulture = value; -#endif } } } diff --git a/MoreLinq.Test/EndsWithTest.cs b/MoreLinq.Test/EndsWithTest.cs index 89334b2a9..316dc7271 100644 --- a/MoreLinq.Test/EndsWithTest.cs +++ b/MoreLinq.Test/EndsWithTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2016 Andreas Gullberg Larsen. 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. @@ -52,13 +52,13 @@ public bool EndsWithWithStrings(string first, string second) [Test] public void EndsWithReturnsTrueIfBothEmpty() { - Assert.True(new int[0].EndsWith(new int[0])); + Assert.That(new int[0].EndsWith(new int[0]), Is.True); } [Test] public void EndsWithReturnsFalseIfOnlyFirstIsEmpty() { - Assert.False(new int[0].EndsWith(new[] {1,2,3})); + Assert.That(new int[0].EndsWith(new[] {1,2,3}), Is.False); } [TestCase("", "", ExpectedResult = true)] @@ -72,11 +72,10 @@ public bool EndsWithReturnsTrueIfSecondIsEmpty(string first, string second) [Test] public void EndsWithDisposesBothSequenceEnumerators() { - using (var first = TestingSequence.Of(1,2,3)) - using (var second = TestingSequence.Of(1)) - { - first.EndsWith(second); - } + using var first = TestingSequence.Of(1,2,3); + using var second = TestingSequence.Of(1); + + first.EndsWith(second); } [Test] @@ -86,10 +85,10 @@ public void EndsWithUsesSpecifiedEqualityComparerOrDefault() var first = new[] {1,2,3}; var second = new[] {4,5,6}; - Assert.False(first.EndsWith(second)); - Assert.False(first.EndsWith(second, null)); - Assert.False(first.EndsWith(second, EqualityComparer.Create(delegate { return false; }))); - Assert.True(first.EndsWith(second, EqualityComparer.Create(delegate { return true; }))); + Assert.That(first.EndsWith(second), Is.False); + Assert.That(first.EndsWith(second, null), Is.False); + Assert.That(first.EndsWith(second, EqualityComparer.Create(delegate { return false; })), Is.False); + Assert.That(first.EndsWith(second, EqualityComparer.Create(delegate { return true; })), Is.True); } [TestCase(SourceKind.BreakingCollection)] @@ -99,7 +98,7 @@ public void EndsWithUsesCollectionsCountToAvoidUnnecessaryIteration(SourceKind s var first = new[] { 1, 2 }.ToSourceKind(sourceKind); var second = new[] { 1, 2, 3 }.ToSourceKind(sourceKind); - Assert.False(first.EndsWith(second)); + Assert.That(first.EndsWith(second), Is.False); } } } diff --git a/MoreLinq.Test/Enumerable.cs b/MoreLinq.Test/Enumerable.cs index a01f088b7..f7795af4e 100644 --- a/MoreLinq.Test/Enumerable.cs +++ b/MoreLinq.Test/Enumerable.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Atif Aziz. 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.Test { using System; @@ -109,7 +126,7 @@ public static int Count(this IEnumerable source, Func(this IEnumerable source) => LinqEnumerable.Count(source); - public static IEnumerable DefaultIfEmpty(this IEnumerable source) => + public static IEnumerable DefaultIfEmpty(this IEnumerable source) => LinqEnumerable.DefaultIfEmpty(source); public static IEnumerable DefaultIfEmpty(this IEnumerable source, TSource defaultValue) => @@ -124,7 +141,7 @@ public static IEnumerable Distinct(this IEnumerable s public static TSource ElementAt(this IEnumerable source, int index) => LinqEnumerable.ElementAt(source, index); - public static TSource ElementAtOrDefault(this IEnumerable source, int index) => + public static TSource? ElementAtOrDefault(this IEnumerable source, int index) => LinqEnumerable.ElementAtOrDefault(source, index); public static IEnumerable Empty() => @@ -142,10 +159,10 @@ public static TSource First(this IEnumerable source) => public static TSource First(this IEnumerable source, Func predicate) => LinqEnumerable.First(source, predicate); - public static TSource FirstOrDefault(this IEnumerable source) => + public static TSource? FirstOrDefault(this IEnumerable source) => LinqEnumerable.FirstOrDefault(source); - public static TSource FirstOrDefault(this IEnumerable source, Func predicate) => + public static TSource? FirstOrDefault(this IEnumerable source, Func predicate) => LinqEnumerable.FirstOrDefault(source, predicate); public static IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func, TResult> resultSelector, IEqualityComparer comparer) => @@ -196,10 +213,10 @@ public static TSource Last(this IEnumerable source) => public static TSource Last(this IEnumerable source, Func predicate) => LinqEnumerable.Last(source, predicate); - public static TSource LastOrDefault(this IEnumerable source) => + public static TSource? LastOrDefault(this IEnumerable source) => LinqEnumerable.LastOrDefault(source); - public static TSource LastOrDefault(this IEnumerable source, Func predicate) => + public static TSource? LastOrDefault(this IEnumerable source, Func predicate) => LinqEnumerable.LastOrDefault(source, predicate); public static long LongCount(this IEnumerable source) => @@ -232,13 +249,13 @@ public static decimal Max(this IEnumerable source, Func(this IEnumerable source, Func selector) => LinqEnumerable.Max(source, selector); - public static TResult Max(this IEnumerable source, Func selector) => + public static TResult? Max(this IEnumerable source, Func selector) => LinqEnumerable.Max(source, selector); public static double? Max(this IEnumerable source, Func selector) => LinqEnumerable.Max(source, selector); - public static TSource Max(this IEnumerable source) => + public static TSource? Max(this IEnumerable source) => LinqEnumerable.Max(source); public static float Max(this IEnumerable source, Func selector) => @@ -286,7 +303,7 @@ public static long Min(this IEnumerable source, Func(this IEnumerable source, Func selector) => LinqEnumerable.Min(source, selector); - public static TResult Min(this IEnumerable source, Func selector) => + public static TResult? Min(this IEnumerable source, Func selector) => LinqEnumerable.Min(source, selector); public static long? Min(this IEnumerable source, Func selector) => @@ -304,7 +321,7 @@ public static decimal Min(this IEnumerable source, Func(this IEnumerable source, Func selector) => LinqEnumerable.Min(source, selector); - public static TSource Min(this IEnumerable source) => + public static TSource? Min(this IEnumerable source) => LinqEnumerable.Min(source); public static double Min(this IEnumerable source, Func selector) => @@ -394,10 +411,10 @@ public static TSource Single(this IEnumerable source) => public static TSource Single(this IEnumerable source, Func predicate) => LinqEnumerable.Single(source, predicate); - public static TSource SingleOrDefault(this IEnumerable source) => + public static TSource? SingleOrDefault(this IEnumerable source) => LinqEnumerable.SingleOrDefault(source); - public static TSource SingleOrDefault(this IEnumerable source, Func predicate) => + public static TSource? SingleOrDefault(this IEnumerable source, Func predicate) => LinqEnumerable.SingleOrDefault(source, predicate); public static IEnumerable Skip(this IEnumerable source, int count) => @@ -493,16 +510,20 @@ public static IOrderedEnumerable ThenByDescending(this I public static TSource[] ToArray(this IEnumerable source) => LinqEnumerable.ToArray(source); - public static Dictionary ToDictionary(this IEnumerable source, Func keySelector) => + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector) + where TKey : notnull => LinqEnumerable.ToDictionary(source, keySelector); - public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer comparer) => + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, IEqualityComparer comparer) + where TKey : notnull => LinqEnumerable.ToDictionary(source, keySelector, comparer); - public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector) => + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector) + where TKey : notnull => LinqEnumerable.ToDictionary(source, keySelector, elementSelector); - public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) => + public static Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + where TKey : notnull => LinqEnumerable.ToDictionary(source, keySelector, elementSelector, comparer); public static List ToList(this IEnumerable source) => diff --git a/MoreLinq.Test/EqualityComparer.cs b/MoreLinq.Test/EqualityComparer.cs index 5eb9ba1a4..655999130 100644 --- a/MoreLinq.Test/EqualityComparer.cs +++ b/MoreLinq.Test/EqualityComparer.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Atif Aziz. 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.Test { using System; @@ -10,24 +27,24 @@ static class EqualityComparer /// . /// - public static IEqualityComparer Create(Func comparer) => + public static IEqualityComparer Create(Func comparer) => new DelegatingComparer(comparer); sealed class DelegatingComparer : IEqualityComparer { - readonly Func _comparer; + readonly Func _comparer; readonly Func _hasher; - public DelegatingComparer(Func comparer) + public DelegatingComparer(Func comparer) : this(comparer, x => x == null ? 0 : x.GetHashCode()) {} - DelegatingComparer(Func comparer, Func hasher) + DelegatingComparer(Func comparer, Func hasher) { _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); _hasher = hasher ?? throw new ArgumentNullException(nameof(hasher)); } - public bool Equals(T x, T y) => _comparer(x, y); + public bool Equals(T? x, T? y) => _comparer(x, y); public int GetHashCode(T obj) => _hasher(obj); } } diff --git a/MoreLinq.Test/EquiZipTest.cs b/MoreLinq.Test/EquiZipTest.cs index 8ca05f1d5..477a86384 100644 --- a/MoreLinq.Test/EquiZipTest.cs +++ b/MoreLinq.Test/EquiZipTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2009 Atif Aziz. 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. @@ -17,7 +17,6 @@ namespace MoreLinq.Test { - using System; using NUnit.Framework; using Tuple = System.ValueTuple; @@ -27,25 +26,23 @@ public class EquiZipTest [Test] public void BothSequencesDisposedWithUnequalLengthsAndLongerFirst() { - using (var longer = TestingSequence.Of(1, 2, 3)) - using (var shorter = TestingSequence.Of(1, 2)) - { - // Yes, this will throw... but then we should still have disposed both sequences - Assert.Throws(() => - longer.EquiZip(shorter, (x, y) => x + y).Consume()); - } + using var longer = TestingSequence.Of(1, 2, 3); + using var shorter = TestingSequence.Of(1, 2); + + // Yes, this will throw... but then we should still have disposed both sequences + Assert.That(() => longer.EquiZip(shorter, (x, y) => x + y).Consume(), + Throws.InvalidOperationException); } [Test] public void BothSequencesDisposedWithUnequalLengthsAndShorterFirst() { - using (var longer = TestingSequence.Of(1, 2, 3)) - using (var shorter = TestingSequence.Of(1, 2)) - { - // Yes, this will throw... but then we should still have disposed both sequences - Assert.Throws(() => - shorter.EquiZip(longer, (x, y) => x + y).Consume()); - } + using var longer = TestingSequence.Of(1, 2, 3); + using var shorter = TestingSequence.Of(1, 2); + + // Yes, this will throw... but then we should still have disposed both sequences + Assert.That(() => shorter.EquiZip(longer, (x, y) => x + y).Consume(), + Throws.InvalidOperationException); } [Test] @@ -61,8 +58,7 @@ public void ZipWithFirstSequenceShorterThanSecondFailStrategy() { var zipped = new[] { 1, 2 }.EquiZip(new[] { 4, 5, 6 }, Tuple.Create); Assert.That(zipped, Is.Not.Null); - Assert.Throws(() => - zipped.Consume()); + Assert.That(zipped.Consume, Throws.InvalidOperationException); } [Test] @@ -70,8 +66,7 @@ public void ZipWithFirstSequnceLongerThanSecondFailStrategy() { var zipped = new[] { 1, 2, 3 }.EquiZip(new[] { 4, 5 }, Tuple.Create); Assert.That(zipped, Is.Not.Null); - Assert.Throws(() => - zipped.Consume()); + Assert.That(zipped.Consume, Throws.InvalidOperationException); } [Test] @@ -84,26 +79,24 @@ public void ZipIsLazy() [Test] public void MoveNextIsNotCalledUnnecessarily() { - using (var s1 = TestingSequence.Of(1, 2)) - using (var s2 = TestingSequence.Of(1, 2, 3)) - using (var s3 = MoreEnumerable.From(() => 1, - () => 2, - () => throw new TestException()) - .AsTestingSequence()) - { - Assert.Throws(() => - s1.EquiZip(s2, s3, (x, y, z) => x + y + z).Consume()); - } + using var s1 = TestingSequence.Of(1, 2); + using var s2 = TestingSequence.Of(1, 2, 3); + using var s3 = MoreEnumerable.From(() => 1, + () => 2, + () => throw new TestException()) + .AsTestingSequence(); + + Assert.That(() => s1.EquiZip(s2, s3, (x, y, z) => x + y + z).Consume(), + Throws.InvalidOperationException); } [Test] public void ZipDisposesInnerSequencesCaseGetEnumeratorThrows() { - using (var s1 = TestingSequence.Of(1, 2)) - { - Assert.Throws(() => - s1.EquiZip(new BreakingSequence(), Tuple.Create).Consume()); - } + using var s1 = TestingSequence.Of(1, 2); + + Assert.That(() => s1.EquiZip(new BreakingSequence(), Tuple.Create).Consume(), + Throws.BreakException); } } } diff --git a/MoreLinq.Test/EvaluateTest.cs b/MoreLinq.Test/EvaluateTest.cs index 7330fce27..40bd4eaab 100644 --- a/MoreLinq.Test/EvaluateTest.cs +++ b/MoreLinq.Test/EvaluateTest.cs @@ -20,7 +20,7 @@ namespace MoreLinq.Test using System; using NUnit.Framework; - class EvaluateTest + public class EvaluateTest { [Test] public void TestEvaluateIsLazy() diff --git a/MoreLinq.Test/ExactlyTest.cs b/MoreLinq.Test/ExactlyTest.cs index 938847f14..0d765d139 100644 --- a/MoreLinq.Test/ExactlyTest.cs +++ b/MoreLinq.Test/ExactlyTest.cs @@ -25,36 +25,36 @@ public class ExactlyTest [Test] public void ExactlyWithNegativeCount() { - AssertThrowsArgument.OutOfRangeException("count", () => - new[] { 1 }.Exactly(-1)); + Assert.That(() => new[] { 1 }.Exactly(-1), + Throws.ArgumentOutOfRangeException("count")); } [Test] public void ExactlyWithEmptySequenceHasExactlyZeroElements() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsTrue(xs.Exactly(0)); + Assert.That(xs.Exactly(0), Is.True); } [Test] public void ExactlyWithEmptySequenceHasExactlyOneElement() { foreach (var xs in Enumerable.Empty().ArrangeCollectionTestCases()) - Assert.IsFalse(xs.Exactly(1)); + Assert.That(xs.Exactly(1), Is.False); } [Test] public void ExactlyWithSingleElementHasExactlyOneElements() { foreach (var xs in new[] { 1 }.ArrangeCollectionTestCases()) - Assert.IsTrue(xs.Exactly(1)); + Assert.That(xs.Exactly(1), Is.True); } [Test] public void ExactlyWithManyElementHasExactlyOneElement() { foreach (var xs in new[] { 1, 2, 3 }.ArrangeCollectionTestCases()) - Assert.IsFalse(xs.Exactly(1)); + Assert.That(xs.Exactly(1), Is.False); } [Test] @@ -64,7 +64,7 @@ public void ExactlyDoesNotIterateUnnecessaryElements() () => 2, () => 3, () => throw new TestException()); - Assert.IsFalse(source.Exactly(2)); + Assert.That(source.Exactly(2), Is.False); } } } diff --git a/MoreLinq.Test/ExcludeTest.cs b/MoreLinq.Test/ExcludeTest.cs index 222db4700..1f72bd39d 100644 --- a/MoreLinq.Test/ExcludeTest.cs +++ b/MoreLinq.Test/ExcludeTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -23,8 +40,8 @@ public void TestExcludeIsLazy() [Test] public void TestExcludeNegativeStartIndexException() { - AssertThrowsArgument.OutOfRangeException("startIndex",() => - Enumerable.Range(1, 10).Exclude(-10, 10)); + Assert.That(() => Enumerable.Range(1, 10).Exclude(-10, 10), + Throws.ArgumentOutOfRangeException("startIndex")); } /// @@ -33,8 +50,8 @@ public void TestExcludeNegativeStartIndexException() [Test] public void TestExcludeNegativeCountException() { - AssertThrowsArgument.OutOfRangeException("count",() => - Enumerable.Range(1, 10).Exclude(0, -5)); + Assert.That(() => Enumerable.Range(1, 10).Exclude(0, -5), + Throws.ArgumentOutOfRangeException("count")); } /// diff --git a/MoreLinq.Test/Extensions.cs b/MoreLinq.Test/Extensions.cs new file mode 100644 index 000000000..5ee4520de --- /dev/null +++ b/MoreLinq.Test/Extensions.cs @@ -0,0 +1,46 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2009 Atif Aziz. 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.Test +{ + using System; + using System.Diagnostics; + using System.Globalization; + + static class Extensions + { + /// + /// Formats the value of this object using the invariant culture. + /// + + [DebuggerStepThrough] + public static string ToInvariantString(this T formattable) where T : IFormattable => + formattable.ToInvariantString(null); + + /// + /// Formats the value of this object using a specific format and the + /// invariant culture. + /// + + [DebuggerStepThrough] + public static string ToInvariantString(this T formattable, string? format) where T : IFormattable + { + if (formattable is null) throw new ArgumentNullException(nameof(formattable)); + return formattable.ToString(format, CultureInfo.InvariantCulture); + } + } +} diff --git a/MoreLinq.Test/FallbackIfEmptyTest.cs b/MoreLinq.Test/FallbackIfEmptyTest.cs index f5b4cdc56..65ccfc788 100644 --- a/MoreLinq.Test/FallbackIfEmptyTest.cs +++ b/MoreLinq.Test/FallbackIfEmptyTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2016 Atif Aziz. 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. @@ -42,12 +42,12 @@ public void FallbackIfEmptyPreservesSourceCollectionIfPossible(SourceKind source { var source = new[] { 1 }.ToSourceKind(sourceKind); // ReSharper disable PossibleMultipleEnumeration - Assert.AreSame(source.FallbackIfEmpty(12), source); - Assert.AreSame(source.FallbackIfEmpty(12, 23), source); - Assert.AreSame(source.FallbackIfEmpty(12, 23, 34), source); - Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45), source); - Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45, 56), source); - Assert.AreSame(source.FallbackIfEmpty(12, 23, 34, 45, 56, 67), source); + Assert.That(source.FallbackIfEmpty(12), Is.SameAs(source)); + Assert.That(source.FallbackIfEmpty(12, 23), Is.SameAs(source)); + Assert.That(source.FallbackIfEmpty(12, 23, 34), Is.SameAs(source)); + Assert.That(source.FallbackIfEmpty(12, 23, 34, 45), Is.SameAs(source)); + Assert.That(source.FallbackIfEmpty(12, 23, 34, 45, 56), Is.SameAs(source)); + Assert.That(source.FallbackIfEmpty(12, 23, 34, 45, 56, 67), Is.SameAs(source)); // ReSharper restore PossibleMultipleEnumeration } @@ -57,8 +57,16 @@ public void FallbackIfEmptyPreservesFallbackCollectionIfPossible(SourceKind sour { var source = new int[0].ToSourceKind(sourceKind); var fallback = new[] { 1 }; - Assert.AreSame(source.FallbackIfEmpty(fallback), fallback); - Assert.AreSame(source.FallbackIfEmpty(fallback.AsEnumerable()), fallback); + Assert.That(source.FallbackIfEmpty(fallback), Is.SameAs(fallback)); + Assert.That(source.FallbackIfEmpty(fallback.AsEnumerable()), Is.SameAs(fallback)); + } + + [Test] + public void FallbackIfEmptyWithEmptyNullableSequence() + { + var source = Enumerable.Empty().Select(x => x); + var fallback = (int?)null; + source.FallbackIfEmpty(fallback).AssertSequenceEqual(fallback); } } } diff --git a/MoreLinq.Test/FillBackwardTest.cs b/MoreLinq.Test/FillBackwardTest.cs index c6e713412..19129b27b 100644 --- a/MoreLinq.Test/FillBackwardTest.cs +++ b/MoreLinq.Test/FillBackwardTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. diff --git a/MoreLinq.Test/FillForwardTest.cs b/MoreLinq.Test/FillForwardTest.cs index 2a05e611e..c881210e9 100644 --- a/MoreLinq.Test/FillForwardTest.cs +++ b/MoreLinq.Test/FillForwardTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. @@ -17,6 +17,7 @@ namespace MoreLinq.Test { + using System.Globalization; using System.Text.RegularExpressions; using NUnit.Framework; @@ -62,7 +63,7 @@ select line.Trim() into line Continent = cont, Country = ctry, City = city, - Value = int.Parse(val), + Value = int.Parse(val, CultureInfo.InvariantCulture), }); data = data.FillForward(e => e.Continent == "-", (e, f) => new { f.Continent, e.Country, e.City, e.Value }) diff --git a/MoreLinq.Test/FlattenTest.cs b/MoreLinq.Test/FlattenTest.cs index 9c28bad9b..d9bf5a89d 100644 --- a/MoreLinq.Test/FlattenTest.cs +++ b/MoreLinq.Test/FlattenTest.cs @@ -136,7 +136,7 @@ public void FlattenPredicate() 7, }; - var result = source.Flatten(obj => !(obj is IEnumerable)); + var result = source.Flatten(obj => obj is not IEnumerable); var expectations = new object[] { @@ -236,29 +236,27 @@ public void FlattenFullIteratedDisposesInnerSequences() 7, }; - using (var inner1 = TestingSequence.Of(4, 5)) - using (var inner2 = TestingSequence.Of(true, false)) - using (var inner3 = TestingSequence.Of(6, inner2, 7)) - using (var source = TestingSequence.Of(inner1, inner3)) - { - Assert.That(source.Flatten(), Is.EqualTo(expectations)); - } + using var inner1 = TestingSequence.Of(4, 5); + using var inner2 = TestingSequence.Of(true, false); + using var inner3 = TestingSequence.Of(6, inner2, 7); + using var source = TestingSequence.Of(inner1, inner3); + + Assert.That(source.Flatten(), Is.EqualTo(expectations)); } [Test] public void FlattenInterruptedIterationDisposesInnerSequences() { - using (var inner1 = TestingSequence.Of(4, 5)) - using (var inner2 = MoreEnumerable.From(() => true, - () => false, - () => throw new TestException()) - .AsTestingSequence()) - using (var inner3 = TestingSequence.Of(6, inner2, 7)) - using (var source = TestingSequence.Of(inner1, inner3)) - { - Assert.Throws(() => - source.Flatten().Consume()); - } + using var inner1 = TestingSequence.Of(4, 5); + using var inner2 = MoreEnumerable.From(() => true, + () => false, + () => throw new TestException()) + .AsTestingSequence(); + using var inner3 = TestingSequence.Of(6, inner2, 7); + using var source = TestingSequence.Of(inner1, inner3); + + Assert.That(() => source.Flatten().Consume(), + Throws.TypeOf()); } [Test] @@ -291,8 +289,8 @@ public void FlattenEvaluatesInnerSequencesLazily() Assert.That(result.Take(10), Is.EqualTo(expectations)); - Assert.Throws(() => - source.Flatten().ElementAt(11)); + Assert.That(() => source.Flatten().ElementAt(11), + Throws.TypeOf()); } // Flatten(this IEnumerable source, Func selector) @@ -327,21 +325,13 @@ public void FlattenSelector() } }; - var result = source.Flatten(obj => + var result = source.Flatten(obj => obj switch { - switch (obj) - { - case string _: - return null; - case IEnumerable inner: - return inner; - case Series s: - return new object[] { s.Name, s.Attributes }; - case Attribute a: - return a.Values; - default: - return null; - } + string => null, + IEnumerable inner => inner, + Series s => new object[] { s.Name, s.Attributes }, + Attribute a => a.Values, + _ => null }); var expectations = new object[] { "series1", 1, 2, 3, 4, "series2", 5, 6 }; @@ -370,17 +360,11 @@ public void FlattenSelectorFilteringOnlyIntegers() 4, }; - var result = source.Flatten(obj => + var result = source.Flatten(obj => obj switch { - switch (obj) - { - case int _: - return null; - case IEnumerable inner: - return inner; - default: - return Enumerable.Empty(); - } + int => null, + IEnumerable inner => inner, + _ => Enumerable.Empty() }); var expectations = new object[] { 1, 2, 3, 4 }; @@ -408,19 +392,12 @@ public void FlattenSelectorWithTree() ) ); - var result = new [] { source }.Flatten(obj => + var result = new[] { source }.Flatten(obj => obj switch { - switch (obj) - { - case int _: - return null; - case Tree tree: - return new object[] { tree.Left, tree.Value, tree.Right }; - case IEnumerable inner: - return inner; - default: - return Enumerable.Empty(); - } + int => null, + Tree tree => new object?[] { tree.Left, tree.Value, tree.Right }, + IEnumerable inner => inner, + _ => Enumerable.Empty() }); var expectations = Enumerable.Range(1, 7); @@ -428,25 +405,25 @@ public void FlattenSelectorWithTree() Assert.That(result, Is.EqualTo(expectations)); } - class Series + sealed class Series { - public string Name; - public Attribute[] Attributes; + public required string Name; + public required Attribute[] Attributes; } - class Attribute + sealed class Attribute { - public int[] Values; + public required int[] Values; } - class Tree + sealed class Tree { public readonly T Value; - public readonly Tree Left; - public readonly Tree Right; + public readonly Tree? Left; + public readonly Tree? Right; public Tree(T value) : this(null, value, null) {} - public Tree(Tree left, T value, Tree right) + public Tree(Tree? left, T value, Tree? right) { Left = left; Value = value; diff --git a/MoreLinq.Test/FoldTest.cs b/MoreLinq.Test/FoldTest.cs index 7824b6873..b6c2972f6 100644 --- a/MoreLinq.Test/FoldTest.cs +++ b/MoreLinq.Test/FoldTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2013 Atif Aziz. 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. @@ -26,22 +26,25 @@ public class FoldTest [Test] public void FoldWithTooFewItems() { - Assert.Throws(() => - Enumerable.Range(1, 3).Fold(BreakingFunc.Of())); + Assert.That(() => Enumerable.Range(1, 3).Fold(BreakingFunc.Of()), + Throws.TypeOf() + .And.Message.EqualTo("Sequence contains too few elements when exactly 4 were expected.")); } [Test] public void FoldWithEmptySequence() { - Assert.Throws(() => - Enumerable.Empty().Fold(BreakingFunc.Of())); + Assert.That(() => Enumerable.Empty().Fold(BreakingFunc.Of()), + Throws.TypeOf() + .And.Message.EqualTo("Sequence contains too few elements when exactly 1 was expected.")); } [Test] public void FoldWithTooManyItems() { - Assert.Throws(() => - Enumerable.Range(1, 3).Fold(BreakingFunc.Of())); + Assert.That(() => Enumerable.Range(1, 3).Fold(BreakingFunc.Of()), + Throws.TypeOf() + .And.Message.EqualTo("Sequence contains too many elements when exactly 2 were expected.")); } [Test] diff --git a/MoreLinq.Test/FromTest.cs b/MoreLinq.Test/FromTest.cs index 9577fe231..2822011f0 100644 --- a/MoreLinq.Test/FromTest.cs +++ b/MoreLinq.Test/FromTest.cs @@ -18,10 +18,9 @@ namespace MoreLinq.Test { using System; - using System.Collections.Generic; using NUnit.Framework; - class FromTest + public class FromTest { [Test] public void TestFromIsLazy() @@ -66,15 +65,14 @@ public void TestFromInvokesMethodsMultipleTimes(int numArgs) int F3() { evals[2]++; return -2; } int F4() { evals[3]++; return -2; } - IEnumerable results; - switch (numArgs) + var results = numArgs switch { - case 1: results = MoreEnumerable.From(F1); break; - case 2: results = MoreEnumerable.From(F1, F2); break; - case 3: results = MoreEnumerable.From(F1, F2, F3); break; - case 4: results = MoreEnumerable.From(F1, F2, F3, F4); break; - default: throw new ArgumentOutOfRangeException(nameof(numArgs)); - } + 1 => MoreEnumerable.From(F1), + 2 => MoreEnumerable.From(F1, F2), + 3 => MoreEnumerable.From(F1, F2, F3), + 4 => MoreEnumerable.From(F1, F2, F3, F4), + _ => throw new ArgumentOutOfRangeException(nameof(numArgs)) + }; results.Consume(); results.Consume(); diff --git a/MoreLinq.Test/FullGroupJoinTest.cs b/MoreLinq.Test/FullGroupJoinTest.cs index 61759d37b..f5435cfa5 100644 --- a/MoreLinq.Test/FullGroupJoinTest.cs +++ b/MoreLinq.Test/FullGroupJoinTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2015 Jonathan Skeet. All rights reserved. +// Copyright (c) 2015 Felipe Sateler. 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. @@ -27,6 +27,7 @@ public class FullGroupJoinTest { public enum OverloadCase { CustomResult, TupleResult } + [Test] public void FullGroupIsLazy() { var bs = new BreakingSequence(); @@ -45,12 +46,12 @@ public void FullGroupJoinsResults(OverloadCase overloadCase) var result = FullGroupJoin(overloadCase, listA, listB, x => x).ToDictionary(a => a.Key); - Assert.AreEqual(3, result.Keys.Count); + Assert.That(result.Keys.Count, Is.EqualTo(3)); - Assert.IsEmpty(result[1].Second); + Assert.That(result[1].Second, Is.Empty); result[1].First.AssertSequenceEqual(1); - Assert.IsEmpty(result[3].First); + Assert.That(result[3].First, Is.Empty); result[3].Second.AssertSequenceEqual(3); result[2].First.AssertSequenceEqual(2); @@ -66,13 +67,13 @@ public void FullGroupJoinsEmptyLeft(OverloadCase overloadCase) var result = FullGroupJoin(overloadCase, listA, listB, x => x).ToDictionary(a => a.Key); - Assert.AreEqual(2, result.Keys.Count); + Assert.That(result.Keys.Count, Is.EqualTo(2)); - Assert.IsEmpty(result[2].First); - Assert.AreEqual(2, result[2].Second.Single()); + Assert.That(result[2].First, Is.Empty); + Assert.That(result[2].Second.Single(), Is.EqualTo(2)); - Assert.IsEmpty(result[3].First); - Assert.AreEqual(3, result[3].Second.Single()); + Assert.That(result[3].First, Is.Empty); + Assert.That(result[3].Second.Single(), Is.EqualTo(3)); } [TestCase(CustomResult)] @@ -84,13 +85,13 @@ public void FullGroupJoinsEmptyRight(OverloadCase overloadCase) var result = FullGroupJoin(overloadCase, listA, listB, x => x).ToDictionary(a => a.Key); - Assert.AreEqual(2, result.Keys.Count); + Assert.That(result.Keys.Count, Is.EqualTo(2)); - Assert.AreEqual(2, result[2].First.Single()); - Assert.IsEmpty(result[2].Second); + Assert.That(result[2].First.Single(), Is.EqualTo(2)); + Assert.That(result[2].Second, Is.Empty); - Assert.AreEqual(3, result[3].First.Single()); - Assert.IsEmpty(result[3].Second); + Assert.That(result[3].First.Single(), Is.EqualTo(3)); + Assert.That(result[3].Second, Is.Empty); } [TestCase(CustomResult)] @@ -130,17 +131,12 @@ public void FullGroupPreservesOrder(OverloadCase overloadCase) } } - static IEnumerable<(int Key, IEnumerable First, IEnumerable Second)> FullGroupJoin(OverloadCase overloadCase, IEnumerable listA, IEnumerable listB, Func getKey) - { - switch (overloadCase) + static IEnumerable<(int Key, IEnumerable First, IEnumerable Second)> FullGroupJoin(OverloadCase overloadCase, IEnumerable listA, IEnumerable listB, Func getKey) => + overloadCase switch { - case CustomResult: - return listA.FullGroupJoin(listB, getKey, getKey, ValueTuple.Create); - case TupleResult: - return listA.FullGroupJoin(listB, getKey, getKey); - default: - throw new ArgumentOutOfRangeException(nameof(overloadCase)); - } - } + CustomResult => listA.FullGroupJoin(listB, getKey, getKey, ValueTuple.Create, comparer: null), + TupleResult => listA.FullGroupJoin(listB, getKey, getKey), + _ => throw new ArgumentOutOfRangeException(nameof(overloadCase)) + }; } } diff --git a/MoreLinq.Test/FullJoinTest.cs b/MoreLinq.Test/FullJoinTest.cs index c460a3264..f648727de 100644 --- a/MoreLinq.Test/FullJoinTest.cs +++ b/MoreLinq.Test/FullJoinTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2015 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. @@ -32,11 +32,12 @@ public void FullJoinWithHomogeneousSequencesIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.FullJoin(ys, e => e, BreakingFunc.Of(), BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -45,12 +46,13 @@ public void FullJoinWithHomogeneousSequencesWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.FullJoin(ys, e => e, BreakingFunc.Of(), BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] @@ -59,11 +61,12 @@ public void FullJoinIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.FullJoin(ys, x => x, y => y.GetHashCode(), BreakingFunc.Of(), BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -72,12 +75,13 @@ public void FullJoinWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.FullJoin(ys, x => x, y => y.GetHashCode(), BreakingFunc.Of(), BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] diff --git a/MoreLinq.Test/FuncModule.cs b/MoreLinq.Test/FuncModule.cs new file mode 100644 index 000000000..75dbe6f08 --- /dev/null +++ b/MoreLinq.Test/FuncModule.cs @@ -0,0 +1,43 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2020 Atif Aziz. 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.Test +{ + using System; + + // This type is designed to be imported statically. + // + // Its members enable replacing explicit instantiations of `Func<...>`, + // as in: + // + // new Func((a, b) => a + b) + // + // with the shorter version: + // + // Func((string a, object b) => a + b) + // + // The `new` is no longer required and the return type can be omitted + // as it can be inferred through the type of the lambda expression. + + static class FuncModule + { + public static Func Func(Func f) => f; + public static Func Func(Func f) => f; + public static Func Func(Func f) => f; + public static Func Func(Func f) => f; + } +} diff --git a/MoreLinq.Test/GenerateTest.cs b/MoreLinq.Test/GenerateTest.cs index 58657fc26..a9e60a8bb 100644 --- a/MoreLinq.Test/GenerateTest.cs +++ b/MoreLinq.Test/GenerateTest.cs @@ -61,7 +61,7 @@ public void GenerateByIndexIsLazy() [Test] public void GenerateByIndex() { - var sequence = MoreEnumerable.GenerateByIndex(x => x.ToString()).Take(3); + var sequence = MoreEnumerable.GenerateByIndex(x => x.ToInvariantString()).Take(3); sequence.AssertSequenceEqual("0", "1", "2"); } } diff --git a/MoreLinq.Test/GroupAdjacentTest.cs b/MoreLinq.Test/GroupAdjacentTest.cs index a68f099bb..dbc1e8762 100644 --- a/MoreLinq.Test/GroupAdjacentTest.cs +++ b/MoreLinq.Test/GroupAdjacentTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2012 Atif Aziz. 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. @@ -55,33 +55,31 @@ public void GroupAdjacentSourceSequence() const string ten = "ten"; var source = new[] { one, two, three, four, five, six, seven, eight, nine, ten }; + var groupings = source.GroupAdjacent(s => s.Length); - using (var reader = groupings.Read()) - { - AssertGrouping(reader, 3, one, two); - AssertGrouping(reader, 5, three); - AssertGrouping(reader, 4, four, five); - AssertGrouping(reader, 3, six); - AssertGrouping(reader, 5, seven, eight); - AssertGrouping(reader, 4, nine); - AssertGrouping(reader, 3, ten); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertGrouping(reader, 3, one, two); + AssertGrouping(reader, 5, three); + AssertGrouping(reader, 4, four, five); + AssertGrouping(reader, 3, six); + AssertGrouping(reader, 5, seven, eight); + AssertGrouping(reader, 4, nine); + AssertGrouping(reader, 3, ten); + reader.ReadEnd(); } [Test] public void GroupAdjacentSourceSequenceComparer() { var source = new[] { "foo", "FOO", "Foo", "bar", "BAR", "Bar" }; + var groupings = source.GroupAdjacent(s => s, StringComparer.OrdinalIgnoreCase); - using (var reader = groupings.Read()) - { - AssertGrouping(reader, "foo", "foo", "FOO", "Foo"); - AssertGrouping(reader, "bar", "bar", "BAR", "Bar"); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertGrouping(reader, "foo", "foo", "FOO", "Foo"); + AssertGrouping(reader, "bar", "bar", "BAR", "Bar"); + reader.ReadEnd(); } [Test] @@ -105,14 +103,12 @@ public void GroupAdjacentSourceSequenceElementSelector() var groupings = source.GroupAdjacent(e => e.Month, e => e.Value * 2); - using (var reader = groupings.Read()) - { - AssertGrouping(reader, 1, 123 * 2, 456 * 2, 789 * 2); - AssertGrouping(reader, 2, 987 * 2, 654 * 2, 321 * 2); - AssertGrouping(reader, 3, 789 * 2, 456 * 2, 123 * 2); - AssertGrouping(reader, 1, 123 * 2, 456 * 2, 781 * 2); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertGrouping(reader, 1, 123 * 2, 456 * 2, 789 * 2); + AssertGrouping(reader, 2, 987 * 2, 654 * 2, 321 * 2); + AssertGrouping(reader, 3, 789 * 2, 456 * 2, 123 * 2); + AssertGrouping(reader, 1, 123 * 2, 456 * 2, 781 * 2); + reader.ReadEnd(); } [Test] @@ -136,14 +132,12 @@ public void GroupAdjacentSourceSequenceElementSelectorComparer() var groupings = source.GroupAdjacent(e => e.Month, e => e.Value * 2, StringComparer.OrdinalIgnoreCase); - using (var reader = groupings.Read()) - { - AssertGrouping(reader, "jan", 123 * 2, 456 * 2, 789 * 2); - AssertGrouping(reader, "feb", 987 * 2, 654 * 2, 321 * 2); - AssertGrouping(reader, "mar", 789 * 2, 456 * 2, 123 * 2); - AssertGrouping(reader, "jan", 123 * 2, 456 * 2, 781 * 2); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertGrouping(reader, "jan", 123 * 2, 456 * 2, 789 * 2); + AssertGrouping(reader, "feb", 987 * 2, 654 * 2, 321 * 2); + AssertGrouping(reader, "mar", 789 * 2, 456 * 2, 123 * 2); + AssertGrouping(reader, "jan", 123 * 2, 456 * 2, 781 * 2); + reader.ReadEnd(); } [Test] @@ -165,16 +159,14 @@ public void GroupAdjacentSourceSequenceResultSelector() new { Month = 1, Value = 781 }, }; - var groupings = source.GroupAdjacent(e => e.Month, (key, group) => group.Sum(v => v.Value)); + var groupings = source.GroupAdjacent(e => e.Month, (_, group) => group.Sum(v => v.Value)); - using (var reader = groupings.Read()) - { - AssertResult(reader, 123 + 456 + 789); - AssertResult(reader, 987 + 654 + 321); - AssertResult(reader, 789 + 456 + 123); - AssertResult(reader, 123 + 456 + 781); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertResult(reader, 123 + 456 + 789); + AssertResult(reader, 987 + 654 + 321); + AssertResult(reader, 789 + 456 + 123); + AssertResult(reader, 123 + 456 + 781); + reader.ReadEnd(); } [Test] @@ -196,16 +188,38 @@ public void GroupAdjacentSourceSequenceResultSelectorComparer() new { Month = "JAN", Value = 781 }, }; - var groupings = source.GroupAdjacent(e => e.Month, (key, group) => group.Sum(v => v.Value), StringComparer.OrdinalIgnoreCase); + var groupings = source.GroupAdjacent(e => e.Month, (_, group) => group.Sum(v => v.Value), StringComparer.OrdinalIgnoreCase); - using (var reader = groupings.Read()) - { - AssertResult(reader, 123 + 456 + 789); - AssertResult(reader, 987 + 654 + 321); - AssertResult(reader, 789 + 456 + 123); - AssertResult(reader, 123 + 456 + 781); - reader.ReadEnd(); - } + using var reader = groupings.Read(); + AssertResult(reader, 123 + 456 + 789); + AssertResult(reader, 987 + 654 + 321); + AssertResult(reader, 789 + 456 + 123); + AssertResult(reader, 123 + 456 + 781); + reader.ReadEnd(); + } + + [Test] + public void GroupAdjacentSourceSequenceWithSomeNullKeys() + { + var groupings = + Enumerable.Range(1, 5) + .SelectMany(x => Enumerable.Repeat((int?)x, x).Append(null)) + .GroupAdjacent(x => x); + + int?[] aNull = { null }; + + using var reader = groupings.Read(); + AssertGrouping(reader, 1, 1); + AssertGrouping(reader, null, aNull); + AssertGrouping(reader, 2, 2, 2); + AssertGrouping(reader, null, aNull); + AssertGrouping(reader, 3, 3, 3, 3); + AssertGrouping(reader, null, aNull); + AssertGrouping(reader, 4, 4, 4, 4, 4); + AssertGrouping(reader, null, aNull); + AssertGrouping(reader, 5, 5, 5, 5, 5, 5); + AssertGrouping(reader, null, aNull); + reader.ReadEnd(); } static void AssertGrouping(SequenceReader> reader, @@ -221,7 +235,7 @@ static void AssertResult(SequenceReader reader, TElement ele { var result = reader.Read(); Assert.That(result, Is.Not.Null); - Assert.AreEqual(element, result); + Assert.That(result, Is.EqualTo(element)); } } } diff --git a/MoreLinq.Test/IndexByTest.cs b/MoreLinq.Test/IndexByTest.cs index 2a02d7474..8b71ea268 100644 --- a/MoreLinq.Test/IndexByTest.cs +++ b/MoreLinq.Test/IndexByTest.cs @@ -81,18 +81,17 @@ public void IndexByWithSomeNullKeys() var source = new[] { "foo", null, "bar", "baz", null, null, "baz", "bar", null, "foo" }; var result = source.IndexBy(c => c); - const string @null = null; // type inference happiness result.AssertSequenceEqual( - KeyValuePair.Create(0, "foo"), - KeyValuePair.Create(0, @null), - KeyValuePair.Create(0, "bar"), - KeyValuePair.Create(0, "baz"), - KeyValuePair.Create(1, @null), - KeyValuePair.Create(2, @null), - KeyValuePair.Create(1, "baz"), - KeyValuePair.Create(1, "bar"), - KeyValuePair.Create(3, @null), - KeyValuePair.Create(1, "foo")); + KeyValuePair.Create(0, (string?)"foo"), + KeyValuePair.Create(0, (string?)null), + KeyValuePair.Create(0, (string?)"bar"), + KeyValuePair.Create(0, (string?)"baz"), + KeyValuePair.Create(1, (string?)null), + KeyValuePair.Create(2, (string?)null), + KeyValuePair.Create(1, (string?)"baz"), + KeyValuePair.Create(1, (string?)"bar"), + KeyValuePair.Create(3, (string?)null), + KeyValuePair.Create(1, (string?)"foo")); } [Test] @@ -116,8 +115,8 @@ public void IndexBytDoesNotIterateUnnecessaryElements() KeyValuePair.Create(1, "bob" ), KeyValuePair.Create(0, "davi" )); - Assert.Throws(() => - result.ElementAt(5)); + Assert.That(() => result.ElementAt(5), + Throws.TypeOf()); } } } diff --git a/MoreLinq.Test/IndexTest.cs b/MoreLinq.Test/IndexTest.cs index ecc200e1d..6b57ee2c3 100644 --- a/MoreLinq.Test/IndexTest.cs +++ b/MoreLinq.Test/IndexTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2012 Atif Aziz. 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. diff --git a/MoreLinq.Test/InsertTest.cs b/MoreLinq.Test/InsertTest.cs index 664f2fbc4..7f9af900f 100644 --- a/MoreLinq.Test/InsertTest.cs +++ b/MoreLinq.Test/InsertTest.cs @@ -25,8 +25,8 @@ public class InsertTest [Test] public void InsertWithNegativeIndex() { - AssertThrowsArgument.OutOfRangeException("index", () => - Enumerable.Range(1, 10).Insert(new[] { 97, 98, 99 }, -1)); + Assert.That(() => Enumerable.Range(1, 10).Insert(new[] { 97, 98, 99 }, -1), + Throws.ArgumentOutOfRangeException("index")); } [TestCase(7)] @@ -37,15 +37,13 @@ public void InsertWithIndexGreaterThanSourceLengthMaterialized(int count) var seq1 = Enumerable.Range(0, count).ToList(); var seq2 = new[] { 97, 98, 99 }; - using (var test1 = seq1.AsTestingSequence()) - using (var test2 = seq2.AsTestingSequence()) - { - var result = test1.Insert(test2, count + 1); + using var test1 = seq1.AsTestingSequence(); + using var test2 = seq2.AsTestingSequence(); - AssertThrowsArgument.OutOfRangeException("index", () => - result.ForEach((e, index) => - Assert.That(e, Is.EqualTo(seq1[index])))); - } + var result = test1.Insert(test2, count + 1); + + Assert.That(() => result.ForEach((e, index) => Assert.That(e, Is.EqualTo(seq1[index]))), + Throws.ArgumentOutOfRangeException("index")); } [TestCase(7)] @@ -56,13 +54,12 @@ public void InsertWithIndexGreaterThanSourceLengthLazy(int count) var seq1 = Enumerable.Range(0, count); var seq2 = new[] { 97, 98, 99 }; - using (var test1 = seq1.AsTestingSequence()) - using (var test2 = seq2.AsTestingSequence()) - { - var result = test1.Insert(test2, count + 1).Take(count); + using var test1 = seq1.AsTestingSequence(); + using var test2 = seq2.AsTestingSequence(); + + var result = test1.Insert(test2, count + 1).Take(count); - Assert.That(seq1, Is.EqualTo(result)); - } + Assert.That(seq1, Is.EqualTo(result)); } [TestCase(3, 0)] @@ -74,13 +71,13 @@ public void Insert(int count, int index) var seq1 = Enumerable.Range(1, count); var seq2 = new[] { 97, 98, 99 }; - using (var test1 = seq1.AsTestingSequence()) - using (var test2 = seq2.AsTestingSequence()) - { - var result = test1.Insert(test2, index); - var expectations = seq1.Take(index).Concat(seq2).Concat(seq1.Skip(index)); - Assert.That(result, Is.EqualTo(expectations)); - } + using var test1 = seq1.AsTestingSequence(); + using var test2 = seq2.AsTestingSequence(); + + var result = test1.Insert(test2, index); + + var expectations = seq1.Take(index).Concat(seq2).Concat(seq1.Skip(index)); + Assert.That(result, Is.EqualTo(expectations)); } [Test] diff --git a/MoreLinq.Test/InterleaveTest.cs b/MoreLinq.Test/InterleaveTest.cs index b95e50517..945dc975f 100644 --- a/MoreLinq.Test/InterleaveTest.cs +++ b/MoreLinq.Test/InterleaveTest.cs @@ -1,6 +1,23 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { - using System; + using System.Collections.Generic; using NUnit.Framework; /// @@ -18,6 +35,76 @@ public void TestInterleaveIsLazy() new BreakingSequence().Interleave(new BreakingSequence()); } + /// + /// Verify that interleaving disposes those enumerators that it managed + /// to open successfully + /// + [Test] + public void TestInterleaveDisposesOnErrorAtGetEnumerator() + { + using var sequenceA = TestingSequence.Of(); + var sequenceB = new BreakingSequence(); + + // Expected and thrown by BreakingSequence + Assert.That(() => sequenceA.Interleave(sequenceB).Consume(), + Throws.BreakException); + } + + /// + /// Verify that Interleave early throw ArgumentNullException when an element + /// of otherSequences is null. + /// + [Test] + public void TestInterleaveEarlyThrowOnNullElementInOtherSequences() + { + var sequenceA = Enumerable.Range(1, 1); + var otherSequences = new IEnumerable[] { null! }; + + Assert.That(() => sequenceA.Interleave(otherSequences), + Throws.ArgumentNullException("otherSequences")); + } + + /// + /// Verify that interleaving disposes those enumerators that it managed + /// to open successfully + /// + [Test] + public void TestInterleaveDisposesOnErrorAtMoveNext() + { + using var sequenceA = TestingSequence.Of(); + using var sequenceB = MoreEnumerable.From(() => throw new TestException()).AsTestingSequence(); + + // Expected and thrown by sequenceB + Assert.That(() => sequenceA.Interleave(sequenceB).Consume(), + Throws.TypeOf()); + } + + /// + /// Verify that interleaving do not call enumerable GetEnumerator method eagerly + /// + [Test] + public void TestInterleaveDoNotCallGetEnumeratorEagerly() + { + using var sequenceA = TestingSequence.Of(1); + var sequenceB = new BreakingSequence(); + + sequenceA.Interleave(sequenceB).Take(1).Consume(); + } + + /// + /// Verify that interleaving do not call enumerators MoveNext method eagerly + /// + [Test] + public void TestInterleaveDoNoCallMoveNextEagerly() + { + using var sequenceA = TestingSequence.Of(1); + using var sequenceB = MoreEnumerable.From(() => throw new TestException()) + .AsTestingSequence(); + var result = sequenceA.Interleave(sequenceB).Take(1); + + Assert.That(() => result.Consume(), Throws.Nothing); + } + /// /// Verify that interleaving disposes those enumerators that it managed /// to open successfully @@ -25,11 +112,27 @@ public void TestInterleaveIsLazy() [Test] public void TestInterleaveDisposesOnError() { - using (var sequenceA = TestingSequence.Of()) - { - Assert.Throws(() => // Expected and thrown by BreakingSequence - sequenceA.Interleave(new BreakingSequence()).Consume()); - } + using var sequenceA = TestingSequence.Of(); + + Assert.That(() => sequenceA.Interleave(new BreakingSequence()).Consume(), + Throws.BreakException); // Expected and thrown by BreakingSequence + } + + /// + /// Verify that, in case of partial enumeration, interleaving disposes those + /// enumerators that it managed to open successfully + /// + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestInterleaveDisposesOnPartialEnumeration(int count) + { + using var sequenceA = TestingSequence.Of(1); + using var sequenceB = TestingSequence.Of(2); + using var sequenceC = TestingSequence.Of(3); + + sequenceA.Interleave(sequenceB, sequenceC).Take(count).Consume(); } /// @@ -119,14 +222,13 @@ public void TestInterleaveDisposesAllIterators() { const int count = 10; - using (var sequenceA = Enumerable.Range(1, count).AsTestingSequence()) - using (var sequenceB = Enumerable.Range(1, count - 1).AsTestingSequence()) - using (var sequenceC = Enumerable.Range(1, count - 5).AsTestingSequence()) - using (var sequenceD = Enumerable.Range(1, 0).AsTestingSequence()) - { - sequenceA.Interleave(sequenceB, sequenceC, sequenceD) - .Consume(); - } + using var sequenceA = Enumerable.Range(1, count).AsTestingSequence(); + using var sequenceB = Enumerable.Range(1, count - 1).AsTestingSequence(); + using var sequenceC = Enumerable.Range(1, count - 5).AsTestingSequence(); + using var sequenceD = Enumerable.Range(1, 0).AsTestingSequence(); + + sequenceA.Interleave(sequenceB, sequenceC, sequenceD) + .Consume(); } } } diff --git a/MoreLinq.Test/KeyValuePair.cs b/MoreLinq.Test/KeyValuePair.cs index a1f79affd..82493f0d0 100644 --- a/MoreLinq.Test/KeyValuePair.cs +++ b/MoreLinq.Test/KeyValuePair.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2012 Atif Aziz. 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. @@ -21,7 +21,6 @@ namespace MoreLinq.Test static class KeyValuePair { - public static KeyValuePair Create(TKey key, TValue value) => - new KeyValuePair(key, value); + public static KeyValuePair Create(TKey key, TValue value) => new(key, value); } } diff --git a/MoreLinq.Test/LagTest.cs b/MoreLinq.Test/LagTest.cs index 01147e60d..3a1c0fdfd 100644 --- a/MoreLinq.Test/LagTest.cs +++ b/MoreLinq.Test/LagTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -24,8 +41,8 @@ public void TestLagIsLazy() [Test] public void TestLagNegativeOffsetException() { - AssertThrowsArgument.OutOfRangeException("offset",() => - Enumerable.Repeat(1, 10).Lag(-10, (val, lagVal) => val)); + Assert.That(() => Enumerable.Repeat(1, 10).Lag(-10, (val, _) => val), + Throws.ArgumentOutOfRangeException("offset")); } /// @@ -34,8 +51,8 @@ public void TestLagNegativeOffsetException() [Test] public void TestLagZeroOffset() { - AssertThrowsArgument.OutOfRangeException("offset", () => - Enumerable.Range(1, 10).Lag(0, (val, lagVal) => val + lagVal)); + Assert.That(() => Enumerable.Range(1, 10).Lag(0, (val, lagVal) => val + lagVal), + Throws.ArgumentOutOfRangeException("offset")); } /// @@ -48,9 +65,9 @@ public void TestLagExplicitDefaultValue() const int lagBy = 10; const int lagDefault = -1; var sequence = Enumerable.Range(1, count); - var result = sequence.Lag(lagBy, lagDefault, (val, lagVal) => lagVal); + var result = sequence.Lag(lagBy, lagDefault, (_, lagVal) => lagVal); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result.Take(lagBy), Is.EqualTo(Enumerable.Repeat(lagDefault, lagBy))); } @@ -63,9 +80,9 @@ public void TestLagImplicitDefaultValue() const int count = 100; const int lagBy = 10; var sequence = Enumerable.Range(1, count); - var result = sequence.Lag(lagBy, (val, lagVal) => lagVal); + var result = sequence.Lag(lagBy, (_, lagVal) => lagVal); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result.Take(lagBy), Is.EqualTo(Enumerable.Repeat(default(int), lagBy))); } @@ -78,9 +95,9 @@ public void TestLagOffsetGreaterThanSequenceLength() { const int count = 100; var sequence = Enumerable.Range(1, count); - var result = sequence.Lag(count + 1, (a, b) => a); + var result = sequence.Lag(count + 1, (a, _) => a); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result, Is.EqualTo(sequence)); } @@ -95,8 +112,8 @@ public void TestLagPassesCorrectLagValueOffsetBy1() var sequence = Enumerable.Range(1, count); var result = sequence.Lag(1, (a, b) => new { A = a, B = b }); - Assert.AreEqual(count, result.Count()); - Assert.IsTrue(result.All(x => x.B == (x.A - 1))); + Assert.That(result.Count(), Is.EqualTo(count)); + Assert.That(result.All(x => x.B == (x.A - 1)), Is.True); } /// @@ -110,9 +127,34 @@ public void TestLagPassesCorrectLagValuesOffsetBy2() var sequence = Enumerable.Range(1, count); var result = sequence.Lag(2, (a, b) => new { A = a, B = b }); - Assert.AreEqual(count, result.Count()); - Assert.IsTrue(result.Skip(2).All(x => x.B == (x.A - 2))); - Assert.IsTrue(result.Take(2).All(x => (x.A - x.B) == x.A)); + Assert.That(result.Count(), Is.EqualTo(count)); + Assert.That(result.Skip(2).All(x => x.B == (x.A - 2)), Is.True); + Assert.That(result.Take(2).All(x => (x.A - x.B) == x.A), Is.True); + } + + [Test] + public void TestLagWithNullableReferences() + { + var words = new[] { "foo", "bar", "baz", "qux" }; + var result = words.Lag(2, (a, b) => new { A = a, B = b }); + result.AssertSequenceEqual( + new { A = "foo", B = (string?)null }, + new { A = "bar", B = (string?)null }, + new { A = "baz", B = (string?)"foo" }, + new { A = "qux", B = (string?)"bar" }); + } + + [Test] + public void TestLagWithNonNullableReferences() + { + var words = new[] { "foo", "bar", "baz", "qux" }; + var empty = string.Empty; + var result = words.Lag(2, empty, (a, b) => new { A = a, B = b }); + result.AssertSequenceEqual( + new { A = "foo", B = empty }, + new { A = "bar", B = empty }, + new { A = "baz", B = "foo" }, + new { A = "qux", B = "bar" }); } } } diff --git a/MoreLinq.Test/LeadTest.cs b/MoreLinq.Test/LeadTest.cs index c2913cfd3..8b2e245b8 100644 --- a/MoreLinq.Test/LeadTest.cs +++ b/MoreLinq.Test/LeadTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -24,8 +41,8 @@ public void TestLeadIsLazy() [Test] public void TestLeadNegativeOffset() { - AssertThrowsArgument.OutOfRangeException("offset", () => - Enumerable.Range(1, 100).Lead(-5, (val, leadVal) => val + leadVal)); + Assert.That(() => Enumerable.Range(1, 100).Lead(-5, (val, leadVal) => val + leadVal), + Throws.ArgumentOutOfRangeException("offset")); } /// @@ -34,8 +51,8 @@ public void TestLeadNegativeOffset() [Test] public void TestLeadZeroOffset() { - AssertThrowsArgument.OutOfRangeException("offset", () => - Enumerable.Range(1, 100).Lead(0, (val, leadVal) => val + leadVal)); + Assert.That(() => Enumerable.Range(1, 100).Lead(0, (val, leadVal) => val + leadVal), + Throws.ArgumentOutOfRangeException("offset")); } /// @@ -48,14 +65,14 @@ public void TestLeadExplicitDefaultValue() const int leadBy = 10; const int leadDefault = -1; var sequence = Enumerable.Range(1, count); - var result = sequence.Lead(leadBy, leadDefault, (val, leadVal) => leadVal); + var result = sequence.Lead(leadBy, leadDefault, (_, leadVal) => leadVal); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result.Skip(count - leadBy), Is.EqualTo(Enumerable.Repeat(leadDefault, leadBy))); } /// - /// Verify that Lead() willuse default(T) if a specific default value is not supplied for the lead value. + /// Verify that Lead() will use default(T) if a specific default value is not supplied for the lead value. /// [Test] public void TestLeadImplicitDefaultValue() @@ -63,9 +80,9 @@ public void TestLeadImplicitDefaultValue() const int count = 100; const int leadBy = 10; var sequence = Enumerable.Range(1, count); - var result = sequence.Lead(leadBy, (val, leadVal) => leadVal); + var result = sequence.Lead(leadBy, (_, leadVal) => leadVal); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result.Skip(count - leadBy), Is.EqualTo(Enumerable.Repeat(default(int), leadBy))); } @@ -81,7 +98,7 @@ public void TestLeadOffsetGreaterThanSequenceLength() var sequence = Enumerable.Range(1, count); var result = sequence.Lead(count + 1, leadDefault, (val, leadVal) => new { A = val, B = leadVal }); - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); Assert.That(result, Is.EqualTo(sequence.Select(x => new { A = x, B = leadDefault }))); } @@ -96,8 +113,8 @@ public void TestLeadPassesCorrectValueOffsetBy1() var sequence = Enumerable.Range(1, count); var result = sequence.Lead(1, count + 1, (val, leadVal) => new { A = val, B = leadVal }); - Assert.AreEqual(count, result.Count()); - Assert.IsTrue(result.All(x => x.B == (x.A + 1))); + Assert.That(result.Count(), Is.EqualTo(count)); + Assert.That(result.All(x => x.B == (x.A + 1)), Is.True); } /// @@ -112,9 +129,34 @@ public void TestLeadPassesCorrectValueOffsetBy2() var sequence = Enumerable.Range(1, count); var result = sequence.Lead(2, leadDefault, (val, leadVal) => new { A = val, B = leadVal }); - Assert.AreEqual(count, result.Count()); - Assert.IsTrue(result.Take(count - 2).All(x => x.B == (x.A + 2))); - Assert.IsTrue(result.Skip(count - 2).All(x => x.B == leadDefault && (x.A == count || x.A == count - 1))); + Assert.That(result.Count(), Is.EqualTo(count)); + Assert.That(result.Take(count - 2).All(x => x.B == (x.A + 2)), Is.True); + Assert.That(result.Skip(count - 2).All(x => x.B == leadDefault && x.A is count or count - 1), Is.True); + } + + [Test] + public void TestLagWithNullableReferences() + { + var words = new[] { "foo", "bar", "baz", "qux" }; + var result = words.Lead(2, (a, b) => new { A = a, B = b }); + result.AssertSequenceEqual( + new { A = "foo", B = (string?)"baz" }, + new { A = "bar", B = (string?)"qux" }, + new { A = "baz", B = (string?)null }, + new { A = "qux", B = (string?)null }); + } + + [Test] + public void TestLagWithNonNullableReferences() + { + var words = new[] { "foo", "bar", "baz", "qux" }; + var empty = string.Empty; + var result = words.Lead(2, empty, (a, b) => new { A = a, B = b }); + result.AssertSequenceEqual( + new { A = "foo", B = "baz" }, + new { A = "bar", B = "qux" }, + new { A = "baz", B = empty }, + new { A = "qux", B = empty }); } } } diff --git a/MoreLinq.Test/LeftJoinTest.cs b/MoreLinq.Test/LeftJoinTest.cs index 8884839e2..76214fb8d 100644 --- a/MoreLinq.Test/LeftJoinTest.cs +++ b/MoreLinq.Test/LeftJoinTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2015 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. @@ -32,10 +32,11 @@ public void LeftJoinWithHomogeneousSequencesIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.LeftJoin(ys, e => e, BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -44,11 +45,12 @@ public void LeftJoinWithHomogeneousSequencesWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.LeftJoin(ys, e => e, BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] @@ -57,10 +59,11 @@ public void LeftJoinIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.LeftJoin(ys, x => x, y => y.GetHashCode(), BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -69,11 +72,12 @@ public void LeftJoinWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.LeftJoin(ys, x => x, y => y.GetHashCode(), BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] diff --git a/MoreLinq.Test/MaxByTest.cs b/MoreLinq.Test/MaxByTest.cs index 0f6c19616..0abc1beb6 100644 --- a/MoreLinq.Test/MaxByTest.cs +++ b/MoreLinq.Test/MaxByTest.cs @@ -31,15 +31,15 @@ public void MaxByIsLazy() [Test] public void MaxByReturnsMaxima() { - Assert.AreEqual(new[] { "hello", "world" }, - SampleData.Strings.MaxBy(x => x.Length)); + Assert.That(SampleData.Strings.MaxBy(x => x.Length), + Is.EqualTo(new[] { "hello", "world" })); } [Test] public void MaxByNullComparer() { - Assert.AreEqual(SampleData.Strings.MaxBy(x => x.Length), - SampleData.Strings.MaxBy(x => x.Length, null)); + Assert.That(SampleData.Strings.MaxBy(x => x.Length, null), + Is.EqualTo(SampleData.Strings.MaxBy(x => x.Length))); } [Test] @@ -51,65 +51,215 @@ public void MaxByEmptySequence() [Test] public void MaxByWithNaturalComparer() { - Assert.AreEqual(new[] { "az" }, SampleData.Strings.MaxBy(x => x[1])); + Assert.That(SampleData.Strings.MaxBy(x => x[1]), Is.EqualTo(new[] { "az" })); } [Test] public void MaxByWithComparer() { - Assert.AreEqual(new[] { "aa" }, SampleData.Strings.MaxBy(x => x[1], SampleData.ReverseCharComparer)); + Assert.That(SampleData.Strings.MaxBy(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "aa" })); } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "hello" })] - [TestCase(2, ExpectedResult = new[] { "hello", "world" })] - [TestCase(3, ExpectedResult = new[] { "hello", "world" })] - public string[] MaxByTakeReturnsMaxima(int count) + public class First { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MaxBy(s => s.Length).Take(count).ToArray(); + [Test] + public void ReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.First(maxima), Is.EqualTo("hello")); + } + + [Test] + public void WithComparerReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.First(maxima), Is.EqualTo("ax")); + } + + [Test] + public void WithEmptySourceThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => + MoreEnumerable.First(strings.MaxBy(s => s.Length)), + Throws.InvalidOperationException); + } + + [Test] + public void WithEmptySourceWithComparerThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => + MoreEnumerable.First(strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer)), + Throws.InvalidOperationException); + } } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "world" })] - [TestCase(2, ExpectedResult = new[] { "hello", "world" })] - [TestCase(3, ExpectedResult = new[] { "hello", "world" })] - public string[] MaxByTakeLastReturnsMaxima(int count) + public class FirstOrDefault { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MaxBy(s => s.Length).TakeLast(count).ToArray(); + [Test] + public void ReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.EqualTo("hello")); + } + + [Test] + public void WithComparerReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.EqualTo("ax")); + } + + [Test] + public void WithEmptySourceReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.Null); + } + + [Test] + public void WithEmptySourceWithComparerReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.FirstOrDefault(maxima), Is.Null); + } + } + + public class Last + { + [Test] + public void ReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.Last(maxima), Is.EqualTo("world")); + } + + [Test] + public void WithComparerReturnsMaximumPerComparer() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.Last(maxima), Is.EqualTo("az")); + } + + [Test] + public void WithEmptySourceThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => + MoreEnumerable.Last(strings.MaxBy(s => s.Length)), + Throws.InvalidOperationException); + } + + [Test] + public void WithEmptySourceWithComparerThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => + MoreEnumerable.Last(strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer)), + Throws.InvalidOperationException); + } + } + + public class LastOrDefault + { + [Test] + public void ReturnsMaximum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.EqualTo("world")); + } + + [Test] + public void WithComparerReturnsMaximumPerComparer() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.EqualTo("az")); + } + + [Test] + public void WithEmptySourceReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length); + Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.Null); + } + + [Test] + public void WithEmptySourceWithComparerReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var maxima = strings.MaxBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.LastOrDefault(maxima), Is.Null); + } } - [TestCase(0, 0, ExpectedResult = new string[0] )] - [TestCase(3, 1, ExpectedResult = new[] { "aa" })] - [TestCase(1, 0, ExpectedResult = new[] { "ax" })] - [TestCase(2, 0, ExpectedResult = new[] { "ax", "aa" })] - [TestCase(3, 0, ExpectedResult = new[] { "ax", "aa", "ab" })] - [TestCase(4, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay" })] - [TestCase(5, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - [TestCase(6, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - public string[] MaxByTakeWithComparerReturnsMaxima(int count, int index) + public class Take { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MaxBy(s => s[index], SampleData.ReverseCharComparer) + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "hello" })] + [TestCase(2, ExpectedResult = new[] { "hello", "world" })] + [TestCase(3, ExpectedResult = new[] { "hello", "world" })] + public string[] ReturnsMaxima(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MaxBy(s => s.Length).Take(count).ToArray(); + } + + [TestCase(0, 0, ExpectedResult = new string[0] )] + [TestCase(3, 1, ExpectedResult = new[] { "aa" })] + [TestCase(1, 0, ExpectedResult = new[] { "ax" })] + [TestCase(2, 0, ExpectedResult = new[] { "ax", "aa" })] + [TestCase(3, 0, ExpectedResult = new[] { "ax", "aa", "ab" })] + [TestCase(4, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay" })] + [TestCase(5, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + [TestCase(6, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + public string[] WithComparerReturnsMaximaPerComparer(int count, int index) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MaxBy(s => s[index], Comparable.DescendingOrderComparer) .Take(count) .ToArray(); + } } - [TestCase(0, 0, ExpectedResult = new string[0] )] - [TestCase(3, 1, ExpectedResult = new[] { "aa" })] - [TestCase(1, 0, ExpectedResult = new[] { "az" })] - [TestCase(2, 0, ExpectedResult = new[] { "ay", "az" })] - [TestCase(3, 0, ExpectedResult = new[] { "ab", "ay", "az" })] - [TestCase(4, 0, ExpectedResult = new[] { "aa", "ab", "ay", "az" })] - [TestCase(5, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - [TestCase(6, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - public string[] MaxByTakeLastWithComparerReturnsMaxima(int count, int index) + public class TakeLast { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MaxBy(s => s[index], SampleData.ReverseCharComparer) + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "world" })] + [TestCase(2, ExpectedResult = new[] { "hello", "world" })] + [TestCase(3, ExpectedResult = new[] { "hello", "world" })] + public string[] TakeLastReturnsMaxima(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MaxBy(s => s.Length).TakeLast(count).ToArray(); + } + + [TestCase(0, 0, ExpectedResult = new string[0] )] + [TestCase(3, 1, ExpectedResult = new[] { "aa" })] + [TestCase(1, 0, ExpectedResult = new[] { "az" })] + [TestCase(2, 0, ExpectedResult = new[] { "ay", "az" })] + [TestCase(3, 0, ExpectedResult = new[] { "ab", "ay", "az" })] + [TestCase(4, 0, ExpectedResult = new[] { "aa", "ab", "ay", "az" })] + [TestCase(5, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + [TestCase(6, 0, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + public string[] WithComparerReturnsMaximaPerComparer(int count, int index) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MaxBy(s => s[index], Comparable.DescendingOrderComparer) .TakeLast(count) .ToArray(); + } } } } diff --git a/MoreLinq.Test/MemoizeTest.cs b/MoreLinq.Test/MemoizeTest.cs index 4d93b2159..6cc0b6565 100644 --- a/MoreLinq.Test/MemoizeTest.cs +++ b/MoreLinq.Test/MemoizeTest.cs @@ -15,10 +15,13 @@ // limitations under the License. #endregion +// Following warnings are disabled due to false negatives: +#pragma warning disable NUnit2040 // Non-reference types for SameAs constraint +#pragma warning disable NUnit2020 // Incompatible types for SameAs constraint + namespace MoreLinq.Test { using System; - using System.Collections; using System.Collections.Generic; using System.Threading; using Delegate = Delegating.Delegate; @@ -39,7 +42,7 @@ public void MemoizeReturningExpectedElementsWhenUsedAtInnerForEach() flowArray.AssertSequenceEqual(flowBuffer); - IEnumerable InnerForEach(IEnumerable source) + static IEnumerable InnerForEach(IEnumerable source) { var firstVisitAtInnerLoopDone = false; @@ -53,7 +56,7 @@ IEnumerable InnerForEach(IEnumerable source) { yield return i; - if (i == 3 || i == 7) + if (i is 3 or 7) { //consume 1-3 already cached //add 4-5 to cache (so go to outer loop) @@ -113,59 +116,61 @@ public void MemoizeWithInMemoryCollection(SourceKind sourceKind) public void MemoizeEnumeratesOnlyOnce() { const int count = 10; - using (var ts = Enumerable.Range(1, count).AsTestingSequence()) + using var ts = Enumerable.Range(1, count).AsTestingSequence(); + + var memoized = ts.Memoize(); + + using ((IDisposable)memoized) { - var memoized = ts.Memoize(); - using ((IDisposable) memoized) - { - Assert.That(memoized.ToList().Count, Is.EqualTo(count)); - Assert.That(memoized.ToList().Count, Is.EqualTo(count)); - } + Assert.That(memoized.ToList().Count, Is.EqualTo(count)); + Assert.That(memoized.ToList().Count, Is.EqualTo(count)); } } [Test] public void MemoizeDoesNotDisposeOnEarlyExitByDefault() { - Assert.Throws(() => + static void Act() { - using (var xs = new[] { 1, 2 }.AsTestingSequence()) - { - xs.Memoize().Take(1).Consume(); - xs.Memoize().Take(1).Consume(); - } - }); + using var xs = new[] { 1, 2 }.AsTestingSequence(); + + xs.Memoize().Take(1).Consume(); + xs.Memoize().Take(1).Consume(); + } + + Assert.That(Act, Throws.TypeOf()); } [Test] public void MemoizeWithDisposeOnEarlyExitTrue() { - using (var xs = new[] { 1, 2 }.AsTestingSequence()) - { - var memoized = xs.Memoize(); - using ((IDisposable) memoized) - memoized.Take(1).Consume(); - } + using var xs = new[] { 1, 2 }.AsTestingSequence(); + + var memoized = xs.Memoize(); + + using ((IDisposable) memoized) + memoized.Take(1).Consume(); } [Test] public void MemoizeDisposesAfterSourceIsIteratedEntirely() { - using (var xs = new[] { 1, 2 }.AsTestingSequence()) - xs.Memoize().Consume(); + using var xs = new[] { 1, 2 }.AsTestingSequence(); + xs.Memoize().Consume(); } [Test, Explicit] public static void MemoizeIsThreadSafe() { var sequence = Enumerable.Range(1, 50000); - var memoized = sequence.AsTestingSequence().Memoize(); + using var ts = sequence.AsTestingSequence(); + var memoized = ts.Memoize(); var lists = Enumerable.Range(0, Environment.ProcessorCount * 2) .Select(_ => new List()) .ToArray(); - var start = new Barrier(lists.Length); + using var start = new Barrier(lists.Length); var threads = from list in lists @@ -216,15 +221,14 @@ public static void MemoizeIteratorThrowsWhenCacheDisposedDuringIteration() var memoized = sequence.Memoize(); var disposable = (IDisposable) memoized; - using (var reader = memoized.Read()) - { - Assert.That(reader.Read(), Is.EqualTo(1)); + using var reader = memoized.Read(); + Assert.That(reader.Read(), Is.EqualTo(1)); - disposable.Dispose(); + disposable.Dispose(); - var e = Assert.Throws(() => reader.Read()); - Assert.That(e.ObjectName, Is.EqualTo("MemoizedEnumerable")); - } + Assert.That(reader.Read, + Throws.ObjectDisposedException + .With.Property(nameof(ObjectDisposedException.ObjectName)).EqualTo("MemoizedEnumerable")); } [Test] @@ -239,29 +243,24 @@ public void MemoizeWithMemoizedSourceReturnsSame() [Test] public void MemoizeRethrowsErrorDuringIterationToAllIteratorsUntilDisposed() { - var error = new Exception("This is a test exception."); + var error = new TestException("This is a test exception."); - IEnumerable TestSequence() - { - yield return 123; - throw error; - } - - var xs = new DisposalTrackingSequence(TestSequence()); + using var xs = MoreEnumerable.From(() => 123, () => throw error) + .AsTestingSequence(TestingSequence.Options.AllowMultipleEnumerations); var memoized = xs.Memoize(); - using ((IDisposable) memoized) + using ((IDisposable)memoized) using (var r1 = memoized.Read()) using (var r2 = memoized.Read()) { Assert.That(r1.Read(), Is.EqualTo(r2.Read())); - var e1 = Assert.Throws(() => r1.Read()); - Assert.That(e1, Is.SameAs(error)); + Assert.That(r1.Read, Throws.TypeOf().And.SameAs(error)); Assert.That(xs.IsDisposed, Is.True); - var e2 = Assert.Throws(() => r2.Read()); - Assert.That(e2, Is.SameAs(error)); + Assert.That(r2.Read, Throws.TypeOf().And.SameAs(error)); } + + using ((IDisposable)memoized) using (var r1 = memoized.Read()) Assert.That(r1.Read(), Is.EqualTo(123)); } @@ -269,31 +268,24 @@ IEnumerable TestSequence() [Test] public void MemoizeRethrowsErrorDuringIterationStartToAllIteratorsUntilDisposed() { - var error = new Exception("This is a test exception."); + var error = new TestException("This is a test exception."); var i = 0; - IEnumerable TestSequence() - { - if (0 == i++) // throw at start for first iteration only - throw error; - yield return 42; - } - - var xs = new DisposalTrackingSequence(TestSequence()); + using var xs = MoreEnumerable.From(() => 0 == i++ + ? throw error // throw at start for first iteration only + : 42) + .AsTestingSequence(TestingSequence.Options.AllowMultipleEnumerations); var memoized = xs.Memoize(); - using ((IDisposable) memoized) + using ((IDisposable)memoized) using (var r1 = memoized.Read()) using (var r2 = memoized.Read()) { - var e1 = Assert.Throws(() => r1.Read()); - Assert.That(e1, Is.SameAs(error)); - + Assert.That(r1.Read, Throws.TypeOf().And.SameAs(error)); Assert.That(xs.IsDisposed, Is.True); - - var e2 = Assert.Throws(() => r2.Read()); - Assert.That(e2, Is.SameAs(error)); + Assert.That(r2.Read, Throws.TypeOf().And.SameAs(error)); } + using ((IDisposable)memoized) using (var r1 = memoized.Read()) using (var r2 = memoized.Read()) Assert.That(r1.Read(), Is.EqualTo(r2.Read())); @@ -302,7 +294,7 @@ IEnumerable TestSequence() [Test] public void MemoizeRethrowsErrorDuringFirstIterationStartToAllIterationsUntilDisposed() { - var error = new Exception("An error on the first call!"); + var error = new TestException("An error on the first call!"); var obj = new object(); var calls = 0; var source = Delegate.Enumerable(() => 0 == calls++ @@ -312,58 +304,10 @@ public void MemoizeRethrowsErrorDuringFirstIterationStartToAllIterationsUntilDis var memo = source.Memoize(); for (var i = 0; i < 2; i++) - { - var e = Assert.Throws(() => memo.First()); - Assert.That(e, Is.SameAs(error)); - } + Assert.That(memo.First, Throws.TypeOf().And.SameAs(error)); ((IDisposable) memo).Dispose(); Assert.That(memo.Single(), Is.EqualTo(obj)); } - - // TODO Consolidate with MoreLinq.Test.TestingSequence? - - sealed class DisposalTrackingSequence : IEnumerable, IDisposable - { - readonly IEnumerable _sequence; - - internal DisposalTrackingSequence(IEnumerable sequence) => - _sequence = sequence; - - public bool? IsDisposed { get; private set; } - - void IDisposable.Dispose() => IsDisposed = null; - - public IEnumerator GetEnumerator() - { - var enumerator = new DisposeTestingSequenceEnumerator(_sequence.GetEnumerator()); - IsDisposed = false; - enumerator.Disposed += delegate { IsDisposed = true; }; - return enumerator; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - sealed class DisposeTestingSequenceEnumerator : IEnumerator - { - readonly IEnumerator _sequence; - - public event EventHandler Disposed; - - public DisposeTestingSequenceEnumerator(IEnumerator sequence) => - _sequence = sequence; - - public T Current => _sequence.Current; - object IEnumerator.Current => Current; - public void Reset() => _sequence.Reset(); - public bool MoveNext() => _sequence.MoveNext(); - - public void Dispose() - { - _sequence.Dispose(); - Disposed?.Invoke(this, EventArgs.Empty); - } - } - } } } diff --git a/MoreLinq.Test/MinByTest.cs b/MoreLinq.Test/MinByTest.cs index 820e05b40..49c8565a7 100644 --- a/MoreLinq.Test/MinByTest.cs +++ b/MoreLinq.Test/MinByTest.cs @@ -17,8 +17,6 @@ namespace MoreLinq.Test { - using System; - using System.Collections.Generic; using NUnit.Framework; [TestFixture] @@ -33,15 +31,15 @@ public void MinByIsLazy() [Test] public void MinByReturnsMinima() { - Assert.AreEqual(new[] { "ax", "aa", "ab", "ay", "az" }, - SampleData.Strings.MinBy(x => x.Length)); + Assert.That(SampleData.Strings.MinBy(x => x.Length), + Is.EqualTo(new[] { "ax", "aa", "ab", "ay", "az" })); } [Test] public void MinByNullComparer() { - Assert.AreEqual(SampleData.Strings.MinBy(x => x.Length), - SampleData.Strings.MinBy(x => x.Length, null)); + Assert.That(SampleData.Strings.MinBy(x => x.Length, null), + Is.EqualTo(SampleData.Strings.MinBy(x => x.Length))); } [Test] @@ -53,63 +51,209 @@ public void MinByEmptySequence() [Test] public void MinByWithNaturalComparer() { - Assert.AreEqual(new[] { "aa" }, SampleData.Strings.MinBy(x => x[1])); + Assert.That(SampleData.Strings.MinBy(x => x[1]), Is.EqualTo(new[] { "aa" })); } [Test] public void MinByWithComparer() { - Assert.AreEqual(new[] { "az" }, SampleData.Strings.MinBy(x => x[1], SampleData.ReverseCharComparer)); + Assert.That(SampleData.Strings.MinBy(x => x[1], Comparable.DescendingOrderComparer), Is.EqualTo(new[] { "az" })); } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "ax" })] - [TestCase(2, ExpectedResult = new[] { "ax", "aa" })] - [TestCase(3, ExpectedResult = new[] { "ax", "aa", "ab" })] - [TestCase(4, ExpectedResult = new[] { "ax", "aa", "ab", "ay" })] - [TestCase(5, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - [TestCase(6, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - public string[] MinByTakeReturnsMinima(int count) + public class First { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MinBy(s => s.Length).Take(count).ToArray(); + [Test] + public void ReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = MoreEnumerable.First(strings.MinBy(s => s.Length)); + Assert.That(minima, Is.EqualTo("ax")); + } + + [Test] + public void WithComparerReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.First(minima), Is.EqualTo("hello")); + } + + [Test] + public void WithEmptySourceThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => MoreEnumerable.First(strings.MinBy(s => s.Length)), + Throws.InvalidOperationException); + } + + [Test] + public void WithEmptySourceWithComparerThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => MoreEnumerable.First(strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer)), + Throws.InvalidOperationException); + } } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "az" })] - [TestCase(2, ExpectedResult = new[] { "ay", "az" })] - [TestCase(3, ExpectedResult = new[] { "ab", "ay", "az" })] - [TestCase(4, ExpectedResult = new[] { "aa", "ab", "ay", "az" })] - [TestCase(5, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - [TestCase(6, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] - public string[] MinByTakeLastReturnsMinima(int count) + public class FirstOrDefault { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MinBy(s => s.Length).TakeLast(count).ToArray(); + [Test] + public void ReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length); + Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.EqualTo("ax")); + } + + [Test] + public void WithComparerReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.EqualTo("hello")); + } + + [Test] + public void WithEmptySourceReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var minima = strings.MinBy(s => s.Length); + Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.Null); + } + + [Test] + public void WithEmptySourceWithComparerReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.FirstOrDefault(minima), Is.Null); + } + } + + public class Last + { + [Test] + public void ReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length); + Assert.That(MoreEnumerable.Last(minima), Is.EqualTo("az")); + } + + [Test] + public void WithComparerReturnsMinimumPerComparer() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.Last(minima), Is.EqualTo("world")); + } + + [Test] + public void WithEmptySourceThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => MoreEnumerable.Last(strings.MinBy(s => s.Length)), + Throws.InvalidOperationException); + } + + [Test] + public void WithEmptySourceWithComparerThrows() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + Assert.That(() => MoreEnumerable.Last(strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer)), + Throws.InvalidOperationException); + } + } + + public class LastOrDefault + { + [Test] + public void ReturnsMinimum() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length); + Assert.That(MoreEnumerable.LastOrDefault(minima), Is.EqualTo("az")); + } + + [Test] + public void WithComparerReturnsMinimumPerComparer() + { + using var strings = SampleData.Strings.AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.LastOrDefault(minima), Is.EqualTo("world")); + } + + [Test] + public void WithEmptySourceReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var minima = strings.MinBy(s => s.Length); + Assert.That(MoreEnumerable.LastOrDefault(minima), Is.Null); + } + + [Test] + public void WithEmptySourceWithComparerReturnsDefault() + { + using var strings = Enumerable.Empty().AsTestingSequence(); + var minima = strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer); + Assert.That(MoreEnumerable.LastOrDefault(minima), Is.Null); + } } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "hello", })] - [TestCase(2, ExpectedResult = new[] { "hello", "world" })] - [TestCase(3, ExpectedResult = new[] { "hello", "world" })] - public string[] MinByTakeWithComparerReturnsMinima(int count) + public class Take { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MinBy(s => s.Length, Comparer.Create((x, y) => -Math.Sign(x.CompareTo(y)))) + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "ax" })] + [TestCase(2, ExpectedResult = new[] { "ax", "aa" })] + [TestCase(3, ExpectedResult = new[] { "ax", "aa", "ab" })] + [TestCase(4, ExpectedResult = new[] { "ax", "aa", "ab", "ay" })] + [TestCase(5, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + [TestCase(6, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + public string[] ReturnsMinima(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MinBy(s => s.Length).Take(count).ToArray(); + } + + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "hello", })] + [TestCase(2, ExpectedResult = new[] { "hello", "world" })] + [TestCase(3, ExpectedResult = new[] { "hello", "world" })] + public string[] WithComparerReturnsMinimaPerComparer(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer) .Take(count) .ToArray(); + } } - [TestCase(0, ExpectedResult = new string[0] )] - [TestCase(1, ExpectedResult = new[] { "world", })] - [TestCase(2, ExpectedResult = new[] { "hello", "world" })] - [TestCase(3, ExpectedResult = new[] { "hello", "world" })] - public string[] MinByTakeLastWithComparerReturnsMinima(int count) + public class TakeLast { - using (var strings = SampleData.Strings.AsTestingSequence()) - return strings.MinBy(s => s.Length, Comparer.Create((x, y) => -Math.Sign(x.CompareTo(y)))) + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "az" })] + [TestCase(2, ExpectedResult = new[] { "ay", "az" })] + [TestCase(3, ExpectedResult = new[] { "ab", "ay", "az" })] + [TestCase(4, ExpectedResult = new[] { "aa", "ab", "ay", "az" })] + [TestCase(5, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + [TestCase(6, ExpectedResult = new[] { "ax", "aa", "ab", "ay", "az" })] + public string[] ReturnsMinima(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MinBy(s => s.Length).TakeLast(count).ToArray(); + } + + [TestCase(0, ExpectedResult = new string[0] )] + [TestCase(1, ExpectedResult = new[] { "world", })] + [TestCase(2, ExpectedResult = new[] { "hello", "world" })] + [TestCase(3, ExpectedResult = new[] { "hello", "world" })] + public string[] WithComparerReturnsMinimaPerComparer(int count) + { + using var strings = SampleData.Strings.AsTestingSequence(); + return strings.MinBy(s => s.Length, Comparable.DescendingOrderComparer) .TakeLast(count) .ToArray(); + } } } } diff --git a/MoreLinq.Test/MoreLinq.Test.csproj b/MoreLinq.Test/MoreLinq.Test.csproj index ea2da2dde..da095895b 100644 --- a/MoreLinq.Test/MoreLinq.Test.csproj +++ b/MoreLinq.Test/MoreLinq.Test.csproj @@ -2,18 +2,15 @@ MoreLinq.Test - netcoreapp2.1;netcoreapp3.0;net451 - true + net7.0;net6.0;netcoreapp3.1;net462 portable MoreLinq.Test Exe true false - false library MoreLinq.Test.Program - 8 618 @@ -22,48 +19,52 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + runtime; build; native; contentfiles; analyzers all - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - + @@ -73,10 +74,6 @@ - - - - diff --git a/MoreLinq.Test/MoveTest.cs b/MoreLinq.Test/MoveTest.cs index 150f958ec..abe85b2ef 100644 --- a/MoreLinq.Test/MoveTest.cs +++ b/MoreLinq.Test/MoveTest.cs @@ -27,22 +27,22 @@ public class MoveTest [Test] public void MoveWithNegativeFromIndex() { - AssertThrowsArgument.OutOfRangeException("fromIndex", () => - new[] { 1 }.Move(-1, 0, 0)); + Assert.That(() => new[] { 1 }.Move(-1, 0, 0), + Throws.ArgumentOutOfRangeException("fromIndex")); } [Test] public void MoveWithNegativeCount() { - AssertThrowsArgument.OutOfRangeException("count", () => - new[] { 1 }.Move(0, -1, 0)); + Assert.That(() => new[] { 1 }.Move(0, -1, 0), + Throws.ArgumentOutOfRangeException("count")); } [Test] public void MoveWithNegativeToIndex() { - AssertThrowsArgument.OutOfRangeException("toIndex", () => - new[] { 1 }.Move(0, 0, -1)); + Assert.That(() => new[] { 1 }.Move(0, 0, -1), + Throws.ArgumentOutOfRangeException("toIndex")); } [Test] @@ -56,15 +56,14 @@ public void Move(int length, int fromIndex, int count, int toIndex) { var source = Enumerable.Range(0, length); - var exclude = source.Exclude(fromIndex, count); + using var test = source.AsTestingSequence(); + + var result = test.Move(fromIndex, count, toIndex); + var slice = source.Slice(fromIndex, count); + var exclude = source.Exclude(fromIndex, count); var expectations = exclude.Take(toIndex).Concat(slice).Concat(exclude.Skip(toIndex)); - - using (var test = source.AsTestingSequence()) - { - var result = test.Move(fromIndex, count, toIndex); - Assert.That(result, Is.EqualTo(expectations)); - } + Assert.That(result, Is.EqualTo(expectations)); } public static IEnumerable MoveSource() @@ -85,13 +84,12 @@ public void MoveWithSequenceShorterThanToIndex(int length, int fromIndex, int co { var source = Enumerable.Range(0, length); - var expectations = source.Exclude(fromIndex, count).Concat(source.Slice(fromIndex, count)); + using var test = source.AsTestingSequence(); - using (var test = source.AsTestingSequence()) - { - var result = test.Move(fromIndex, count, toIndex); - Assert.That(result, Is.EqualTo(expectations)); - } + var result = test.Move(fromIndex, count, toIndex); + + var expectations = source.Exclude(fromIndex, count).Concat(source.Slice(fromIndex, count)); + Assert.That(result, Is.EqualTo(expectations)); } public static IEnumerable MoveWithSequenceShorterThanToIndexSource() diff --git a/MoreLinq.Test/NullArgumentTest.cs b/MoreLinq.Test/NullArgumentTest.cs index dc3115a6d..773bdd6b4 100644 --- a/MoreLinq.Test/NullArgumentTest.cs +++ b/MoreLinq.Test/NullArgumentTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2015 Julian Lettner. 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. @@ -20,12 +20,12 @@ namespace MoreLinq.Test using System; using System.Collections; using System.Collections.Generic; - using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using NUnit.Framework; using NUnit.Framework.Interfaces; + using StackTrace = System.Diagnostics.StackTrace; [TestFixture] public class NullArgumentTest @@ -41,7 +41,7 @@ public void CanBeNull(Action testCase) => static IEnumerable GetNotNullTestCases() => GetTestCases(canBeNull: false, testCaseFactory: (method, args, paramName) => () => { - Exception e = null; + Exception? e = null; try { @@ -53,24 +53,29 @@ static IEnumerable GetNotNullTestCases() => } Assert.That(e, Is.Not.Null, $"No exception was thrown when {nameof(ArgumentNullException)} was expected."); - Assert.That(e, Is.InstanceOf()); - var ane = (ArgumentNullException) e; - Assert.That(ane.ParamName, Is.EqualTo(paramName)); - var stackTrace = new StackTrace(ane, false); + Assert.That(e, Is.InstanceOf().With.Property(nameof(ArgumentNullException.ParamName)).EqualTo(paramName)); + Debug.Assert(e is not null); + var stackTrace = new StackTrace(e, false); var stackFrame = stackTrace.GetFrames().First(); - var actualType = stackFrame.GetMethod().DeclaringType; +#if NETCOREAPP3_1 + // Under .NET Core 3.1, "StackTrace.GetFrames()" was defined to return an array + // of nullable frame elements. See: + // https://github.com/dotnet/corefx/blob/v3.1.32/src/Common/src/CoreLib/System/Diagnostics/StackTrace.cs#L162 + Debug.Assert(stackFrame is not null); +#endif + var actualType = stackFrame.GetMethod()?.DeclaringType; Assert.That(actualType, Is.SameAs(typeof(MoreEnumerable))); }); static IEnumerable GetCanBeNullTestCases() => GetTestCases(canBeNull: true, testCaseFactory: (method, args, _) => () => method.Invoke(null, args)); - static IEnumerable GetTestCases(bool canBeNull, Func testCaseFactory) => + static IEnumerable GetTestCases(bool canBeNull, Func testCaseFactory) => from m in typeof (MoreEnumerable).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) from t in CreateTestCases(m, canBeNull, testCaseFactory) select t; - static IEnumerable CreateTestCases(MethodInfo methodDefinition, bool canBeNull, Func testCaseFactory) + static IEnumerable CreateTestCases(MethodInfo methodDefinition, bool canBeNull, Func testCaseFactory) { var method = InstantiateMethod(methodDefinition); var parameters = method.GetParameters().ToList(); @@ -78,7 +83,10 @@ static IEnumerable CreateTestCases(MethodInfo methodDefinition, b return from param in parameters where IsReferenceType(param) && CanBeNull(param) == canBeNull let arguments = parameters.Select(p => p == param ? null : CreateInstance(p.ParameterType)).ToArray() - let testCase = testCaseFactory(method, arguments, param.Name) + let testCase = testCaseFactory(method, arguments, +#pragma warning disable CA2201 // Do not raise reserved exception types + param.Name ?? throw new NullReferenceException()) +#pragma warning restore CA2201 // Do not raise reserved exception types let testName = GetTestName(methodDefinition, param) select (ITestCaseData) new TestCaseData(testCase).SetName(testName); } @@ -98,10 +106,12 @@ static Type InstantiateType(TypeInfo typeParameter) { var constraints = typeParameter.GetGenericParameterConstraints(); - if (constraints.Length == 0) return typeof (int); - if (constraints.Length == 1) return constraints.Single(); - - throw new NotImplementedException("NullArgumentTest.InstantiateType"); + return constraints.Length switch + { + 0 => typeof(int), + 1 => constraints.Single(), + _ => throw new NotImplementedException("NullArgumentTest.InstantiateType") + }; } static bool IsReferenceType(ParameterInfo parameter) => @@ -137,15 +147,25 @@ static object CreateInstance(Type type) if (type == typeof (string)) return ""; if (type == typeof(TaskScheduler)) return TaskScheduler.Default; if (type == typeof(IEnumerable)) return new[] { 1, 2, 3 }; // Provide non-empty sequence for MinBy/MaxBy. - if (type.IsArray) return Array.CreateInstance(type.GetElementType(), 0); - if (type.GetTypeInfo().IsValueType || HasDefaultConstructor(type)) return Activator.CreateInstance(type); if (typeof(Delegate).IsAssignableFrom(type)) return CreateDelegateInstance(type); - var typeInfo = type.GetTypeInfo(); + if (type.IsArray) + { + var elementType = type.GetElementType(); + Debug.Assert(elementType is not null); + return Array.CreateInstance(elementType, 0); + } + + if (type.GetTypeInfo().IsValueType || HasDefaultConstructor(type)) + { + var instance = Activator.CreateInstance(type); + Debug.Assert(instance is not null); + return instance; + } - return typeInfo.IsGenericType - ? CreateGenericInterfaceInstance(typeInfo) - : EmptyEnumerable.Instance; + return type.GetTypeInfo() is { IsGenericType: true } typeInfo + ? CreateGenericInterfaceInstance(typeInfo) + : EmptyEnumerable.Instance; } static bool HasDefaultConstructor(Type type) => @@ -154,6 +174,7 @@ static bool HasDefaultConstructor(Type type) => static Delegate CreateDelegateInstance(Type type) { var invoke = type.GetMethod("Invoke"); + Debug.Assert(invoke is not null); var parameters = invoke.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)); var body = Expression.Default(invoke.ReturnType); // requires >= .NET 4.0 var lambda = Expression.Lambda(type, body, parameters); @@ -165,8 +186,10 @@ static object CreateGenericInterfaceInstance(TypeInfo type) Debug.Assert(type.IsGenericType && type.IsInterface); var name = type.Name.Substring(1); // Delete first character, i.e. the 'I' in IEnumerable var definition = typeof (GenericArgs).GetTypeInfo().GetNestedType(name); - var instantiation = definition.MakeGenericType(type.GetGenericArguments()); - return Activator.CreateInstance(instantiation); + Debug.Assert(definition is not null); + var instance = Activator.CreateInstance(definition.MakeGenericType(type.GetGenericArguments())); + Debug.Assert(instance is not null); + return instance; } static class EmptyEnumerable @@ -189,47 +212,51 @@ public void Reset() { } // ReSharper disable UnusedMember.Local, UnusedAutoPropertyAccessor.Local static class GenericArgs { - class Enumerator : IEnumerator + sealed class Enumerator : IEnumerator { public bool MoveNext() => false; - public T Current { get; private set; } - object IEnumerator.Current => Current; + public T? Current { get; private set; } + object? IEnumerator.Current => Current; public void Reset() { } public void Dispose() { } } - public class Enumerable : IEnumerable + public class Enumerable : IEnumerable { - public IEnumerator GetEnumerator() => new Enumerator(); + public IEnumerator GetEnumerator() => new Enumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public class OrderedEnumerable : Enumerable, System.Linq.IOrderedEnumerable +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + + public sealed class OrderedEnumerable : Enumerable, System.Linq.IOrderedEnumerable { - public System.Linq.IOrderedEnumerable CreateOrderedEnumerable(Func keySelector, IComparer comparer, bool descending) + public System.Linq.IOrderedEnumerable CreateOrderedEnumerable(Func keySelector, IComparer? comparer, bool descending) { if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); return this; } } - public class AwaitQuery : Enumerable, - Experimental.IAwaitQuery + public sealed class AwaitQuery : Enumerable, + Experimental.IAwaitQuery { public Experimental.AwaitQueryOptions Options => Experimental.AwaitQueryOptions.Default; - public Experimental.IAwaitQuery WithOptions(Experimental.AwaitQueryOptions options) => this; + public Experimental.IAwaitQuery WithOptions(Experimental.AwaitQueryOptions options) => this; } - public class Comparer : IComparer + public sealed class Comparer : IComparer { - public int Compare(T x, T y) => -1; + public int Compare(T? x, T? y) => -1; } - public class EqualityComparer : IEqualityComparer + public sealed class EqualityComparer : IEqualityComparer { - public bool Equals(T x, T y) => false; + public bool Equals(T? x, T? y) => false; public int GetHashCode(T obj) => 0; } + +#pragma warning restore CA1812 // Avoid uninstantiated internal classes } } } diff --git a/MoreLinq.Test/OrderByTest.cs b/MoreLinq.Test/OrderByTest.cs index 07753d5cc..8d4c2f6c0 100644 --- a/MoreLinq.Test/OrderByTest.cs +++ b/MoreLinq.Test/OrderByTest.cs @@ -1,5 +1,24 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { + using System.Collections.Generic; + using System.Globalization; using NUnit.Framework; /// @@ -28,6 +47,9 @@ public void TestOrderBySelectorPreserved() Assert.That(resultDes1, Is.EqualTo(resultDes2)); } + static readonly IComparer NumericStringComparer = + Comparer.Create((a, b) => int.Parse(a, CultureInfo.InvariantCulture).CompareTo(int.Parse(b, CultureInfo.InvariantCulture))); + /// /// Verify that OrderBy preserves the comparer /// @@ -35,10 +57,10 @@ public void TestOrderBySelectorPreserved() public void TestOrderByComparerPreserved() { var sequence = Enumerable.Range(1, 100); - var sequenceAscending = sequence.Select(x => x.ToString()); + var sequenceAscending = sequence.Select(x => x.ToInvariantString()); var sequenceDescending = sequenceAscending.Reverse(); - var comparer = Comparer.Create((a, b) => int.Parse(a).CompareTo(int.Parse(b))); + var comparer = NumericStringComparer; var resultAsc1 = sequenceAscending.OrderBy(x => x, comparer, OrderByDirection.Descending); var resultAsc2 = sequenceAscending.OrderByDescending(x => x, comparer); @@ -102,7 +124,7 @@ public void TestThenByComparerPreserved() new {A = "2", B = "1"}, }; - var comparer = Comparer.Create((a, b) => int.Parse(a).CompareTo(int.Parse(b))); + var comparer = NumericStringComparer; var resultA1 = sequence.OrderBy(x => x.A, comparer, OrderByDirection.Ascending) .ThenBy(y => y.B, comparer, OrderByDirection.Ascending); diff --git a/MoreLinq.Test/OrderedMergeTest.cs b/MoreLinq.Test/OrderedMergeTest.cs new file mode 100644 index 000000000..416e43c48 --- /dev/null +++ b/MoreLinq.Test/OrderedMergeTest.cs @@ -0,0 +1,72 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2019 Pierre Lando. 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.Test +{ + using System.Collections.Generic; + using NUnit.Framework; + using NUnit.Framework.Interfaces; + + [TestFixture] + public class OrderedMergeTest + { + static IEnumerable Seq(params T[] values) => values; + + public static readonly IEnumerable TestData = + from e in new[] + { + new + { + A = Seq(0, 2, 4), + B = Seq(0, 1, 2, 3, 4), + R = Seq(0, 1, 2, 3, 4) + }, + new + { + A = Seq(0, 1, 2, 3, 4), + B = Seq(0, 2, 4), + R = Seq(0, 1, 2, 3, 4) + }, + new + { + A = Seq(0, 2, 4), + B = Seq(1, 3, 5), + R = Seq(0, 1, 2, 3, 4, 5) + }, + new + { + A = Seq(), + B = Seq(0, 1, 2), + R = Seq(0, 1, 2) + }, + new + { + A = Seq(0, 1, 2), + B = Seq(), + R = Seq(0, 1, 2) + } + } + select new TestCaseData(e.A.AsTestingSequence(), e.B.AsTestingSequence()).Returns(e.R); + + [Test, TestCaseSource(nameof(TestData))] + public IEnumerable OrderedMerge(IEnumerable first, + IEnumerable second) + { + return first.OrderedMerge(second); + } + } +} diff --git a/MoreLinq.Test/PadStartTest.cs b/MoreLinq.Test/PadStartTest.cs index a0091a374..a65e3bd2b 100644 --- a/MoreLinq.Test/PadStartTest.cs +++ b/MoreLinq.Test/PadStartTest.cs @@ -29,7 +29,7 @@ public class PadStartTest [Test] public void PadStartWithNegativeWidth() { - AssertThrowsArgument.Exception("width", () => new int[0].PadStart(-1)); + Assert.That(() => new int[0].PadStart(-1), Throws.ArgumentException("width")); } [Test] @@ -38,13 +38,25 @@ public void PadStartIsLazy() new BreakingSequence().PadStart(0); } - [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 4, new[] { 0, 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 5, new[] { 0, 0, 123, 456, 789 })] - public void PadStart(ICollection source, int width, IEnumerable expected) + public class PadStartWithDefaultPadding { - AssertEqual(source, x => x.PadStart(width), expected); + [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 4, new[] { 0, 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 5, new[] { 0, 0, 123, 456, 789 })] + public void ValueTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width), expected); + } + + [TestCase(new[] { "foo", "bar", "baz" }, 2, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 3, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 4, new[] { null, "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 5, new[] { null, null, "foo", "bar", "baz" })] + public void ReferenceTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width), expected); + } } // PadStart(source, width, padding) @@ -52,7 +64,7 @@ public void PadStart(ICollection source, int width, IEnumerable expect [Test] public void PadStartWithPaddingWithNegativeWidth() { - AssertThrowsArgument.Exception("width", () => new int[0].PadStart(-1, 1)); + Assert.That(() => new int[0].PadStart(-1, 1), Throws.ArgumentException("width")); } [Test] @@ -61,13 +73,25 @@ public void PadStartWithPaddingIsLazy() new BreakingSequence().PadStart(0, -1); } - [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 4, new[] { -1, 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 5, new[] { -1, -1, 123, 456, 789 })] - public void PadStartWithPadding(ICollection source, int width, IEnumerable expected) + public class PadStartWithPadding { - AssertEqual(source, x => x.PadStart(width, -1), expected); + [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 4, new[] { -1, 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 5, new[] { -1, -1, 123, 456, 789 })] + public void ValueTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width, -1), expected); + } + + [TestCase(new[] { "foo", "bar", "baz" }, 2, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 3, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 4, new[] { "", "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 5, new[] { "", "", "foo", "bar", "baz" })] + public void ReferenceTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width, string.Empty), expected); + } } // PadStart(source, width, paddingSelector) @@ -75,7 +99,7 @@ public void PadStartWithPadding(ICollection source, int width, IEnumerable< [Test] public void PadStartWithSelectorWithNegativeWidth() { - AssertThrowsArgument.Exception("width", () => new int[0].PadStart(-1, x => x)); + Assert.That(() => new int[0].PadStart(-1, x => x), Throws.ArgumentException("width")); } [Test] @@ -84,17 +108,32 @@ public void PadStartWithSelectorIsLazy() new BreakingSequence().PadStart(0, BreakingFunc.Of()); } - [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 4, new[] { 0, 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 5, new[] { 0, -1, 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 6, new[] { 0, -1, -4, 123, 456, 789 })] - [TestCase(new[] { 123, 456, 789 }, 7, new[] { 0, -1, -4, -9, 123, 456, 789 })] - public void PadStartWithSelector(ICollection source, int width, IEnumerable expected) + public class PadStartWithSelector { - AssertEqual(source, x => x.PadStart(width, y => y * -y), expected); + [TestCase(new[] { 123, 456, 789 }, 2, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 3, new[] { 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 4, new[] { 0, 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 5, new[] { 0, -1, 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 6, new[] { 0, -1, -4, 123, 456, 789 })] + [TestCase(new[] { 123, 456, 789 }, 7, new[] { 0, -1, -4, -9, 123, 456, 789 })] + public void ValueTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width, y => y * -y), expected); + } + + [TestCase(new[] { "foo", "bar", "baz" }, 2, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 3, new[] { "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 4, new[] { "+", "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 5, new[] { "+", "++", "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 6, new[] { "+", "++", "+++", "foo", "bar", "baz" })] + [TestCase(new[] { "foo", "bar", "baz" }, 7, new[] { "+", "++", "+++", "++++", "foo", "bar", "baz" })] + public void ReferenceTypeElements(ICollection source, int width, IEnumerable expected) + { + AssertEqual(source, x => x.PadStart(width, y => new string('+', y + 1)), expected); + } } + static void AssertEqual(ICollection input, Func, IEnumerable> op, IEnumerable expected) { // Test that the behaviour does not change whether a collection diff --git a/MoreLinq.Test/PadTest.cs b/MoreLinq.Test/PadTest.cs index 6a333bf82..4537bdca0 100644 --- a/MoreLinq.Test/PadTest.cs +++ b/MoreLinq.Test/PadTest.cs @@ -25,8 +25,7 @@ public class PadTest [Test] public void PadNegativeWidth() { - AssertThrowsArgument.Exception("width",() => - new object[0].Pad(-1)); + Assert.That(() => new object[0].Pad(-1), Throws.ArgumentException("width")); } [Test] @@ -41,39 +40,73 @@ public void PadWithFillerIsLazy() new BreakingSequence().Pad(0, new object()); } - [Test] - public void PadWideSourceSequence() + public class ValueTypeElements { - var result = new[] { 123, 456, 789 }.Pad(2); - result.AssertSequenceEqual(123, 456, 789); - } + [Test] + public void PadWideSourceSequence() + { + var result = new[] { 123, 456, 789 }.Pad(2); + result.AssertSequenceEqual(123, 456, 789); + } - [Test] - public void PadEqualSourceSequence() - { - var result = new[] { 123, 456, 789 }.Pad(3); - result.AssertSequenceEqual(123, 456, 789); - } + [Test] + public void PadEqualSourceSequence() + { + var result = new[] { 123, 456, 789 }.Pad(3); + result.AssertSequenceEqual(123, 456, 789); + } - [Test] - public void PadNarrowSourceSequenceWithDefaultPadding() - { - var result = new[] { 123, 456, 789 }.Pad(5); - result.AssertSequenceEqual(123, 456, 789, 0, 0); - } + [Test] + public void PadNarrowSourceSequenceWithDefaultPadding() + { + var result = new[] { 123, 456, 789 }.Pad(5); + result.AssertSequenceEqual(123, 456, 789, 0, 0); + } - [Test] - public void PadNarrowSourceSequenceWithNonDefaultPadding() - { - var result = new[] { 123, 456, 789 }.Pad(5, -1); - result.AssertSequenceEqual(123, 456, 789, -1, -1); + [Test] + public void PadNarrowSourceSequenceWithNonDefaultPadding() + { + var result = new[] { 123, 456, 789 }.Pad(5, -1); + result.AssertSequenceEqual(123, 456, 789, -1, -1); + } + + [Test] + public void PadNarrowSourceSequenceWithDynamicPadding() + { + var result = "hello".ToCharArray().Pad(15, i => i % 2 == 0 ? '+' : '-'); + result.AssertSequenceEqual("hello-+-+-+-+-+".ToCharArray()); + } } - [Test] - public void PadNarrowSourceSequenceWithDynamicPadding() + public class ReferenceTypeElements { - var result = "hello".ToCharArray().Pad(15, i => i % 2 == 0 ? '+' : '-'); - result.AssertSequenceEqual("hello-+-+-+-+-+".ToCharArray()); + [Test] + public void PadWideSourceSequence() + { + var result = new[] { "foo", "bar", "baz" }.Pad(2); + result.AssertSequenceEqual("foo", "bar", "baz"); + } + + [Test] + public void PadEqualSourceSequence() + { + var result = new[] { "foo", "bar", "baz" }.Pad(3); + result.AssertSequenceEqual("foo", "bar", "baz"); + } + + [Test] + public void PadNarrowSourceSequenceWithDefaultPadding() + { + var result = new[] { "foo", "bar", "baz" }.Pad(5); + result.AssertSequenceEqual("foo", "bar", "baz", null, null); + } + + [Test] + public void PadNarrowSourceSequenceWithNonDefaultPadding() + { + var result = new[] { "foo", "bar", "baz" }.Pad(5, string.Empty); + result.AssertSequenceEqual("foo", "bar", "baz", string.Empty, string.Empty); + } } } } diff --git a/MoreLinq.Test/PairwiseTest.cs b/MoreLinq.Test/PairwiseTest.cs index 7d84355f4..d810ea9fa 100644 --- a/MoreLinq.Test/PairwiseTest.cs +++ b/MoreLinq.Test/PairwiseTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2012 Atif Aziz. 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. diff --git a/MoreLinq.Test/PartialSortByTest.cs b/MoreLinq.Test/PartialSortByTest.cs index 51a2b8a03..e192e60fe 100644 --- a/MoreLinq.Test/PartialSortByTest.cs +++ b/MoreLinq.Test/PartialSortByTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2016 Atif Aziz. 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. @@ -87,7 +87,7 @@ public void PartialSortByIsStable() var sorted = foobars.PartialSort(5); - // Pair expected and actuals by index and then check + // Pair expected and actual by index and then check // reference equality, finding the first mismatch. var mismatchIndex = diff --git a/MoreLinq.Test/PartialSortTest.cs b/MoreLinq.Test/PartialSortTest.cs index 16a906f15..3c644e6ae 100644 --- a/MoreLinq.Test/PartialSortTest.cs +++ b/MoreLinq.Test/PartialSortTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2016 Atif Aziz. 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. @@ -92,7 +92,7 @@ public void PartialSortByIsStable() var sorted = foobars.PartialSortBy(5, s => s.Length); - // Pair expected and actuals by index and then check + // Pair expected and actual by index and then check // reference equality, finding the first mismatch. var mismatchIndex = diff --git a/MoreLinq.Test/PartitionTest.cs b/MoreLinq.Test/PartitionTest.cs index 727dff238..cdc4ac19c 100644 --- a/MoreLinq.Test/PartitionTest.cs +++ b/MoreLinq.Test/PartitionTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2010 Leopold Bushkin. 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. @@ -93,18 +93,16 @@ public void PartitionBooleanGroupingWithSingleKey() Assert.That(m3, Is.EqualTo(new[] { 0, 3, 6, 9 })); - using (var r = etc.Read()) - { - var r1 = r.Read(); - Assert.That(r1.Key, Is.EqualTo(1)); - Assert.That(r1, Is.EqualTo(new[] { 1, 4, 7 })); + using var r = etc.Read(); + var r1 = r.Read(); + Assert.That(r1.Key, Is.EqualTo(1)); + Assert.That(r1, Is.EqualTo(new[] { 1, 4, 7 })); - var r2 = r.Read(); - Assert.That(r2.Key, Is.EqualTo(2)); - Assert.That(r2, Is.EqualTo(new[] { 2, 5, 8 })); + var r2 = r.Read(); + Assert.That(r2.Key, Is.EqualTo(2)); + Assert.That(r2, Is.EqualTo(new[] { 2, 5, 8 })); - r.ReadEnd(); - } + r.ReadEnd(); } [Test] @@ -118,13 +116,11 @@ public void PartitionBooleanGroupingWitTwoKeys() Assert.That(ms, Is.EqualTo(new[] { 0, 3, 6, 9 })); Assert.That(r1, Is.EqualTo(new[] { 1, 4, 7 })); - using (var r = etc.Read()) - { - var r2 = r.Read(); - Assert.That(r2.Key, Is.EqualTo(2)); - Assert.That(r2, Is.EqualTo(new[] { 2, 5, 8 })); - r.ReadEnd(); - } + using var r = etc.Read(); + var r2 = r.Read(); + Assert.That(r2.Key, Is.EqualTo(2)); + Assert.That(r2, Is.EqualTo(new[] { 2, 5, 8 })); + r.ReadEnd(); } [Test] @@ -153,12 +149,10 @@ public void PartitionBooleanGroupingWithSingleKeyWithComparer() Assert.That(foo, Is.EqualTo(new[] { "foo", "FOO" })); - using (var r = etc.Read()) - { - var bar = r.Read(); - Assert.That(bar, Is.EqualTo(new[] { "bar", "Bar" })); - r.ReadEnd(); - } + using var r = etc.Read(); + var bar = r.Read(); + Assert.That(bar, Is.EqualTo(new[] { "bar", "Bar" })); + r.ReadEnd(); } [Test] @@ -174,18 +168,16 @@ public void PartitionBooleanGroupingWithTwoKeysWithComparer() Assert.That(foos, Is.EqualTo(new[] { "foo", "FOO" })); Assert.That(bar, Is.EqualTo(new[] { "bar", "Bar" })); - using (var r = etc.Read()) - { - var baz = r.Read(); - Assert.That(baz.Key, Is.EqualTo("baz")); - Assert.That(baz, Is.EqualTo(new[] { "baz", "bAz" })); + using var r = etc.Read(); + var baz = r.Read(); + Assert.That(baz.Key, Is.EqualTo("baz")); + Assert.That(baz, Is.EqualTo(new[] { "baz", "bAz" })); - var qux = r.Read(); - Assert.That(qux.Key, Is.EqualTo("QUx")); - Assert.That(qux, Is.EqualTo(new[] { "QUx", "QuX" })); + var qux = r.Read(); + Assert.That(qux.Key, Is.EqualTo("QUx")); + Assert.That(qux, Is.EqualTo(new[] { "QUx", "QuX" })); - r.ReadEnd(); - } + r.ReadEnd(); } [Test] @@ -202,13 +194,11 @@ public void PartitionBooleanGroupingWithThreeKeysWithComparer() Assert.That(bar, Is.EqualTo(new[] { "bar", "Bar" })); Assert.That(baz, Is.EqualTo(new[] { "baz", "bAz" })); - using (var r = etc.Read()) - { - var qux = r.Read(); - Assert.That(qux.Key, Is.EqualTo("QUx")); - Assert.That(qux, Is.EqualTo(new[] { "QUx", "QuX" })); - r.ReadEnd(); - } + using var r = etc.Read(); + var qux = r.Read(); + Assert.That(qux.Key, Is.EqualTo("QUx")); + Assert.That(qux, Is.EqualTo(new[] { "QUx", "QuX" })); + r.ReadEnd(); } } } diff --git a/MoreLinq.Test/PermutationsTest.cs b/MoreLinq.Test/PermutationsTest.cs index cbb3eef8a..231e4c601 100644 --- a/MoreLinq.Test/PermutationsTest.cs +++ b/MoreLinq.Test/PermutationsTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System.Collections.Generic; @@ -46,7 +63,7 @@ public void TestCardinalityTwoPermutation() var permutations = set.Permutations(); // should contain two results: the set itself and its reverse - Assert.IsTrue(permutations.Count() == 2); + Assert.That(permutations.Count(), Is.EqualTo(2)); Assert.That(permutations.First(), Is.EqualTo(set)); Assert.That(permutations.Last(), Is.EqualTo(set.Reverse())); } @@ -72,8 +89,8 @@ public void TestCardinalityThreePermutation() }; // should contain six permutations (as defined above) - Assert.AreEqual(expectedPermutations.Length, permutations.Count()); - Assert.IsTrue(permutations.All(p => expectedPermutations.Contains(p, EqualityComparer.Create>((x, y) => x.SequenceEqual(y))))); + Assert.That(permutations.Count(), Is.EqualTo(expectedPermutations.Length)); + Assert.That(permutations.All(p => expectedPermutations.Contains(p, SequenceEqualityComparer.Instance)), Is.True); } /// @@ -115,8 +132,8 @@ public void TestCardinalityFourPermutation() }; // should contain six permutations (as defined above) - Assert.AreEqual(expectedPermutations.Length, permutations.Count()); - Assert.IsTrue(permutations.All(p => expectedPermutations.Contains(p, EqualityComparer.Create>((x, y) => x.SequenceEqual(y))))); + Assert.That(permutations.Count(), Is.EqualTo(expectedPermutations.Length)); + Assert.That(permutations.All(p => expectedPermutations.Contains(p, SequenceEqualityComparer.Instance)), Is.True); } /// @@ -127,7 +144,7 @@ public void TestCardinalityFourPermutation() public void TestHigherCardinalityPermutations() { // NOTE: Testing higher cardinality permutations by exhaustive comparison becomes tedious - // above cardiality 4 sets, as the number of permutations is N! (factorial). To provide + // above cardinality 4 sets, as the number of permutations is N! (factorial). To provide // some level of verification, though, we will simply test the count of items in the // permuted sets, and verify they are equal to the expected number (count!). @@ -142,7 +159,7 @@ public void TestHigherCardinalityPermutations() { var permutedSet = set.Permutations(); var permutationCount = permutedSet.Count(); - Assert.AreEqual(Combinatorics.Factorial(set.Count()), permutationCount); + Assert.That(permutationCount, Is.EqualTo(Combinatorics.Factorial(set.Count()))); } } @@ -168,16 +185,22 @@ public void TestPermutationsAreIndependent() var listPermutations = new List>(); listPermutations.AddRange(permutedSets); - Assert.IsNotEmpty(listPermutations); + Assert.That(listPermutations, Is.Not.Empty); for (var i = 0; i < listPermutations.Count; i++) { for (var j = 1; j < listPermutations.Count; j++) { if (j == i) continue; - Assert.AreNotSame(listPermutations[i], listPermutations[j]); + Assert.That(listPermutations[i], Is.Not.SameAs(listPermutations[j])); } } } + + static class SequenceEqualityComparer + { + public static readonly IEqualityComparer> Instance = + EqualityComparer.Create>((x, y) => x is { } sx && y is { } sy && sx.SequenceEqual(sy)); + } } } diff --git a/MoreLinq.Test/PipeTest.cs b/MoreLinq.Test/PipeTest.cs index de119d50c..33eeb1f6c 100644 --- a/MoreLinq.Test/PipeTest.cs +++ b/MoreLinq.Test/PipeTest.cs @@ -48,7 +48,7 @@ public void PipeActionOccursBeforeYield() var source = new[] { new StringBuilder(), new StringBuilder() }; // The action will occur "in" the pipe, so by the time Where gets it, the // sequence will be empty. - Assert.That(source.Pipe(sb => sb.Append("x")) + Assert.That(source.Pipe(sb => sb.Append('x')) .Where(x => x.Length == 0), Is.Empty); } diff --git a/MoreLinq.Test/PrependTest.cs b/MoreLinq.Test/PrependTest.cs index 381e1deba..a13e3fb77 100644 --- a/MoreLinq.Test/PrependTest.cs +++ b/MoreLinq.Test/PrependTest.cs @@ -46,7 +46,7 @@ public void PrependWithEmptyTailSequence() public void PrependWithNullHead() { string[] tail = { "second", "third" }; - string head = null; + string? head = null; var whole = tail.Prepend(head); whole.AssertSequenceEqual(null, "second", "third"); } diff --git a/MoreLinq.Test/Program.cs b/MoreLinq.Test/Program.cs index b37944246..80f6ddf0d 100644 --- a/MoreLinq.Test/Program.cs +++ b/MoreLinq.Test/Program.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2017 Atif Aziz. 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.Test { using System; @@ -7,8 +24,10 @@ namespace MoreLinq.Test static class Program { - static int Main(string[] args) => - new AutoRun(typeof(Program).GetTypeInfo().Assembly) - .Execute(args, new ExtendedTextWrapper(Console.Out), Console.In); + static int Main(string[] args) + { + using var writer = new ExtendedTextWrapper(Console.Out); + return new AutoRun(typeof(Program).GetTypeInfo().Assembly).Execute(args, writer, Console.In); + } } } diff --git a/MoreLinq.Test/RandomSubsetTest.cs b/MoreLinq.Test/RandomSubsetTest.cs index 2081ba60f..265abdbbb 100644 --- a/MoreLinq.Test/RandomSubsetTest.cs +++ b/MoreLinq.Test/RandomSubsetTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; @@ -26,8 +43,8 @@ public void TestRandomSubsetIsLazy() [Test] public void TestRandomSubsetNegativeSubsetSize() { - AssertThrowsArgument.OutOfRangeException("subsetSize", () => - Enumerable.Range(1, 10).RandomSubset(-5)); + Assert.That(() => Enumerable.Range(1, 10).RandomSubset(-5), + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -36,8 +53,8 @@ public void TestRandomSubsetNegativeSubsetSize() [Test] public void TestRandomSubsetNegativeSubsetSize2() { - AssertThrowsArgument.OutOfRangeException("subsetSize", () => - Enumerable.Range(1, 10).RandomSubset(-1, new Random())); + Assert.That(() => Enumerable.Range(1, 10).RandomSubset(-1, new Random()), + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -49,7 +66,7 @@ public void TestRandomSubsetOfEmptySequence() var sequence = Enumerable.Empty(); var result = sequence.RandomSubset(0); // we can only get subsets <= sequence.Count() - Assert.AreEqual(0, result.Count()); + Assert.That(result.Count(), Is.Zero); } /// @@ -64,8 +81,8 @@ public void TestRandomSubsetSameLengthAsSequence() var resultB = sequence.RandomSubset(count, new Random(12345)); // ensure random subset is always a complete reordering of original sequence - Assert.AreEqual(count, resultA.Distinct().Count()); - Assert.AreEqual(count, resultB.Distinct().Count()); + Assert.That(resultA.Distinct().Count(), Is.EqualTo(count)); + Assert.That(resultB.Distinct().Count(), Is.EqualTo(count)); } /// @@ -81,8 +98,8 @@ public void TestRandomSubsetShorterThanSequence() var resultB = sequence.RandomSubset(subsetSize, new Random(12345)); // ensure random subset is always a distinct subset of original sequence - Assert.AreEqual(subsetSize, resultA.Distinct().Count()); - Assert.AreEqual(subsetSize, resultB.Distinct().Count()); + Assert.That(resultA.Distinct().Count(), Is.EqualTo(subsetSize)); + Assert.That(resultB.Distinct().Count(), Is.EqualTo(subsetSize)); } /// @@ -96,10 +113,8 @@ public void TestRandomSubsetLongerThanSequence() const int subsetSize = count + 5; var sequence = Enumerable.Range(1, count); - AssertThrowsArgument.OutOfRangeException("subsetSize", () => - { - sequence.RandomSubset(subsetSize).Consume(); - }); + Assert.That(() => sequence.RandomSubset(subsetSize).Consume(), + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -113,10 +128,8 @@ public void TestRandomSubsetLongerThanSequence2() const int subsetSize = count + 5; var sequence = Enumerable.Range(1, count); - AssertThrowsArgument.OutOfRangeException("subsetSize", () => - { - sequence.RandomSubset(subsetSize, new Random(1234)).Consume(); - }); + Assert.That(() => sequence.RandomSubset(subsetSize, new Random(1234)).Consume(), + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -169,10 +182,10 @@ public void TestRandomSubsetIsUnbiased() // ensure that wth increasing trial size the a RSD% continually decreases for (var j = 0; j < rsdResults.Length - 1; j++) - Assert.Less(rsdResults[j + 1], rsdResults[j]); + Assert.That(rsdResults[j + 1], Is.LessThan(rsdResults[j])); // ensure that the RSD% for the 5M trial size is < 1.0 (this is somewhat arbitrary) - Assert.Less(rsdResults.Last(), 1.0); + Assert.That(rsdResults.Last(), Is.LessThan(1.0)); // for sanity, we output the RSD% values as a cross-check, the expected result should be // that the RSD% rapidly decreases and eventually drops below 1.0 diff --git a/MoreLinq.Test/RandomTest.cs b/MoreLinq.Test/RandomTest.cs index e90ff4eef..3a25d5b4b 100644 --- a/MoreLinq.Test/RandomTest.cs +++ b/MoreLinq.Test/RandomTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; @@ -19,10 +36,10 @@ public class RandomTest public void TestNegativeMaxValueException() { const int maxValue = -10; - Assert.Less(maxValue, 0); + Assert.That(maxValue, Is.LessThan(0)); - AssertThrowsArgument.OutOfRangeException("maxValue",() => - MoreEnumerable.Random(maxValue)); + Assert.That(() => MoreEnumerable.Random(maxValue), + Throws.ArgumentOutOfRangeException("maxValue")); } /// @@ -35,10 +52,10 @@ public void TestMinValueGreaterThanMaxValueException() const int minValue = 100; const int maxValue = 10; - Assert.Greater(minValue, maxValue); + Assert.That(minValue, Is.GreaterThan(maxValue)); - AssertThrowsArgument.OutOfRangeException("minValue",() => - MoreEnumerable.Random(minValue, maxValue)); + Assert.That(() => MoreEnumerable.Random(minValue, maxValue), + Throws.ArgumentOutOfRangeException("minValue")); } /// @@ -51,10 +68,10 @@ public void TestRandomDouble() var resultB = MoreEnumerable.RandomDouble(new Random()).Take(RandomTrials); // NOTE: Unclear what should actually be verified here... some additional thought needed. - Assert.AreEqual(RandomTrials, resultA.Count()); - Assert.AreEqual(RandomTrials, resultB.Count()); - Assert.IsTrue(resultA.All(x => x >= 0.0 && x < 1.0)); - Assert.IsTrue(resultB.All(x => x >= 0.0 && x < 1.0)); + Assert.That(resultA.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultB.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultA.All(x => x is >= 0.0 and < 1.0), Is.True); + Assert.That(resultB.All(x => x is >= 0.0 and < 1.0), Is.True); } /// @@ -67,10 +84,10 @@ public void TestRandomMaxConstraint() var resultA = MoreEnumerable.Random(max).Take(RandomTrials); var resultB = MoreEnumerable.Random(new Random(), max).Take(RandomTrials); - Assert.AreEqual(RandomTrials, resultA.Count()); - Assert.AreEqual(RandomTrials, resultB.Count()); - Assert.IsTrue(resultA.All(x => x < max)); - Assert.IsTrue(resultB.All(x => x < max)); + Assert.That(resultA.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultB.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultA.All(x => x < max), Is.True); + Assert.That(resultB.All(x => x < max), Is.True); } /// @@ -84,10 +101,10 @@ public void TestRandomMinMaxConstraint() var resultA = MoreEnumerable.Random(min, max).Take(RandomTrials); var resultB = MoreEnumerable.Random(new Random(), min, max).Take(RandomTrials); - Assert.AreEqual(RandomTrials, resultA.Count()); - Assert.AreEqual(RandomTrials, resultB.Count()); - Assert.IsTrue(resultA.All(x => x >= min && x < max)); - Assert.IsTrue(resultB.All(x => x >= min && x < max)); + Assert.That(resultA.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultB.Count(), Is.EqualTo(RandomTrials)); + Assert.That(resultA.All(x => x is >= min and < max), Is.True); + Assert.That(resultB.All(x => x is >= min and < max), Is.True); } /// diff --git a/MoreLinq.Test/RankTest.cs b/MoreLinq.Test/RankTest.cs index a3e586af4..f63ec1def 100644 --- a/MoreLinq.Test/RankTest.cs +++ b/MoreLinq.Test/RankTest.cs @@ -1,6 +1,24 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; + using System.Collections.Generic; using NUnit.Framework; /// @@ -34,7 +52,8 @@ public void TestRankByIsLazy() public void TestRankNullComparer() { var sequence = Enumerable.Repeat(1, 10); - sequence.AsTestingSequence().Rank(null).AssertSequenceEqual(sequence); + using var ts = sequence.AsTestingSequence(); + ts.Rank(null).AssertSequenceEqual(sequence); } /// @@ -44,7 +63,8 @@ public void TestRankNullComparer() public void TestRankByNullComparer() { var sequence = Enumerable.Repeat(1, 10); - sequence.AsTestingSequence().RankBy(x => x, null).AssertSequenceEqual(sequence); + using var ts = sequence.AsTestingSequence(); + ts.RankBy(x => x, null).AssertSequenceEqual(sequence); } /// @@ -55,11 +75,11 @@ public void TestRankByNullComparer() public void TestRankDescendingSequence() { const int count = 100; - var sequence = Enumerable.Range(456, count).Reverse(); - var result = sequence.AsTestingSequence().Rank().ToArray(); + using var sequence = Enumerable.Range(456, count).Reverse().AsTestingSequence(); + var result = sequence.Rank().ToArray(); var expectedResult = Enumerable.Range(1, count); - Assert.AreEqual(count, result.Length); + Assert.That(result.Length, Is.EqualTo(count)); Assert.That(result, Is.EqualTo(expectedResult)); } @@ -71,11 +91,11 @@ public void TestRankDescendingSequence() public void TestRankByAscendingSeries() { const int count = 100; - var sequence = Enumerable.Range(456, count); - var result = sequence.AsTestingSequence().Rank().ToArray(); + using var sequence = Enumerable.Range(456, count).AsTestingSequence(); + var result = sequence.Rank().ToArray(); var expectedResult = Enumerable.Range(1, count).Reverse(); - Assert.AreEqual(count, result.Length); + Assert.That(result.Length, Is.EqualTo(count)); Assert.That(result, Is.EqualTo(expectedResult)); } @@ -86,10 +106,10 @@ public void TestRankByAscendingSeries() public void TestRankEquivalentItems() { const int count = 100; - var sequence = Enumerable.Repeat(1234, count); - var result = sequence.AsTestingSequence().Rank().ToArray(); + using var sequence = Enumerable.Repeat(1234, count).AsTestingSequence(); + var result = sequence.Rank().ToArray(); - Assert.AreEqual(count, result.Length); + Assert.That(result.Length, Is.EqualTo(count)); Assert.That(result, Is.EqualTo(Enumerable.Repeat(1, count))); } @@ -103,9 +123,10 @@ public void TestRankGroupedItems() var sequence = Enumerable.Range(0, count) .Concat(Enumerable.Range(0, count)) .Concat(Enumerable.Range(0, count)); - var result = sequence.AsTestingSequence().Rank(); + using var ts = sequence.AsTestingSequence(); + var result = ts.Rank(); - Assert.AreEqual(count, result.Distinct().Count()); + Assert.That(result.Distinct().Count(), Is.EqualTo(count)); Assert.That(result, Is.EqualTo(sequence.Reverse().Select(x => x + 1))); } @@ -116,10 +137,10 @@ public void TestRankGroupedItems() public void TestRankOfHighestItemIsOne() { const int count = 10; - var sequence = Enumerable.Range(1, count); - var result = sequence.AsTestingSequence().Rank(); + using var sequence = Enumerable.Range(1, count).AsTestingSequence(); + var result = sequence.Rank(); - Assert.AreEqual(1, result.OrderBy(x => x).First()); + Assert.That(result.OrderBy(x => x).First(), Is.EqualTo(1)); } /// @@ -139,9 +160,10 @@ public void TestRankByKeySelector() new { Name = "Jim", Age = 74, ExpectedRank = 1 }, new { Name = "Jes", Age = 11, ExpectedRank = 8 }, }; - var result = sequence.AsTestingSequence().RankBy(x => x.Age).ToArray(); + using var ts = sequence.AsTestingSequence(); + var result = ts.RankBy(x => x.Age).ToArray(); - Assert.AreEqual(sequence.Length, result.Length); + Assert.That(result.Length, Is.EqualTo(sequence.Length)); Assert.That(result, Is.EqualTo(sequence.Select(x => x.ExpectedRank))); } @@ -154,9 +176,11 @@ public void TestRankCustomComparer() const int count = 10; var ordinals = Enumerable.Range(1, count); var sequence = ordinals.Select( x => new DateTime(2010,x,20-x) ); - // invert the CompareTo operation to Rank in reverse order (ascening to descending) - var resultA = sequence.AsTestingSequence().Rank(Comparer.Create((a, b) => -a.CompareTo(b))); - var resultB = sequence.AsTestingSequence().RankBy(x => x.Day, Comparer.Create((a, b) => -a.CompareTo(b))); + // invert the CompareTo operation to Rank in reverse order (ascending to descending) + using var tsA = sequence.AsTestingSequence(); + var resultA = tsA.Rank(Comparer.Create((a, b) => -a.CompareTo(b))); + using var tsB = sequence.AsTestingSequence(); + var resultB = tsB.RankBy(x => x.Day, Comparer.Create((a, b) => -a.CompareTo(b))); Assert.That(resultA, Is.EqualTo(ordinals)); Assert.That(resultB, Is.EqualTo(ordinals.Reverse())); diff --git a/MoreLinq.Test/RepeatTest.cs b/MoreLinq.Test/RepeatTest.cs index 7a3023348..12c8b2bf6 100644 --- a/MoreLinq.Test/RepeatTest.cs +++ b/MoreLinq.Test/RepeatTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -45,8 +62,8 @@ public void TestRepeatBehavior() [Test] public void TestNegativeRepeatCount() { - AssertThrowsArgument.OutOfRangeException("count", () => - Enumerable.Range(1, 10).Repeat(-3)); + Assert.That(() => Enumerable.Range(1, 10).Repeat(-3), + Throws.ArgumentOutOfRangeException("count")); } /// @@ -56,11 +73,11 @@ public void TestNegativeRepeatCount() public void TestRepeatForeverBehaviorSingleElementList() { const int value = 3; - using (var sequence = new[] { value }.AsTestingSequence()) - { - var result = sequence.Repeat(); - Assert.IsTrue(result.Take(100).All(x => x == value)); - } + using var sequence = new[] { value }.AsTestingSequence(); + + var result = sequence.Repeat(); + + Assert.That(result.Take(100).All(x => x == value), Is.True); } /// diff --git a/MoreLinq.Test/ReturnTest.cs b/MoreLinq.Test/ReturnTest.cs index 0cf3f3ee9..fda98990b 100644 --- a/MoreLinq.Test/ReturnTest.cs +++ b/MoreLinq.Test/ReturnTest.cs @@ -22,11 +22,11 @@ namespace MoreLinq.Test using NUnit.Framework; using NUnit.Framework.Interfaces; - class ReturnTest + public class ReturnTest { static class SomeSingleton { - public static readonly object Item = new object(); + public static readonly object Item = new(); public static readonly IEnumerable Sequence = MoreEnumerable.Return(Item); public static IList List => (IList)Sequence; public static ICollection Collection => (ICollection)Sequence; @@ -34,8 +34,8 @@ static class SomeSingleton static class NullSingleton { - public static readonly IEnumerable Sequence = MoreEnumerable.Return(null); - public static IList List => (IList)Sequence; + public static readonly IEnumerable Sequence = MoreEnumerable.Return(null); + public static IList List => (IList)Sequence; } [Test] @@ -77,13 +77,13 @@ public void TestContainsDoesNotThrowWhenTheItemProvidedIsNull() [Test] public void TestIndexOfDoesNotThrowWhenTheItemProvidedIsNull() { - Assert.That(() => NullSingleton.List.IndexOf(new object()), Throws.Nothing); + Assert.That(() => SomeSingleton.List.IndexOf(new object()), Throws.Nothing); } [Test] public void TestIndexOfDoesNotThrowWhenTheItemContainedIsNull() { - Assert.That(() => SomeSingleton.List.IndexOf(null), Throws.Nothing); + Assert.That(() => NullSingleton.List.IndexOf(null), Throws.Nothing); } [Test] diff --git a/MoreLinq.Test/RightJoinTest.cs b/MoreLinq.Test/RightJoinTest.cs index 00177d6bf..a11386a45 100644 --- a/MoreLinq.Test/RightJoinTest.cs +++ b/MoreLinq.Test/RightJoinTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2015 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. @@ -32,10 +32,11 @@ public void RightJoinWithHomogeneousSequencesIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.RightJoin(ys, e => e, BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -44,11 +45,12 @@ public void RightJoinWithHomogeneousSequencesWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.RightJoin(ys, e => e, BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] @@ -57,10 +59,11 @@ public void RightJoinIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.RightJoin(ys, x => x.GetHashCode(), y => y, BreakingFunc.Of(), - BreakingFunc.Of())); + BreakingFunc.Of()), + Throws.Nothing); } [Test] @@ -69,11 +72,12 @@ public void RightJoinWithComparerIsLazy() var xs = new BreakingSequence(); var ys = new BreakingSequence(); - Assert.DoesNotThrow(() => + Assert.That(() => xs.RightJoin(ys, x => x.GetHashCode(), y => y, BreakingFunc.Of(), BreakingFunc.Of(), - comparer: null)); + comparer: null), + Throws.Nothing); } [Test] diff --git a/MoreLinq.Test/RunLengthEncodeTest.cs b/MoreLinq.Test/RunLengthEncodeTest.cs index 614a6bd32..707d61d55 100644 --- a/MoreLinq.Test/RunLengthEncodeTest.cs +++ b/MoreLinq.Test/RunLengthEncodeTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; @@ -29,7 +46,7 @@ public void TestRunLengthEncodeEmptySequence() var sequence = Enumerable.Empty(); var result = sequence.RunLengthEncode(); - Assert.That(result, Is.EqualTo(sequence.Select(x => new KeyValuePair(x, x)))); + Assert.That(result, Is.Empty); } /// @@ -39,10 +56,14 @@ public void TestRunLengthEncodeEmptySequence() public void TestRunLengthEncodeCustomComparer() { var sequence = new[] { "a", "A", "a", "b", "b", "B", "B" }; - var result = sequence.RunLengthEncode(StringComparer.CurrentCultureIgnoreCase) - .Select(kvp => new KeyValuePair(kvp.Key.ToLower(), kvp.Value)); - var expectedResult = new[] {new KeyValuePair("a", 3), - new KeyValuePair("b", 4)}; + + var result = sequence.RunLengthEncode(StringComparer.InvariantCultureIgnoreCase) + .Select(kvp => KeyValuePair.Create(kvp.Key.ToLowerInvariant(), kvp.Value)); + var expectedResult = new[] + { + KeyValuePair.Create("a", 3), + KeyValuePair.Create("b", 4) + }; Assert.That(result, Is.EqualTo(expectedResult)); } @@ -54,7 +75,7 @@ public void TestRunLengthEncodeCustomComparer() public void TestRunLengthEncodeResults() { var sequence = new[] { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6 }; - var expectedResult = Enumerable.Range(1, 6).Select(x => new KeyValuePair(x, x)); + var expectedResult = Enumerable.Range(1, 6).Select(x => KeyValuePair.Create(x, x)); var result = sequence.RunLengthEncode(); Assert.That(result, Is.EqualTo(expectedResult)); @@ -68,7 +89,7 @@ public void TestRunLengthEncodeNoRuns() { var sequence = Enumerable.Range(1, 10); var result = sequence.RunLengthEncode(); - var expectedResult = sequence.Select(x => new KeyValuePair(x, 1)); + var expectedResult = sequence.Select(x => KeyValuePair.Create(x, 1)); Assert.That(result, Is.EqualTo(expectedResult)); } @@ -84,7 +105,7 @@ public void TestRunLengthEncodeOneRun() const int repeatCount = 10; var sequence = Enumerable.Repeat(value, repeatCount); var result = sequence.RunLengthEncode(); - var expectedResult = new[] { new KeyValuePair(value, repeatCount) }; + var expectedResult = new[] { KeyValuePair.Create(value, repeatCount) }; Assert.That(result, Is.EqualTo(expectedResult)); } diff --git a/MoreLinq.Test/SampleData.cs b/MoreLinq.Test/SampleData.cs index 5a2b4c7f0..6e257d060 100644 --- a/MoreLinq.Test/SampleData.cs +++ b/MoreLinq.Test/SampleData.cs @@ -18,7 +18,6 @@ namespace MoreLinq.Test { using System; - using System.Collections.Generic; using System.Collections.ObjectModel; /// @@ -26,20 +25,17 @@ namespace MoreLinq.Test /// static class SampleData { - internal static readonly ReadOnlyCollection Strings = new ReadOnlyCollection( - new[] { "ax", "hello", "world", "aa", "ab", "ay", "az" }); + internal static readonly ReadOnlyCollection Strings = new(new[] + { + "ax", "hello", "world", "aa", "ab", "ay", "az" + }); - internal static readonly ReadOnlyCollection Values = - new ReadOnlyCollection(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + internal static readonly ReadOnlyCollection Values = new(new[] + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + }); internal static readonly Func Plus = (a, b) => a + b; internal static readonly Func Mul = (a, b) => a * b; - - internal static readonly IComparer ReverseCharComparer = new ReverseCharComparerImpl(); - - class ReverseCharComparerImpl : IComparer - { - public int Compare(char x, char y) => y.CompareTo(x); - } } } diff --git a/MoreLinq.Test/ScanByTest.cs b/MoreLinq.Test/ScanByTest.cs index 7a11a698b..40414fd3c 100644 --- a/MoreLinq.Test/ScanByTest.cs +++ b/MoreLinq.Test/ScanByTest.cs @@ -50,7 +50,7 @@ public void ScanBy() var result = source.ScanBy( item => item.First(), - key => (Element: default(string), Key: key, State: key - 1), + key => (Element: string.Empty, Key: key, State: key - 1), (state, key, item) => (item, char.ToUpperInvariant(key), state.State + 1)); result.AssertSequenceEqual( @@ -67,7 +67,7 @@ public void ScanBy() [Test] public void ScanByWithSecondOccurenceImmediatelyAfterFirst() { - var result = "jaffer".ScanBy(c => c, k => -1, (i, k, e) => i + 1); + var result = "jaffer".ScanBy(c => c, _ => -1, (i, _, _) => i + 1); result.AssertSequenceEqual( KeyValuePair.Create('j', 0), @@ -83,8 +83,8 @@ public void ScanByWithEqualityComparer() { var source = new[] { "a", "B", "c", "A", "b", "A" }; var result = source.ScanBy(c => c, - k => -1, - (i, k, e) => i + 1, + _ => -1, + (i, _, _) => i + 1, StringComparer.OrdinalIgnoreCase); result.AssertSequenceEqual( @@ -100,19 +100,34 @@ public void ScanByWithEqualityComparer() public void ScanByWithSomeNullKeys() { var source = new[] { "foo", null, "bar", "baz", null, null, "baz", "bar", null, "foo" }; - var result = source.ScanBy(c => c, k => -1, (i, k, e) => i + 1); + var result = source.ScanBy(c => c, _ => -1, (i, _, _) => i + 1); result.AssertSequenceEqual( - KeyValuePair.Create("foo" , 0), - KeyValuePair.Create((string)null, 0), - KeyValuePair.Create("bar" , 0), - KeyValuePair.Create("baz" , 0), - KeyValuePair.Create((string)null, 1), - KeyValuePair.Create((string)null, 2), - KeyValuePair.Create("baz" , 1), - KeyValuePair.Create("bar" , 1), - KeyValuePair.Create((string)null, 3), - KeyValuePair.Create("foo" , 1)); + KeyValuePair.Create((string?)"foo", 0), + KeyValuePair.Create((string?)null , 0), + KeyValuePair.Create((string?)"bar", 0), + KeyValuePair.Create((string?)"baz", 0), + KeyValuePair.Create((string?)null , 1), + KeyValuePair.Create((string?)null , 2), + KeyValuePair.Create((string?)"baz", 1), + KeyValuePair.Create((string?)"bar", 1), + KeyValuePair.Create((string?)null , 3), + KeyValuePair.Create((string?)"foo", 1)); + } + + [Test] + public void ScanByWithNullSeed() + { + var nil = (object?)null; + var source = new[] { "foo", null, "bar", null, "baz" }; + var result = source.ScanBy(c => c, _ => nil, (_, _, _) => nil); + + result.AssertSequenceEqual( + KeyValuePair.Create((string?)"foo", nil), + KeyValuePair.Create((string?)null , nil), + KeyValuePair.Create((string?)"bar", nil), + KeyValuePair.Create((string?)null , nil), + KeyValuePair.Create((string?)"baz", nil)); } [Test] @@ -127,7 +142,7 @@ public void ScanByDoesNotIterateUnnecessaryElements() () => "angelo", () => "carlos"); - var result = source.ScanBy(c => c.First(), k => -1, (i, k, e) => i + 1); + var result = source.ScanBy(c => c.First(), _ => -1, (i, _, _) => i + 1); result.Take(5).AssertSequenceEqual( KeyValuePair.Create('a', 0), @@ -136,8 +151,8 @@ public void ScanByDoesNotIterateUnnecessaryElements() KeyValuePair.Create('b', 1), KeyValuePair.Create('d', 0)); - Assert.Throws(() => - result.ElementAt(5)); + Assert.That(() => result.ElementAt(5), + Throws.TypeOf()); } } } diff --git a/MoreLinq.Test/ScanRightTest.cs b/MoreLinq.Test/ScanRightTest.cs index 3cb334612..711f9ed68 100644 --- a/MoreLinq.Test/ScanRightTest.cs +++ b/MoreLinq.Test/ScanRightTest.cs @@ -62,9 +62,9 @@ public void ScanRightFuncIsNotInvokedOnSingleElementSequence() public void ScanRight(SourceKind sourceKind) { var result = Enumerable.Range(1, 5) - .Select(x => x.ToString()) + .Select(x => x.ToInvariantString()) .ToSourceKind(sourceKind) - .ScanRight((a, b) => string.Format("({0}+{1})", a, b)); + .ScanRight((a, b) => $"({a}+{b})"); var expectations = new[] { "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" }; @@ -84,7 +84,7 @@ public void ScanRightIsLazy() [TestCase(true)] public void ScanRightSeedWithEmptySequence(object defaultValue) { - Assert.That(new int[0].ScanRight(defaultValue, (a, b) => b), Is.EqualTo(new[] { defaultValue })); + Assert.That(new int[0].ScanRight(defaultValue, (_, b) => b), Is.EqualTo(new[] { defaultValue })); } [Test] @@ -101,7 +101,7 @@ public void ScanRightSeedFuncIsNotInvokedOnEmptySequence() public void ScanRightSeed() { var result = Enumerable.Range(1, 4) - .ScanRight("5", (a, b) => string.Format("({0}+{1})", a, b)); + .ScanRight("5", (a, b) => $"({a}+{b})"); var expectations = new[] { "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" }; diff --git a/MoreLinq.Test/ScanTest.cs b/MoreLinq.Test/ScanTest.cs index ac6abaa32..65e6eed10 100644 --- a/MoreLinq.Test/ScanTest.cs +++ b/MoreLinq.Test/ScanTest.cs @@ -17,7 +17,6 @@ namespace MoreLinq.Test { - using System; using NUnit.Framework; [TestFixture] @@ -48,14 +47,14 @@ public void ScanDoesNotIterateExtra() { var sequence = Enumerable.Range(1, 3).Concat(new BreakingSequence()).Scan(SampleData.Plus); var gold = new[] {1, 3, 6}; - Assert.Throws(sequence.Consume); + Assert.That(sequence.Consume, Throws.BreakException); sequence.Take(3).AssertSequenceEqual(gold); } [Test] public void SeededScanEmpty() { - Assert.AreEqual(-1, new int[0].Scan(-1, SampleData.Plus).Single()); + Assert.That(new int[0].Scan(-1, SampleData.Plus).Single(), Is.EqualTo(-1)); } [Test] @@ -69,7 +68,7 @@ public void SeededScanSum() [Test] public void SeededScanIsLazy() { - new BreakingSequence().Scan(null, BreakingFunc.Of()); + new BreakingSequence().Scan(null, BreakingFunc.Of()); } [Test] @@ -77,7 +76,7 @@ public void SeededScanDoesNotIterateExtra() { var sequence = Enumerable.Range(1, 3).Concat(new BreakingSequence()).Scan(0, SampleData.Plus); var gold = new[] { 0, 1, 3, 6 }; - Assert.Throws(sequence.Consume); + Assert.That(sequence.Consume, Throws.BreakException); sequence.Take(4).AssertSequenceEqual(gold); } diff --git a/MoreLinq.Test/SegmentTest.cs b/MoreLinq.Test/SegmentTest.cs index bfde0301e..3ece846b5 100644 --- a/MoreLinq.Test/SegmentTest.cs +++ b/MoreLinq.Test/SegmentTest.cs @@ -1,5 +1,24 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { + using System.Collections.Generic; + using NUnit.Framework.Interfaces; using NUnit.Framework; /// @@ -27,7 +46,7 @@ public void TestIdentitySegment() { const int count = 5; var sequence = Enumerable.Range(1, count); - var result = sequence.Segment(x => false); + var result = sequence.Segment(_ => false); Assert.That(result.Single(), Is.EqualTo(sequence)); } @@ -39,7 +58,7 @@ public void TestIdentitySegment() public void TestEmptySequence() { var sequence = Enumerable.Repeat(-1, 0); - var result = sequence.Segment(x => true); + var result = sequence.Segment(_ => true); Assert.That(result, Is.Empty); } @@ -51,14 +70,14 @@ public void TestSegmentIsIdempotent() { const int value = -1; var sequence = Enumerable.Repeat(value, 10); - var result = sequence.Segment(x => true); + var result = sequence.Segment(_ => true); foreach (var segment in result) { for (var i = 0; i < 2; i++) { - Assert.IsTrue(segment.Any()); - Assert.AreEqual(value, segment.Single()); + Assert.That(segment.Any(), Is.True); + Assert.That(segment.Single(), Is.EqualTo(value)); } } } @@ -71,13 +90,13 @@ public void TestSegmentIsIdempotent() public void TestFirstSegmentNeverEmpty() { var sequence = Enumerable.Repeat(-1, 10); - var resultA = sequence.Segment(x => true); - var resultB = sequence.Segment((x, index) => true); - var resultC = sequence.Segment((x, prevX, index) => true); + var resultA = sequence.Segment(_ => true); + var resultB = sequence.Segment((_, _) => true); + var resultC = sequence.Segment((_, _, _) => true); - Assert.IsTrue(resultA.First().Any()); - Assert.IsTrue(resultB.First().Any()); - Assert.IsTrue(resultC.First().Any()); + Assert.That(resultA.First().Any(), Is.True); + Assert.That(resultB.First().Any(), Is.True); + Assert.That(resultC.First().Any(), Is.True); } /// @@ -91,9 +110,9 @@ public void TestSegmentationStartsWithSecondItem() var resultB = sequence.Segment(BreakingFunc.Of()); var resultC = sequence.Segment(BreakingFunc.Of()); - Assert.IsTrue(resultA.Any()); - Assert.IsTrue(resultB.Any()); - Assert.IsTrue(resultC.Any()); + Assert.That(resultA.Any(), Is.True); + Assert.That(resultB.Any(), Is.True); + Assert.That(resultC.Any(), Is.True); } /// @@ -106,12 +125,12 @@ public void VerifyCanSegmentByIndex() const int segmentSize = 2; var sequence = Enumerable.Repeat(1, count); - var result = sequence.Segment((x, i) => i % segmentSize == 0); + var result = sequence.Segment((_, i) => i % segmentSize == 0); - Assert.AreEqual(count / segmentSize, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count / segmentSize)); foreach (var segment in result) { - Assert.AreEqual(segmentSize, segment.Count()); + Assert.That(segment.Count(), Is.EqualTo(segmentSize)); } } @@ -124,10 +143,35 @@ public void VerifyCanSegmentByPrevious() const int repCount = 5; var sequence = Enumerable.Range(1, 3) .SelectMany(x => Enumerable.Repeat(x, repCount)); - var result = sequence.Segment((curr, prev, i) => curr != prev); + var result = sequence.Segment((curr, prev, _) => curr != prev); + + Assert.That(result.Count(), Is.EqualTo(sequence.Distinct().Count())); + Assert.That(result.All(s => s.Count() == repCount), Is.True); + } + + static IEnumerable Seq(params T[] values) => values; - Assert.AreEqual(sequence.Distinct().Count(), result.Count()); - Assert.IsTrue(result.All(s => s.Count() == repCount)); + public static readonly IEnumerable TestData = + from e in new[] + { + // input sequence is empty + new { Source = Seq(), Expected = Seq>() }, + // input sequence contains only new segment start + new { Source = Seq(0, 3, 6), Expected = Seq(Seq(0), Seq(3), Seq(6)) }, + // input sequence do not contains new segment start + new { Source = Seq(1, 2, 4, 5), Expected = Seq(Seq(1, 2, 4, 5)) }, + // input sequence start with a segment start + new { Source = Seq(0, 1, 2, 3, 4, 5), Expected = Seq(Seq(0, 1, 2), Seq(3, 4, 5)) }, + // input sequence do not start with a segment start + new { Source = Seq(1, 2, 3, 4, 5), Expected = Seq(Seq(1, 2), Seq(3, 4, 5)) } + } + select new TestCaseData(e.Source).Returns(e.Expected); + + [Test, TestCaseSource(nameof(TestData))] + public IEnumerable> TestSegment(IEnumerable source) + { + using var ts = source.AsTestingSequence(); + return ts.Segment(v => v % 3 == 0); } } } diff --git a/MoreLinq.Test/SequenceReader.cs b/MoreLinq.Test/SequenceReader.cs index 1ff809035..5a2a0cba6 100644 --- a/MoreLinq.Test/SequenceReader.cs +++ b/MoreLinq.Test/SequenceReader.cs @@ -35,9 +35,9 @@ public static SequenceReader Read(this IEnumerable source) /// "read" operation. /// /// Type of elements to read. - class SequenceReader : IDisposable + sealed class SequenceReader : IDisposable { - IEnumerator _enumerator; + IEnumerator? _enumerator; /// /// Initializes a instance @@ -63,84 +63,46 @@ static IEnumerator GetEnumerator(IEnumerable source) return source.GetEnumerator(); } + IEnumerator Enumerator => + _enumerator ?? throw new ObjectDisposedException(GetType().FullName); + /// - /// Tires to read the next value. + /// Reads a value otherwise throws + /// if no more values are available. /// - /// - /// When this method returns, contains the value read on success. - /// /// - /// Returns true if a value was successfully read; otherwise, false. + /// Returns the read value; /// - public virtual bool TryRead(out T value) + public T Read() { - EnsureNotDisposed(); + var e = Enumerator; - value = default; - - var e = _enumerator; if (!e.MoveNext()) - return false; + throw new InvalidOperationException(); - value = e.Current; - return true; + return e.Current; } - /// - /// Tires to read the next value otherwise return the default. - /// - - public T TryRead() => TryRead(default); - - /// - /// Tires to read the next value otherwise return a given default. - /// - - public T TryRead(T defaultValue) => - TryRead(out var result) ? result : defaultValue; - - /// - /// Reads a value otherwise throws - /// if no more values are available. - /// - /// - /// Returns the read value; - /// - - public T Read() => - TryRead(out var result) ? result : throw new InvalidOperationException(); - /// /// Reads the end. If the end has not been reached then it /// throws . /// - public virtual void ReadEnd() + public void ReadEnd() { - EnsureNotDisposed(); + var enumerator = Enumerator; - if (_enumerator.MoveNext()) + if (enumerator.MoveNext()) throw new InvalidOperationException(); } - /// - /// Ensures that this object has not been disposed, that - /// has not been previously called. - /// - - protected void EnsureNotDisposed() - { - if (_enumerator == null) - throw new ObjectDisposedException(GetType().FullName); - } - /// /// Disposes this object and enumerator with which is was /// initialized. /// - public virtual void Dispose() + public void Dispose() { var e = _enumerator; if (e == null) return; diff --git a/MoreLinq.Test/SequenceTest.cs b/MoreLinq.Test/SequenceTest.cs index d2e288be7..ad01a274d 100644 --- a/MoreLinq.Test/SequenceTest.cs +++ b/MoreLinq.Test/SequenceTest.cs @@ -124,7 +124,7 @@ public void SequenceEdgeCases(int start, int stop, int step, int count) { var result = MoreEnumerable.Sequence(start, stop, step); - Assert.AreEqual(result.Count(), count); + Assert.That(result.Count(), Is.EqualTo(count)); } [TestCase( 5, 10)] @@ -134,7 +134,7 @@ public void SequenceWithStepZero(int start, int stop) { var result = MoreEnumerable.Sequence(start, stop, 0); - Assert.IsTrue(result.Take(100).All(x => x == start)); + Assert.That(result.Take(100).All(x => x == start), Is.True); } } } diff --git a/MoreLinq.Test/ShuffleTest.cs b/MoreLinq.Test/ShuffleTest.cs index 2bd04e3a1..eeb82edc0 100644 --- a/MoreLinq.Test/ShuffleTest.cs +++ b/MoreLinq.Test/ShuffleTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 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.Test { using System; @@ -6,7 +23,7 @@ namespace MoreLinq.Test [TestFixture] public class ShuffleTest { - static Random seed = new Random(12345); + static Random seed = new(12345); [Test] public void ShuffleIsLazy() diff --git a/MoreLinq.Test/SkipUntilTest.cs b/MoreLinq.Test/SkipUntilTest.cs index 066ffe643..373945869 100644 --- a/MoreLinq.Test/SkipUntilTest.cs +++ b/MoreLinq.Test/SkipUntilTest.cs @@ -18,6 +18,8 @@ namespace MoreLinq.Test { using NUnit.Framework; + using NUnit.Framework.Interfaces; + using System.Collections.Generic; [TestFixture] public class SkipUntilTest @@ -57,5 +59,26 @@ public void SkipUntilEvaluatesPredicateLazily() var sequence = Enumerable.Range(-2, 5).SkipUntil(x => 1 / x == -1); sequence.AssertSequenceEqual(0, 1, 2); } + + public static readonly IEnumerable TestData = + from e in new[] + { + new { Source = new int[0] , Min = 0, Expected = new int[0] }, // empty sequence + new { Source = new[] { 0 }, Min = 0, Expected = new int[0] }, // one-item sequence, predicate succeed + new { Source = new[] { 0 }, Min = 1, Expected = new int[0] }, // one-item sequence, predicate don't succeed + new { Source = new[] { 1, 2, 3 }, Min = 0, Expected = new[] { 2, 3 } }, // predicate succeed on first item + new { Source = new[] { 1, 2, 3 }, Min = 1, Expected = new[] { 2, 3 } }, + new { Source = new[] { 1, 2, 3 }, Min = 2, Expected = new[] { 3 } }, + new { Source = new[] { 1, 2, 3 }, Min = 3, Expected = new int[0] }, // predicate succeed on last item + new { Source = new[] { 1, 2, 3 }, Min = 4, Expected = new int[0] } // predicate never succeed + } + select new TestCaseData(e.Source, e.Min).Returns(e.Expected); + + [Test, TestCaseSource(nameof(TestData))] + public int[] TestSkipUntil(int[] source, int min) + { + using var ts = source.AsTestingSequence(); + return ts.SkipUntil(v => v >= min).ToArray(); + } } } diff --git a/MoreLinq.Test/SliceTest.cs b/MoreLinq.Test/SliceTest.cs index 984da8811..e5fd11c56 100644 --- a/MoreLinq.Test/SliceTest.cs +++ b/MoreLinq.Test/SliceTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using NUnit.Framework; @@ -125,8 +142,8 @@ public void TestSliceOptimization(SourceKind sourceKind) var result = sequence.Slice(sliceStart, sliceCount); - Assert.AreEqual(sliceCount, result.Count()); - CollectionAssert.AreEqual(Enumerable.Range(5, sliceCount), result); + Assert.That(result.Count(), Is.EqualTo(sliceCount)); + Assert.That(Enumerable.Range(5, sliceCount), Is.EqualTo(result)); } } } diff --git a/MoreLinq.Test/SortedMergeTest.cs b/MoreLinq.Test/SortedMergeTest.cs index 061f57949..4428e4c5b 100644 --- a/MoreLinq.Test/SortedMergeTest.cs +++ b/MoreLinq.Test/SortedMergeTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; @@ -29,12 +46,12 @@ public void TestSortedMergeIsLazy() [Test] public void TestSortedMergeDisposesOnError() { - using (var sequenceA = TestingSequence.Of()) - { - // Expected and thrown by BreakingSequence - Assert.Throws(() => - sequenceA.SortedMerge(OrderByDirection.Ascending, new BreakingSequence()).Consume()); - } + using var sequenceA = TestingSequence.Of(); + + // Expected and thrown by BreakingSequence + Assert.That(() => sequenceA.SortedMerge(OrderByDirection.Ascending, new BreakingSequence()) + .Consume(), + Throws.BreakException); } /// @@ -45,7 +62,7 @@ public void TestSortedMergeComparerNull() { var sequenceA = Enumerable.Range(1, 3); var sequenceB = Enumerable.Range(4, 3); - var result = sequenceA.SortedMerge(OrderByDirection.Ascending, (IComparer)null, sequenceB); + var result = sequenceA.SortedMerge(OrderByDirection.Ascending, (IComparer?)null, sequenceB); Assert.That(result, Is.EqualTo(sequenceA.Concat(sequenceB))); } @@ -149,9 +166,10 @@ public void TestSortedMergeCustomComparer() var sequenceA = new[] { "a", "D", "G", "h", "i", "J", "O", "t", "z" }; var sequenceB = new[] { "b", "E", "k", "q", "r", "u", "V", "x", "Y" }; var sequenceC = new[] { "C", "F", "l", "m", "N", "P", "s", "w" }; + var comparer = StringComparer.InvariantCultureIgnoreCase; var expectedResult = sequenceA.Concat(sequenceB).Concat(sequenceC) - .OrderBy(a => a, StringComparer.CurrentCultureIgnoreCase); - var result = sequenceA.SortedMerge(OrderByDirection.Ascending, sequenceB, sequenceC); + .OrderBy(a => a, comparer); + var result = sequenceA.SortedMerge(OrderByDirection.Ascending, comparer, sequenceB, sequenceC); Assert.That(result, Is.EqualTo(expectedResult)); } @@ -163,14 +181,14 @@ public void TestSortedMergeCustomComparer() public void TestSortedMergeAllSequencesDisposed() { const int count = 10; - using (var sequenceA = Enumerable.Range(1, count).AsTestingSequence()) - using (var sequenceB = Enumerable.Range(1, count - 1).AsTestingSequence()) - using (var sequenceC = Enumerable.Range(1, count - 5).AsTestingSequence()) - using (var sequenceD = Enumerable.Range(1, 0).AsTestingSequence()) - { - sequenceA.SortedMerge(OrderByDirection.Ascending, sequenceB, sequenceC, sequenceD) - .Consume(); // ensures the sequences are actually merged and iterators are obtained - } + + using var sequenceA = Enumerable.Range(1, count).AsTestingSequence(); + using var sequenceB = Enumerable.Range(1, count - 1).AsTestingSequence(); + using var sequenceC = Enumerable.Range(1, count - 5).AsTestingSequence(); + using var sequenceD = Enumerable.Range(1, 0).AsTestingSequence(); + + sequenceA.SortedMerge(OrderByDirection.Ascending, sequenceB, sequenceC, sequenceD) + .Consume(); // ensures the sequences are actually merged and iterators are obtained } } } diff --git a/MoreLinq.Test/SplitTest.cs b/MoreLinq.Test/SplitTest.cs index 9026cd93c..b7f6044de 100644 --- a/MoreLinq.Test/SplitTest.cs +++ b/MoreLinq.Test/SplitTest.cs @@ -40,25 +40,23 @@ public void SplitUptoMaxCount() public void SplitWithSeparatorSelector() { var result = new int?[] { 1, 2, null, 3, null, 4, 5, 6 }.Split(n => n == null); - using (var reader = result.Read()) - { - reader.Read().AssertSequenceEqual(1, 2); - reader.Read().AssertSequenceEqual(3); - reader.Read().AssertSequenceEqual(4, 5, 6); - reader.ReadEnd(); - } + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2); + reader.Read().AssertSequenceEqual(3); + reader.Read().AssertSequenceEqual(4, 5, 6); + reader.ReadEnd(); } [Test] public void SplitWithSeparatorSelectorUptoMaxCount() { var result = new int?[] { 1, 2, null, 3, null, 4, 5, 6 }.Split(n => n == null, 1); - using (var reader = result.Read()) - { - reader.Read().AssertSequenceEqual(1, 2); - reader.Read().AssertSequenceEqual(3, null, 4, 5, 6); - reader.ReadEnd(); - } + + using var reader = result.Read(); + reader.Read().AssertSequenceEqual(1, 2); + reader.Read().AssertSequenceEqual(3, null, 4, 5, 6); + reader.ReadEnd(); } } } diff --git a/MoreLinq.Test/StartsWithTest.cs b/MoreLinq.Test/StartsWithTest.cs index 88c22de17..99d3f01c0 100644 --- a/MoreLinq.Test/StartsWithTest.cs +++ b/MoreLinq.Test/StartsWithTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2016 Andreas Gullberg Larsen (angularsen). 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. @@ -52,13 +52,13 @@ public bool StartsWithWithStrings(string first, string second) [Test] public void StartsWithReturnsTrueIfBothEmpty() { - Assert.True(new int[0].StartsWith(new int[0])); + Assert.That(new int[0].StartsWith(new int[0]), Is.True); } [Test] public void StartsWithReturnsFalseIfOnlyFirstIsEmpty() { - Assert.False(new int[0].StartsWith(new[] {1,2,3})); + Assert.That(new int[0].StartsWith(new[] {1,2,3}), Is.False); } [TestCase("", "", ExpectedResult = true)] @@ -72,11 +72,10 @@ public bool StartsWithReturnsTrueIfSecondIsEmpty(string first, string second) [Test] public void StartsWithDisposesBothSequenceEnumerators() { - using (var first = TestingSequence.Of(1,2,3)) - using (var second = TestingSequence.Of(1)) - { - first.StartsWith(second); - } + using var first = TestingSequence.Of(1,2,3); + using var second = TestingSequence.Of(1); + + first.StartsWith(second); } [Test] @@ -86,10 +85,10 @@ public void StartsWithUsesSpecifiedEqualityComparerOrDefault() var first = new[] {1,2,3}; var second = new[] {4,5,6}; - Assert.False(first.StartsWith(second)); - Assert.False(first.StartsWith(second, null)); - Assert.False(first.StartsWith(second, EqualityComparer.Create(delegate { return false; }))); - Assert.True(first.StartsWith(second, EqualityComparer.Create(delegate { return true; }))); + Assert.That(first.StartsWith(second), Is.False); + Assert.That(first.StartsWith(second, null), Is.False); + Assert.That(first.StartsWith(second, EqualityComparer.Create(delegate { return false; })), Is.False); + Assert.That(first.StartsWith(second, EqualityComparer.Create(delegate { return true; })), Is.True); } [TestCase(SourceKind.BreakingCollection)] @@ -99,7 +98,7 @@ public void StartsWithUsesCollectionsCountToAvoidUnnecessaryIteration(SourceKind var first = new[] { 1, 2 }.ToSourceKind(sourceKind); var second = new[] { 1, 2, 3 }.ToSourceKind(sourceKind); - Assert.False(first.StartsWith(second)); + Assert.That(first.StartsWith(second), Is.False); } } } diff --git a/MoreLinq.Test/SubjectTest.cs b/MoreLinq.Test/SubjectTest.cs index cd571589d..fe44b2485 100644 --- a/MoreLinq.Test/SubjectTest.cs +++ b/MoreLinq.Test/SubjectTest.cs @@ -25,9 +25,9 @@ namespace MoreLinq.Test public class SubjectTest { static IDisposable Subscribe(IObservable subject, - Action onNext = null, - Action onError = null, - Action onCompleted = null) => + Action? onNext = null, + Action? onError = null, + Action? onCompleted = null) => subject.Subscribe(onNext ?? BreakingAction.Of(), onError ?? BreakingAction.Of(), onCompleted ?? BreakingAction.WithoutArguments); @@ -36,8 +36,8 @@ static IDisposable Subscribe(IObservable subject, public void SubscribeWithNullObserverThrows() { var subject = new Subject(); - var e = Assert.Throws(() => subject.Subscribe(null)); - Assert.That(e.ParamName, Is.EqualTo("observer")); + Assert.That(() => subject.Subscribe(null!), + Throws.ArgumentNullException("observer")); } [Test] @@ -60,8 +60,8 @@ public void OnNextObservations() [Test] public void OnErrorObservations() { - Exception error1 = null; - Exception error2 = null; + Exception? error1 = null; + Exception? error2 = null; var subject = new Subject(); @@ -139,7 +139,7 @@ public void SubscriptionPostCompletion() [Test] public void SubscriptionPostError() { - Exception observedError = null; + Exception? observedError = null; var subject = new Subject(); var error = new TestException(); subject.OnError(error); @@ -215,8 +215,12 @@ public void ErrorsOnce() public void SafeToDisposeDuringOnNext() { var subject = new Subject(); - IDisposable subscription = null; - var action = new Action(() => subscription.Dispose()); + IDisposable? subscription = null; + var action = new Action(() => + { + Debug.Assert(subscription is not null); + subscription.Dispose(); + }); subscription = subject.Subscribe(_ => action()); subject.OnNext(42); action = BreakingAction.WithoutArguments; diff --git a/MoreLinq.Test/SubsetTest.cs b/MoreLinq.Test/SubsetTest.cs index cc4725a2d..e08174ef8 100644 --- a/MoreLinq.Test/SubsetTest.cs +++ b/MoreLinq.Test/SubsetTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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.Test { using System; @@ -28,8 +45,8 @@ public void TestNegativeSubsetSize() const int count = 10; var sequence = Enumerable.Range(1, count); - AssertThrowsArgument.OutOfRangeException("subsetSize",() => - sequence.Subsets(-5)); + Assert.That(() => sequence.Subsets(-5), + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -42,10 +59,8 @@ public void TestSubsetLargerThanSequence() var sequence = Enumerable.Range(1, count); var result = sequence.Subsets(count + 5); - AssertThrowsArgument.OutOfRangeException("subsetSize", () => - { - result.Consume(); // this particular exception is deferred until sequence evaluation - }); + Assert.That(result.Consume, // this particular exception is deferred until sequence evaluation + Throws.ArgumentOutOfRangeException("subsetSize")); } /// @@ -73,7 +88,7 @@ public void TestSubsetsInIncreasingOrder() var prevSubset = Enumerable.Empty(); foreach (var subset in result) { - Assert.GreaterOrEqual(subset.Count, prevSubset.Count()); + Assert.That(subset.Count, Is.GreaterThanOrEqualTo(prevSubset.Count())); prevSubset = subset; } } @@ -90,7 +105,7 @@ public void TestAllSubsetsExpectedCount() var expectedCount = Math.Pow(2, count); - Assert.AreEqual(expectedCount, result.Count()); + Assert.That(result.Count(), Is.EqualTo(expectedCount)); } /// @@ -130,7 +145,7 @@ public void TestKSubsetExpectedCount() // number of subsets of a given size is defined by the binomial coefficient: c! / ((c-s)!*s!) var expectedSubsetCount = Combinatorics.Binomial(count, subsetSize); - Assert.AreEqual(expectedSubsetCount, result.Count()); + Assert.That(result.Count(), Is.EqualTo(expectedSubsetCount)); } /// diff --git a/MoreLinq.Test/TagFirstLastTest.cs b/MoreLinq.Test/TagFirstLastTest.cs index b3017b6f9..484904a9d 100644 --- a/MoreLinq.Test/TagFirstLastTest.cs +++ b/MoreLinq.Test/TagFirstLastTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2013 Atif Aziz. 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. @@ -22,35 +22,56 @@ namespace MoreLinq.Test [TestFixture] public class TagFirstLastTest { + [Test] + public void TagFirstLastDoesOneLookAhead() + { + using var source = MoreEnumerable.From(() => 123, () => 456, BreakingFunc.Of()).AsTestingSequence(); + var result = source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }); + + result.Take(1).Consume(); + } + [Test] public void TagFirstLastIsLazy() { new BreakingSequence().TagFirstLast(BreakingFunc.Of()); } + [Test] + public void TagFirstLastWithSourceSequenceOfZero() + { + using var source = Enumerable.Empty().AsTestingSequence(); + var result = source.TagFirstLast(BreakingFunc.Of()); + + Assert.That(result, Is.Empty); + } + [Test] public void TagFirstLastWithSourceSequenceOfOne() { - var source = new[] { 123 }; - source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }) - .AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = true }); + using var source = new[] { 123 }.AsTestingSequence(); + var result = source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }); + + result.AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = true }); } [Test] public void TagFirstLastWithSourceSequenceOfTwo() { - var source = new[] { 123, 456 }; - source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }) - .AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = false }, + using var source = new[] { 123, 456 }.AsTestingSequence(); + var result = source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }); + + result.AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = false }, new { Item = 456, IsFirst = false, IsLast = true }); } [Test] public void TagFirstLastWithSourceSequenceOfThree() { - var source = new[] { 123, 456, 789 }; - source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }) - .AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = false }, + using var source = new[] { 123, 456, 789 }.AsTestingSequence(); + var result = source.TagFirstLast((item, isFirst, isLast) => new { Item = item, IsFirst = isFirst, IsLast = isLast }); + + result.AssertSequenceEqual(new { Item = 123, IsFirst = true, IsLast = false }, new { Item = 456, IsFirst = false, IsLast = false }, new { Item = 789, IsFirst = false, IsLast = true }); } diff --git a/MoreLinq.Test/TakeEveryTest.cs b/MoreLinq.Test/TakeEveryTest.cs index a10d9d5e9..a3fc857cf 100644 --- a/MoreLinq.Test/TakeEveryTest.cs +++ b/MoreLinq.Test/TakeEveryTest.cs @@ -25,15 +25,15 @@ public class TakeEveryTest [Test] public void TakeEveryNegativeSkip() { - AssertThrowsArgument.OutOfRangeException("step",() => - new object[0].TakeEvery(-1)); + Assert.That(() => new object[0].TakeEvery(-1), + Throws.ArgumentOutOfRangeException("step")); } [Test] public void TakeEveryOutOfRangeZeroStep() { - AssertThrowsArgument.OutOfRangeException("step", () => - new object[0].TakeEvery(0)); + Assert.That(() => new object[0].TakeEvery(0), + Throws.ArgumentOutOfRangeException("step")); } [Test] diff --git a/MoreLinq.Test/TakeLastTest.cs b/MoreLinq.Test/TakeLastTest.cs index 4c9e35660..31548d411 100644 --- a/MoreLinq.Test/TakeLastTest.cs +++ b/MoreLinq.Test/TakeLastTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2009 Atif Aziz. 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. @@ -57,10 +57,8 @@ public void TakeLastIsLazy() [Test] public void TakeLastDisposesSequenceEnumerator() { - using (var seq = TestingSequence.Of(1,2,3)) - { - seq.TakeLast(1).Consume(); - } + using var seq = TestingSequence.Of(1,2,3); + seq.TakeLast(1).Consume(); } [TestCase(SourceKind.BreakingList)] diff --git a/MoreLinq.Test/TestException.cs b/MoreLinq.Test/TestException.cs index 7100f9ba6..98cb7c715 100644 --- a/MoreLinq.Test/TestException.cs +++ b/MoreLinq.Test/TestException.cs @@ -21,5 +21,9 @@ namespace MoreLinq.Test /// Reserved for use within tests. /// - sealed class TestException : System.Exception {} + sealed class TestException : System.Exception + { + public TestException() : this(null) { } + public TestException(string? message) : base(message) { } + } } diff --git a/MoreLinq.Test/TestExtensions.cs b/MoreLinq.Test/TestExtensions.cs index dfd05f458..b6e12aee2 100644 --- a/MoreLinq.Test/TestExtensions.cs +++ b/MoreLinq.Test/TestExtensions.cs @@ -76,21 +76,15 @@ internal static IEnumerable> ArrangeCollectionTestCases(this I internal static IEnumerable ToSourceKind(this IEnumerable input, SourceKind sourceKind) { - switch (sourceKind) + return sourceKind switch { - case SourceKind.Sequence: - return input.Select(x => x); - case SourceKind.BreakingList: - return new BreakingList(input.ToList()); - case SourceKind.BreakingReadOnlyList: - return new BreakingReadOnlyList(input.ToList()); - case SourceKind.BreakingCollection: - return new BreakingCollection(input.ToList()); - case SourceKind.BreakingReadOnlyCollection: - return new BreakingReadOnlyCollection(input.ToList()); - default: - throw new ArgumentException(nameof(sourceKind)); - } + SourceKind.Sequence => input.Select(x => x), + SourceKind.BreakingList => new BreakingList(input.ToList()), + SourceKind.BreakingReadOnlyList => new BreakingReadOnlyList(input.ToList()), + SourceKind.BreakingCollection => new BreakingCollection(input.ToList()), + SourceKind.BreakingReadOnlyCollection => new BreakingReadOnlyCollection(input.ToList()), + _ => throw new ArgumentException(null, nameof(sourceKind)) + }; } } } diff --git a/MoreLinq.Test/TestingSequence.cs b/MoreLinq.Test/TestingSequence.cs index 3f9f56298..98572d7cc 100644 --- a/MoreLinq.Test/TestingSequence.cs +++ b/MoreLinq.Test/TestingSequence.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2009 Atif Aziz. 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. @@ -25,12 +25,23 @@ namespace MoreLinq.Test static class TestingSequence { internal static TestingSequence Of(params T[] elements) => - new TestingSequence(elements); + Of(Options.None, elements); - internal static TestingSequence AsTestingSequence(this IEnumerable source) => + internal static TestingSequence Of(Options options, params T[] elements) => + elements.AsTestingSequence(options); + + internal static TestingSequence AsTestingSequence(this IEnumerable source, + Options options = Options.None) => source != null - ? new TestingSequence(source) + ? new TestingSequence(source) { IsReiterationAllowed = options.HasFlag(Options.AllowMultipleEnumerations) } : throw new ArgumentNullException(nameof(source)); + + [Flags] + public enum Options + { + None, + AllowMultipleEnumerations + } } /// @@ -41,11 +52,13 @@ internal static TestingSequence AsTestingSequence(this IEnumerable sour sealed class TestingSequence : IEnumerable, IDisposable { bool? _disposed; - IEnumerable _sequence; + IEnumerable? _sequence; internal TestingSequence(IEnumerable sequence) => _sequence = sequence; + public bool IsDisposed => _disposed ?? false; + public bool IsReiterationAllowed { get; init; } public int MoveNextCallCount { get; private set; } void IDisposable.Dispose() => @@ -58,14 +71,18 @@ void AssertDisposed() { if (_disposed == null) return; - Assert.IsTrue(_disposed, "Expected sequence to be disposed."); + Assert.That(_disposed, Is.True, "Expected sequence to be disposed."); _disposed = null; } public IEnumerator GetEnumerator() { - Assert.That(_sequence, Is.Not.Null, "LINQ operators should not enumerate a sequence more than once."); - var enumerator = _sequence.GetEnumerator().AsWatchtable(); + if (!IsReiterationAllowed) + Assert.That(_sequence, Is.Not.Null, "LINQ operators should not enumerate a sequence more than once."); + + Debug.Assert(_sequence is not null); + + var enumerator = _sequence.GetEnumerator().AsWatchable(); _disposed = false; enumerator.Disposed += delegate { @@ -79,7 +96,10 @@ public IEnumerator GetEnumerator() ended = !moved; MoveNextCallCount++; }; - _sequence = null; + + if (!IsReiterationAllowed) + _sequence = null; + return enumerator; } diff --git a/MoreLinq.Test/Throws.cs b/MoreLinq.Test/Throws.cs new file mode 100644 index 000000000..672234e3c --- /dev/null +++ b/MoreLinq.Test/Throws.cs @@ -0,0 +1,53 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. 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.Test +{ + using System; + using NUnit.Framework.Constraints; + + static class Throws + { + public static ThrowsNothingConstraint Nothing => NUnit.Framework.Throws.Nothing; + public static ExactTypeConstraint InvalidOperationException => NUnit.Framework.Throws.InvalidOperationException; + public static ExactTypeConstraint ObjectDisposedException => TypeOf(); + public static ExactTypeConstraint BreakException => TypeOf(); + + public static InstanceOfTypeConstraint InstanceOf() + where T : Exception => + NUnit.Framework.Throws.InstanceOf(); + + public static ExactTypeConstraint TypeOf() + where T : Exception => + NUnit.Framework.Throws.TypeOf(); + + public static EqualConstraint ArgumentException(string expectedParamName) => + NUnit.Framework.Throws.ArgumentException.With.ParamName().EqualTo(expectedParamName); + + public static EqualConstraint ArgumentNullException(string expectedParamName) => + NUnit.Framework.Throws.ArgumentNullException.With.ParamName().EqualTo(expectedParamName); + + public static ExactTypeConstraint ArgumentOutOfRangeException() => + TypeOf(); + + public static EqualConstraint ArgumentOutOfRangeException(string expectedParamName) => + ArgumentOutOfRangeException().With.ParamName().EqualTo(expectedParamName); + + static ResolvableConstraintExpression ParamName(this ConstraintExpression constraint) => + constraint.Property(nameof(System.ArgumentException.ParamName)); + } +} diff --git a/MoreLinq.Test/ToArrayByIndexTest.cs b/MoreLinq.Test/ToArrayByIndexTest.cs index f5f06fb89..fcc02f64e 100644 --- a/MoreLinq.Test/ToArrayByIndexTest.cs +++ b/MoreLinq.Test/ToArrayByIndexTest.cs @@ -68,11 +68,11 @@ public void ToArrayByIndexWithBadIndexSelectorThrows() { var input = new[] { 42 }; - Assert.Throws(() => - input.ToArrayByIndex(_ => -1)); + Assert.That(() => input.ToArrayByIndex(_ => -1), + Throws.TypeOf()); - Assert.Throws(() => - input.ToArrayByIndex(_ => -1, BreakingFunc.Of())); + Assert.That(() => input.ToArrayByIndex(_ => -1, BreakingFunc.Of()), + Throws.TypeOf()); } [TestCase(10, -1)] @@ -80,11 +80,11 @@ public void ToArrayByIndexWithBadIndexSelectorThrows() public void ToArrayByIndexWithLengthWithBadIndexSelectorThrows(int length, int badIndex) { var input = new[] { 42 }; - Assert.Throws(() => - input.ToArrayByIndex(length, _ => badIndex)); + Assert.That(() => input.ToArrayByIndex(length, _ => badIndex), + Throws.TypeOf()); - Assert.Throws(() => - input.ToArrayByIndex(10, _ => -1, BreakingFunc.Of())); + Assert.That(() => input.ToArrayByIndex(length, _ => badIndex, BreakingFunc.Of()), + Throws.TypeOf()); } [Test] diff --git a/MoreLinq.Test/ToDataTableTest.cs b/MoreLinq.Test/ToDataTableTest.cs index 10bf432e9..e800dd7c0 100644 --- a/MoreLinq.Test/ToDataTableTest.cs +++ b/MoreLinq.Test/ToDataTableTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2010 Johannes Rudolph. 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. @@ -15,6 +15,8 @@ // limitations under the License. #endregion +#nullable enable + namespace MoreLinq.Test { using System; @@ -27,7 +29,7 @@ namespace MoreLinq.Test [TestFixture] public class ToDataTableTest { - class TestObject + sealed class TestObject { public int KeyField; public Guid? ANullableGuidField; @@ -36,12 +38,7 @@ class TestObject public decimal? ANullableDecimal { get; } public object Unreadable { set => throw new NotImplementedException(); } - public object this[int index] - { - get => new object(); - set { } - } - + public object this[int index] { get => new(); set { } } public TestObject(int key) { @@ -51,6 +48,8 @@ public TestObject(int key) ANullableDecimal = key / 3; AString = "ABCDEFGHIKKLMNOPQRSTUVWXYSZ"; } + + public override string ToString() => nameof(TestObject); } @@ -67,52 +66,59 @@ public ToDataTableTest() [Test] public void ToDataTableNullMemberExpressionMethod() { - Expression> expression = null; + Expression>? expression = null; - AssertThrowsArgument.Exception("expressions",() => - _testObjects.ToDataTable(expression)); + Assert.That(() => _testObjects.ToDataTable(expression!), + Throws.ArgumentException("expressions")); } [Test] public void ToDataTableTableWithWrongColumnNames() { - var dt = new DataTable(); + using var dt = new DataTable(); dt.Columns.Add("Test"); - AssertThrowsArgument.Exception("table",() => - _testObjects.ToDataTable(dt)); + Assert.That(() => _testObjects.ToDataTable(dt), + Throws.ArgumentException("table")); } [Test] public void ToDataTableTableWithWrongColumnDataType() { - var dt = new DataTable(); + using var dt = new DataTable(); dt.Columns.Add("AString", typeof(int)); - AssertThrowsArgument.Exception("table",() => - _testObjects.ToDataTable(dt, t=>t.AString)); + Assert.That(() => _testObjects.ToDataTable(dt, t=>t.AString), + Throws.ArgumentException("table")); } [Test] public void ToDataTableMemberExpressionMethod() { - AssertThrowsArgument.Exception("lambda", () => - _testObjects.ToDataTable(t => t.ToString())); + Assert.That(() => _testObjects.ToDataTable(t => t.ToString()), + Throws.ArgumentException("lambda")); } [Test] public void ToDataTableMemberExpressionNonMember() { - AssertThrowsArgument.Exception("lambda", () => - _testObjects.ToDataTable(t => t.ToString().Length)); + Assert.That(() => _testObjects.ToDataTable(t => t.ToString().Length), + Throws.ArgumentException("lambda")); } [Test] public void ToDataTableMemberExpressionIndexer() { - AssertThrowsArgument.Exception("lambda",() => - _testObjects.ToDataTable(t => t[0])); + Assert.That(() => _testObjects.ToDataTable(t => t[0]), + Throws.ArgumentException("lambda")); + } + + [Test] + public void ToDataTableMemberExpressionStatic() + { + Assert.That(() => _ = _testObjects.ToDataTable(_ => DateTime.Now), + Throws.ArgumentException("lambda")); } [Test] @@ -122,27 +128,27 @@ public void ToDataTableSchemaInDeclarationOrder() // Assert properties first, then fields, then in declaration order - Assert.AreEqual("AString", dt.Columns[0].Caption); - Assert.AreEqual(typeof(string), dt.Columns[0].DataType); + Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString")); + Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string))); - Assert.AreEqual("ANullableDecimal", dt.Columns[1].Caption); - Assert.AreEqual(typeof(decimal), dt.Columns[1].DataType); + Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal")); + Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal))); - Assert.AreEqual("KeyField", dt.Columns[2].Caption); - Assert.AreEqual(typeof(int), dt.Columns[2].DataType); + Assert.That(dt.Columns[2].Caption, Is.EqualTo("KeyField")); + Assert.That(dt.Columns[2].DataType, Is.EqualTo(typeof(int))); - Assert.AreEqual("ANullableGuidField", dt.Columns[3].Caption); - Assert.AreEqual(typeof(Guid), dt.Columns[3].DataType); - Assert.IsTrue(dt.Columns[3].AllowDBNull); + Assert.That(dt.Columns[3].Caption, Is.EqualTo("ANullableGuidField")); + Assert.That(dt.Columns[3].DataType, Is.EqualTo(typeof(Guid))); + Assert.That(dt.Columns[3].AllowDBNull, Is.True); - Assert.AreEqual(4, dt.Columns.Count); + Assert.That(dt.Columns.Count, Is.EqualTo(4)); } [Test] public void ToDataTableContainsAllElements() { var dt = _testObjects.ToDataTable(); - Assert.AreEqual(_testObjects.Count, dt.Rows.Count); + Assert.That(dt.Rows.Count, Is.EqualTo(_testObjects.Count)); } [Test] @@ -150,16 +156,16 @@ public void ToDataTableWithExpression() { var dt = _testObjects.ToDataTable(t => t.AString); - Assert.AreEqual("AString", dt.Columns[0].Caption); - Assert.AreEqual(typeof(string), dt.Columns[0].DataType); + Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString")); + Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string))); - Assert.AreEqual(1, dt.Columns.Count); + Assert.That(dt.Columns.Count, Is.EqualTo(1)); } [Test] public void ToDataTableWithSchema() { - var dt = new DataTable(); + using var dt = new DataTable(); var columns = dt.Columns; columns.Add("Column1", typeof(int)); columns.Add("Value", typeof(string)); @@ -170,7 +176,7 @@ public void ToDataTableWithSchema() .Cast() .ToArray(); - vars.Select(e => new { Name = e.Key.ToString(), Value = e.Value.ToString() }) + vars.Select(e => new { Name = e.Key.ToString(), Value = e.Value!.ToString() }) .ToDataTable(dt, e => e.Name, e => e.Value); var rows = dt.Rows.Cast().ToArray(); @@ -181,7 +187,10 @@ public void ToDataTableWithSchema() struct Point { - public static Point Empty = new Point(); + +#pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649) + public static Point Empty = new(); +#pragma warning restore CA1805 // Do not initialize unnecessarily public bool IsEmpty => X == 0 && Y == 0; public int X { get; } public int Y { get; } @@ -193,15 +202,17 @@ public void ToDataTableIgnoresStaticMembers() { var points = new[] { new Point(12, 34) }.ToDataTable(); - Assert.AreEqual(3, points.Columns.Count); - DataColumn x, y, empty; - Assert.NotNull(x = points.Columns["X"]); - Assert.NotNull(y = points.Columns["Y"]); - Assert.NotNull(empty = points.Columns["IsEmpty"]); + Assert.That(points.Columns.Count, Is.EqualTo(3)); + var x = points.Columns["X"]; + var y = points.Columns["Y"]; + var empty = points.Columns["IsEmpty"]; + Assert.That(x, Is.Not.Null); + Assert.That(y, Is.Not.Null); + Assert.That(empty, Is.Not.Null); var row = points.Rows.Cast().Single(); - Assert.AreEqual(12, row[x]); - Assert.AreEqual(34, row[y]); - Assert.AreEqual(false, row[empty]); + Assert.That(row[x], Is.EqualTo(12)); + Assert.That(row[y], Is.EqualTo(34)); + Assert.That(row[empty], Is.False); } } } diff --git a/MoreLinq.Test/ToDelimitedStringTest.cs b/MoreLinq.Test/ToDelimitedStringTest.cs index 8d7eb1f0a..61d21e659 100644 --- a/MoreLinq.Test/ToDelimitedStringTest.cs +++ b/MoreLinq.Test/ToDelimitedStringTest.cs @@ -32,7 +32,7 @@ public void ToDelimitedStringWithNonEmptySequenceAndDelimiter() [Test] public void ToDelimitedStringWithNonEmptySequenceContainingNulls() { - var result = new object[] { 1, null, "foo", true }.ToDelimitedString(","); + var result = new object?[] { 1, null, "foo", true }.ToDelimitedString(","); Assert.That(result, Is.EqualTo("1,,foo,True")); } @@ -40,7 +40,7 @@ public void ToDelimitedStringWithNonEmptySequenceContainingNulls() public void ToDelimitedStringWithNonEmptySequenceContainingNullsAtStart() { // See: https://github.com/morelinq/MoreLINQ/issues/43 - var result = new object[] { null, null, "foo" }.ToDelimitedString(","); + var result = new object?[] { null, null, "foo" }.ToDelimitedString(","); Assert.That(result, Is.EqualTo(",,foo")); } } diff --git a/MoreLinq.Test/ToDictionaryTest.cs b/MoreLinq.Test/ToDictionaryTest.cs index 6b8bf11ff..e4a1f8ad6 100644 --- a/MoreLinq.Test/ToDictionaryTest.cs +++ b/MoreLinq.Test/ToDictionaryTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. diff --git a/MoreLinq.Test/ToLookupTest.cs b/MoreLinq.Test/ToLookupTest.cs index 6bcaa389f..c7403d0b5 100644 --- a/MoreLinq.Test/ToLookupTest.cs +++ b/MoreLinq.Test/ToLookupTest.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copyright (c) 2008 Jonathan Skeet. All rights reserved. +// Copyright (c) 2017 Atif Aziz. 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. diff --git a/MoreLinq.Test/TraceTest.cs b/MoreLinq.Test/TraceTest.cs index 0379c2f49..5c9725a35 100644 --- a/MoreLinq.Test/TraceTest.cs +++ b/MoreLinq.Test/TraceTest.cs @@ -78,19 +78,13 @@ public void TraceSequenceWithFormatter() static IEnumerable Lines(string str) { - using (var e = _(string.IsNullOrEmpty(str) - ? TextReader.Null - : new StringReader(str))) - { - while (e.MoveNext()) - yield return e.Current; - } + using var e = _(string.IsNullOrEmpty(str) ? TextReader.Null : new StringReader(str)); + while (e.MoveNext()) + yield return e.Current; - IEnumerator _(TextReader reader) + static IEnumerator _(TextReader reader) { - Debug.Assert(reader != null); - string line; - while ((line = reader.ReadLine()) != null) + while (reader.ReadLine() is { } line) yield return line; } } diff --git a/MoreLinq.Test/TransposeTest.cs b/MoreLinq.Test/TransposeTest.cs index d0436aead..70f3c6772 100644 --- a/MoreLinq.Test/TransposeTest.cs +++ b/MoreLinq.Test/TransposeTest.cs @@ -33,14 +33,13 @@ public void TransposeIsLazy() [Test] public void TransposeWithOneNullRow() { - using (var seq1 = TestingSequence.Of(10, 11)) - using (var seq2 = TestingSequence.Of()) - using (var seq3 = TestingSequence.Of(30, 31, 32)) - using (var matrix = TestingSequence.Of(seq1, seq2, seq3, null)) - { - Assert.Throws(() => - matrix.Transpose().FirstOrDefault()); - } + using var seq1 = TestingSequence.Of(10, 11); + using var seq2 = TestingSequence.Of(); + using var seq3 = TestingSequence.Of(30, 31, 32); + using var matrix = TestingSequence.Of>(seq1, seq2, seq3, null!); + + Assert.That(() => matrix.Transpose().FirstOrDefault(), + Throws.TypeOf()); } [Test] @@ -54,13 +53,12 @@ public void TransposeWithRowsOfSameLength() new [] { 13, 23, 33 }, }; - using (var row1 = TestingSequence.Of(10, 11, 12, 13)) - using (var row2 = TestingSequence.Of(20, 21, 22, 23)) - using (var row3 = TestingSequence.Of(30, 31, 32, 33)) - using (var matrix = TestingSequence.Of(row1, row2, row3)) - { - AssertMatrix(expectations, matrix.Transpose()); - } + using var row1 = TestingSequence.Of(10, 11, 12, 13); + using var row2 = TestingSequence.Of(20, 21, 22, 23); + using var row3 = TestingSequence.Of(30, 31, 32, 33); + using var matrix = TestingSequence.Of(row1, row2, row3); + + AssertMatrix(expectations, matrix.Transpose()); } [Test] @@ -73,14 +71,13 @@ public void TransposeWithRowsOfDifferentLengths() new[] { 32 } }; - using (var row1 = TestingSequence.Of(10, 11)) - using (var row2 = TestingSequence.Of(20)) - using (var row3 = TestingSequence.Of()) - using (var row4 = TestingSequence.Of(30, 31, 32)) - using (var matrix = TestingSequence.Of(row1, row2, row3, row4)) - { - AssertMatrix(expectations, matrix.Transpose()); - } + using var row1 = TestingSequence.Of(10, 11); + using var row2 = TestingSequence.Of(20); + using var row3 = TestingSequence.Of(); + using var row4 = TestingSequence.Of(30, 31, 32); + using var matrix = TestingSequence.Of(row1, row2, row3, row4); + + AssertMatrix(expectations, matrix.Transpose()); } [Test] @@ -177,23 +174,22 @@ public void TransposeConsumesRowsLazily() result.ElementAt(0).AssertSequenceEqual(10, 20, 30); - Assert.Throws(() => - result.ElementAt(1)); + Assert.That(() => result.ElementAt(1), + Throws.TypeOf()); } [Test] public void TransposeWithErroneousRowDisposesRowIterators() { - using (var row1 = TestingSequence.Of(10, 11)) - using (var row2 = MoreEnumerable.From(() => 20, - () => throw new TestException()) - .AsTestingSequence()) - using (var row3 = TestingSequence.Of(30, 32)) - using (var matrix = TestingSequence.Of(row1, row2, row3)) - { - Assert.Throws(() => - matrix.Transpose().Consume()); - } + using var row1 = TestingSequence.Of(10, 11); + using var row2 = MoreEnumerable.From(() => 20, + () => throw new TestException()) + .AsTestingSequence(); + using var row3 = TestingSequence.Of(30, 32); + using var matrix = TestingSequence.Of(row1, row2, row3); + + Assert.That(() => matrix.Transpose().Consume(), + Throws.TypeOf()); } static bool IsPrime(int number) @@ -219,7 +215,7 @@ static void AssertMatrix(IEnumerable> expectation, IEnumerable var resultList = result.ToList(); var expectationList = expectation.ToList(); - Assert.AreEqual(expectationList.Count, resultList.Count); + Assert.That(resultList.Count, Is.EqualTo(expectationList.Count)); expectationList.Zip(resultList, ValueTuple.Create) .ForEach(t => t.Item1.AssertSequenceEqual(t.Item2)); diff --git a/MoreLinq.Test/TraverseTest.cs b/MoreLinq.Test/TraverseTest.cs index 37e54fd41..07736d9ac 100644 --- a/MoreLinq.Test/TraverseTest.cs +++ b/MoreLinq.Test/TraverseTest.cs @@ -26,13 +26,13 @@ public class TraverseTest [Test] public void TraverseDepthFirstFNullGenerator() { - MoreEnumerable.TraverseDepthFirst(new object(), o => new BreakingSequence()); + MoreEnumerable.TraverseDepthFirst(new object(), _ => new BreakingSequence()); } [Test] public void TraverseBreadthFirstIsStreaming() { - MoreEnumerable.TraverseBreadthFirst(new object(), o => new BreakingSequence()); + MoreEnumerable.TraverseBreadthFirst(new object(), _ => new BreakingSequence()); } [Test] @@ -50,7 +50,7 @@ public void TraverseBreadthFirstPreservesChildrenOrder() res.AssertSequenceEqual(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); } - class Tree + sealed class Tree { public T Value { get; } public IEnumerable> Children { get; } @@ -64,8 +64,7 @@ public Tree(T value, IEnumerable> children) static class Tree { - public static Tree New(T value, params Tree[] children) => - new Tree(value, children); + public static Tree New(T value, params Tree[] children) => new(value, children); } [Test] diff --git a/MoreLinq.Test/TrySingleTest.cs b/MoreLinq.Test/TrySingleTest.cs index 675044c7e..5dfffbcaf 100644 --- a/MoreLinq.Test/TrySingleTest.cs +++ b/MoreLinq.Test/TrySingleTest.cs @@ -15,14 +15,14 @@ // limitations under the License. #endregion -using NUnit.Framework.Interfaces; - namespace MoreLinq.Test { using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework; + using NUnit.Framework.Interfaces; + using Experimental; [TestFixture] public class TrySingleTest @@ -76,12 +76,14 @@ class BreakingSingleElementCollectionBase : IEnumerable protected BreakingSingleElementCollectionBase(T element) => _element = element; +#pragma warning disable CA1822 // Mark members as static public int Count => 1; +#pragma warning restore CA1822 // Mark members as static public IEnumerator GetEnumerator() { yield return _element; - throw new Exception($"{nameof(MoreEnumerable.TrySingle)} should not have attempted to consume a second element."); + Assert.Fail($"{nameof(ExperimentalEnumerable.TrySingle)} should not have attempted to consume a second element."); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -128,7 +130,7 @@ static IEnumerable TestSequence() { yield return 1; yield return 2; - throw new Exception(nameof(MoreEnumerable.TrySingle) + " should not have attempted to consume a third element."); + Assert.Fail(nameof(ExperimentalEnumerable.TrySingle) + " should not have attempted to consume a third element."); } var (cardinality, value) = TestSequence().TrySingle("zero", "one", "many"); diff --git a/MoreLinq.Test/WatchableEnumerator.cs b/MoreLinq.Test/WatchableEnumerator.cs index 72fbe0b44..cb6b14bef 100644 --- a/MoreLinq.Test/WatchableEnumerator.cs +++ b/MoreLinq.Test/WatchableEnumerator.cs @@ -23,22 +23,21 @@ namespace MoreLinq.Test partial class TestExtensions { - public static WatchableEnumerator AsWatchtable(this IEnumerator source) => - new WatchableEnumerator(source); + public static WatchableEnumerator AsWatchable(this IEnumerator source) => new(source); } sealed class WatchableEnumerator : IEnumerator { readonly IEnumerator _source; - public event EventHandler Disposed; - public event EventHandler MoveNextCalled; + public event EventHandler? Disposed; + public event EventHandler? MoveNextCalled; public WatchableEnumerator(IEnumerator source) => _source = source ?? throw new ArgumentNullException(nameof(source)); public T Current => _source.Current; - object IEnumerator.Current => Current; + object? IEnumerator.Current => Current; public void Reset() => _source.Reset(); public bool MoveNext() diff --git a/MoreLinq.Test/WindowLeftTest.cs b/MoreLinq.Test/WindowLeftTest.cs index d85d8f11f..e24cf2dbd 100644 --- a/MoreLinq.Test/WindowLeftTest.cs +++ b/MoreLinq.Test/WindowLeftTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 Atif Aziz. 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.Test { using System.Collections.Generic; @@ -12,21 +29,66 @@ public void WindowLeftIsLazy() new BreakingSequence().WindowLeft(1); } + [Test] + public void WindowModifiedBeforeMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowLeft(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + window1[1] = -1; + e.MoveNext(); + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(1)); + } + + [Test] + public void WindowModifiedAfterMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowLeft(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + window1[1] = -1; + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(1)); + } + + [Test] + public void WindowModifiedDoesNotAffectPreviousWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowLeft(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + var window2 = e.Current; + window2[0] = -1; + + Assert.That(window1[1], Is.EqualTo(1)); + } + [Test] public void WindowLeftWithNegativeWindowSize() { - AssertThrowsArgument.OutOfRangeException("size", () => - Enumerable.Repeat(1, 10).WindowLeft(-5)); + Assert.That(() => Enumerable.Repeat(1, 10).WindowLeft(-5), + Throws.ArgumentOutOfRangeException("size")); } [Test] public void WindowLeftWithEmptySequence() { - using (var xs = Enumerable.Empty().AsTestingSequence()) - { - var result = xs.WindowLeft(5); - Assert.That(result, Is.Empty); - } + using var xs = Enumerable.Empty().AsTestingSequence(); + + var result = xs.WindowLeft(5); + + Assert.That(result, Is.Empty); } [Test] @@ -50,31 +112,31 @@ public void WindowLeftWithSingleElement() [Test] public void WindowLeftWithWindowSizeLargerThanSequence() { - using (var sequence = Enumerable.Range(1, 5).AsTestingSequence()) - using (var reader = sequence.WindowLeft(10).Read()) - { - reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); - reader.Read().AssertSequenceEqual(2, 3, 4, 5); - reader.Read().AssertSequenceEqual(3, 4, 5); - reader.Read().AssertSequenceEqual(4, 5); - reader.Read().AssertSequenceEqual(5); - reader.ReadEnd(); - } + using var sequence = Enumerable.Range(1, 5).AsTestingSequence(); + + using var reader = sequence.WindowLeft(10).Read(); + + reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); + reader.Read().AssertSequenceEqual(2, 3, 4, 5); + reader.Read().AssertSequenceEqual(3, 4, 5); + reader.Read().AssertSequenceEqual(4, 5); + reader.Read().AssertSequenceEqual(5); + reader.ReadEnd(); } [Test] public void WindowLeftWithWindowSizeSmallerThanSequence() { - using (var sequence = Enumerable.Range(1, 5).AsTestingSequence()) - using (var reader = sequence.WindowLeft(3).Read()) - { - reader.Read().AssertSequenceEqual(1, 2, 3); - reader.Read().AssertSequenceEqual(2, 3, 4); - reader.Read().AssertSequenceEqual(3, 4, 5); - reader.Read().AssertSequenceEqual(4, 5); - reader.Read().AssertSequenceEqual(5); - reader.ReadEnd(); - } + using var sequence = Enumerable.Range(1, 5).AsTestingSequence(); + + using var reader = sequence.WindowLeft(3).Read(); + + reader.Read().AssertSequenceEqual(1, 2, 3); + reader.Read().AssertSequenceEqual(2, 3, 4); + reader.Read().AssertSequenceEqual(3, 4, 5); + reader.Read().AssertSequenceEqual(4, 5); + reader.Read().AssertSequenceEqual(5); + reader.ReadEnd(); } } } diff --git a/MoreLinq.Test/WindowRightTest.cs b/MoreLinq.Test/WindowRightTest.cs index 62951923c..57d5f5040 100644 --- a/MoreLinq.Test/WindowRightTest.cs +++ b/MoreLinq.Test/WindowRightTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 Atif Aziz. 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.Test { using System.Collections.Generic; @@ -12,21 +29,66 @@ public void WindowRightIsLazy() new BreakingSequence().WindowRight(1); } + [Test] + public void WindowModifiedBeforeMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowRight(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + window1[0] = -1; + e.MoveNext(); + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(0)); + } + + [Test] + public void WindowModifiedAfterMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowRight(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + window1[0] = -1; + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(0)); + } + + [Test] + public void WindowModifiedDoesNotAffectPreviousWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.WindowRight(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + var window2 = e.Current; + window2[0] = -1; + + Assert.That(window1[0], Is.EqualTo(0)); + } + [Test] public void WindowRightWithNegativeWindowSize() { - AssertThrowsArgument.OutOfRangeException("size", () => - Enumerable.Repeat(1, 10).WindowRight(-5)); + Assert.That(() => Enumerable.Repeat(1, 10).WindowRight(-5), + Throws.ArgumentOutOfRangeException("size")); } [Test] public void WindowRightWithEmptySequence() { - using (var xs = Enumerable.Empty().AsTestingSequence()) - { - var result = xs.WindowRight(5); - Assert.That(result, Is.Empty); - } + using var xs = Enumerable.Empty().AsTestingSequence(); + + var result = xs.WindowRight(5); + + Assert.That(result, Is.Empty); } [Test] @@ -50,31 +112,29 @@ public void WindowRightWithSingleElement() [Test] public void WindowRightWithWindowSizeLargerThanSequence() { - using (var sequence = Enumerable.Range(1, 5).AsTestingSequence()) - using (var reader = sequence.WindowRight(10).Read()) - { - reader.Read().AssertSequenceEqual( 1); - reader.Read().AssertSequenceEqual( 1, 2); - reader.Read().AssertSequenceEqual( 1, 2, 3); - reader.Read().AssertSequenceEqual( 1, 2, 3, 4); - reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); - reader.ReadEnd(); - } + using var sequence = Enumerable.Range(1, 5).AsTestingSequence(); + + using var reader = sequence.WindowRight(10).Read(); + reader.Read().AssertSequenceEqual( 1); + reader.Read().AssertSequenceEqual( 1, 2); + reader.Read().AssertSequenceEqual( 1, 2, 3); + reader.Read().AssertSequenceEqual( 1, 2, 3, 4); + reader.Read().AssertSequenceEqual(1, 2, 3, 4, 5); + reader.ReadEnd(); } [Test] public void WindowRightWithWindowSizeSmallerThanSequence() { - using (var sequence = Enumerable.Range(1, 5).AsTestingSequence()) - using (var reader = sequence.WindowRight(3).Read()) - { - reader.Read().AssertSequenceEqual( 1); - reader.Read().AssertSequenceEqual( 1, 2); - reader.Read().AssertSequenceEqual(1, 2, 3); - reader.Read().AssertSequenceEqual(2, 3, 4); - reader.Read().AssertSequenceEqual(3, 4, 5); - reader.ReadEnd(); - } + using var sequence = Enumerable.Range(1, 5).AsTestingSequence(); + + using var reader = sequence.WindowRight(3).Read(); + reader.Read().AssertSequenceEqual( 1); + reader.Read().AssertSequenceEqual( 1, 2); + reader.Read().AssertSequenceEqual(1, 2, 3); + reader.Read().AssertSequenceEqual(2, 3, 4); + reader.Read().AssertSequenceEqual(3, 4, 5); + reader.ReadEnd(); } } } diff --git a/MoreLinq.Test/WindowTest.cs b/MoreLinq.Test/WindowTest.cs index 83e71e6a4..fb5c93c97 100644 --- a/MoreLinq.Test/WindowTest.cs +++ b/MoreLinq.Test/WindowTest.cs @@ -1,3 +1,20 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2018 Atif Aziz. 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.Test { using NUnit.Framework; @@ -17,6 +34,51 @@ public void TestWindowIsLazy() new BreakingSequence().Window(1); } + [Test] + public void WindowModifiedBeforeMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.Window(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + window1[1] = -1; + e.MoveNext(); + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(1)); + } + + [Test] + public void WindowModifiedAfterMoveNextDoesNotAffectNextWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.Window(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + window1[1] = -1; + var window2 = e.Current; + + Assert.That(window2[0], Is.EqualTo(1)); + } + + [Test] + public void WindowModifiedDoesNotAffectPreviousWindow() + { + var sequence = Enumerable.Range(0, 3); + using var e = sequence.Window(2).GetEnumerator(); + + e.MoveNext(); + var window1 = e.Current; + e.MoveNext(); + var window2 = e.Current; + window2[0] = -1; + + Assert.That(window1[1], Is.EqualTo(1)); + } + /// /// Verify that a negative window size results in an exception /// @@ -25,8 +87,8 @@ public void TestWindowNegativeWindowSizeException() { var sequence = Enumerable.Repeat(1, 10); - AssertThrowsArgument.OutOfRangeException("size",() => - sequence.Window(-5)); + Assert.That(() => sequence.Window(-5), + Throws.ArgumentOutOfRangeException("size")); } /// @@ -54,11 +116,11 @@ public void TestWindowOfSingleElement() var result = sequence.Window(1); // number of windows should be equal to the source sequence length - Assert.AreEqual(count, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count)); // each window should contain single item consistent of element at that offset var index = -1; foreach (var window in result) - Assert.AreEqual(sequence.ElementAt(++index), window.Single()); + Assert.That(window.Single(), Is.EqualTo(sequence.ElementAt(++index))); } /// @@ -90,7 +152,7 @@ public void TestWindowSmallerThanSequence() var result = sequence.Window(windowSize); // ensure that the number of windows is correct - Assert.AreEqual(count - windowSize + 1, result.Count()); + Assert.That(result.Count(), Is.EqualTo(count - windowSize + 1)); // ensure each window contains the correct set of items var index = -1; foreach (var window in result) @@ -104,15 +166,14 @@ public void TestWindowSmallerThanSequence() [Test] public void TestWindowWindowsImmutability() { - using (var windows = Enumerable.Range(1, 5).Window(2).AsTestingSequence()) - using (var reader = windows.ToArray().Read()) - { - reader.Read().AssertSequenceEqual(1, 2); - reader.Read().AssertSequenceEqual(2, 3); - reader.Read().AssertSequenceEqual(3, 4); - reader.Read().AssertSequenceEqual(4, 5); - reader.ReadEnd(); - } + using var windows = Enumerable.Range(1, 5).Window(2).AsTestingSequence(); + + using var reader = windows.ToArray().Read(); + reader.Read().AssertSequenceEqual(1, 2); + reader.Read().AssertSequenceEqual(2, 3); + reader.Read().AssertSequenceEqual(3, 4); + reader.Read().AssertSequenceEqual(4, 5); + reader.ReadEnd(); } } } diff --git a/MoreLinq.Test/ZipLongestTest.cs b/MoreLinq.Test/ZipLongestTest.cs index 2046c17fd..7c3faaec7 100644 --- a/MoreLinq.Test/ZipLongestTest.cs +++ b/MoreLinq.Test/ZipLongestTest.cs @@ -31,13 +31,13 @@ public class ZipLongestTest public static readonly IEnumerable TestData = from e in new[] { - new { A = Seq( ), B = Seq("foo", "bar", "baz"), Result = Seq((0, "foo"), (0, "bar"), (0, "baz")) }, - new { A = Seq(1 ), B = Seq("foo", "bar", "baz"), Result = Seq((1, "foo"), (0, "bar"), (0, "baz")) }, - new { A = Seq(1, 2 ), B = Seq("foo", "bar", "baz"), Result = Seq((1, "foo"), (2, "bar"), (0, "baz")) }, - new { A = Seq(1, 2, 3), B = Seq( ), Result = Seq((1, null ), (2, null ), (3, (string) null)) }, - new { A = Seq(1, 2, 3), B = Seq("foo" ), Result = Seq((1, "foo"), (2, null ), (3, null )) }, - new { A = Seq(1, 2, 3), B = Seq("foo", "bar" ), Result = Seq((1, "foo"), (2, "bar"), (3, null )) }, - new { A = Seq(1, 2, 3), B = Seq("foo", "bar", "baz"), Result = Seq((1, "foo"), (2, "bar"), (3, "baz")) }, + new { A = Seq( ), B = Seq("foo", "bar", "baz"), Result = Seq<(int, string?)>((0, "foo"), (0, "bar"), (0, "baz")) }, + new { A = Seq(1 ), B = Seq("foo", "bar", "baz"), Result = Seq<(int, string?)>((1, "foo"), (0, "bar"), (0, "baz")) }, + new { A = Seq(1, 2 ), B = Seq("foo", "bar", "baz"), Result = Seq<(int, string?)>((1, "foo"), (2, "bar"), (0, "baz")) }, + new { A = Seq(1, 2, 3), B = Seq( ), Result = Seq<(int, string?)>((1, null ), (2, null ), (3, null )) }, + new { A = Seq(1, 2, 3), B = Seq("foo" ), Result = Seq<(int, string?)>((1, "foo"), (2, null ), (3, null )) }, + new { A = Seq(1, 2, 3), B = Seq("foo", "bar" ), Result = Seq<(int, string?)>((1, "foo"), (2, "bar"), (3, null )) }, + new { A = Seq(1, 2, 3), B = Seq("foo", "bar", "baz"), Result = Seq<(int, string?)>((1, "foo"), (2, "bar"), (3, "baz")) }, } select new TestCaseData(e.A, e.B) .Returns(e.Result); @@ -46,9 +46,9 @@ from e in new[] [Test, TestCaseSource(nameof(TestData))] public IEnumerable<(int, string)> ZipLongest(int[] first, string[] second) { - using (var ts1 = TestingSequence.Of(first)) - using (var ts2 = TestingSequence.Of(second)) - return ts1.ZipLongest(ts2, Tuple.Create).ToArray(); + using var ts1 = TestingSequence.Of(first); + using var ts2 = TestingSequence.Of(second); + return ts1.ZipLongest(ts2, Tuple.Create).ToArray(); } [Test] @@ -61,7 +61,7 @@ public void ZipLongestIsLazy() [Test] public void ZipLongestDisposeSequencesEagerly() { - var shorter = TestingSequence.Of(1, 2, 3); + using var shorter = TestingSequence.Of(1, 2, 3); var longer = MoreEnumerable.Generate(1, x => x + 1); var zipped = shorter.ZipLongest(longer, Tuple.Create); @@ -76,11 +76,10 @@ public void ZipLongestDisposeSequencesEagerly() [Test] public void ZipLongestDisposesInnerSequencesCaseGetEnumeratorThrows() { - using (var s1 = TestingSequence.Of(1, 2)) - { - Assert.Throws(() => - s1.ZipLongest(new BreakingSequence(), Tuple.Create).Consume()); - } + using var s1 = TestingSequence.Of(1, 2); + + Assert.That(() => s1.ZipLongest(new BreakingSequence(), Tuple.Create).Consume(), + Throws.BreakException); } } } diff --git a/MoreLinq.Test/ZipShortestTest.cs b/MoreLinq.Test/ZipShortestTest.cs index 5b29f46c4..3a63b3660 100644 --- a/MoreLinq.Test/ZipShortestTest.cs +++ b/MoreLinq.Test/ZipShortestTest.cs @@ -17,7 +17,6 @@ namespace MoreLinq.Test { - using System; using NUnit.Framework; using Tuple = System.ValueTuple; @@ -27,21 +26,19 @@ public class ZipShortestTest [Test] public void BothSequencesDisposedWithUnequalLengthsAndLongerFirst() { - using (var longer = TestingSequence.Of(1, 2, 3)) - using (var shorter = TestingSequence.Of(1, 2)) - { - longer.ZipShortest(shorter, (x, y) => x + y).Consume(); - } + using var longer = TestingSequence.Of(1, 2, 3); + using var shorter = TestingSequence.Of(1, 2); + + longer.ZipShortest(shorter, (x, y) => x + y).Consume(); } [Test] public void BothSequencesDisposedWithUnequalLengthsAndShorterFirst() { - using (var longer = TestingSequence.Of(1, 2, 3)) - using (var shorter = TestingSequence.Of(1, 2)) - { - shorter.ZipShortest(longer, (x, y) => x + y).Consume(); - } + using var longer = TestingSequence.Of(1, 2, 3); + using var shorter = TestingSequence.Of(1, 2); + + shorter.ZipShortest(longer, (x, y) => x + y).Consume(); } [Test] @@ -78,42 +75,41 @@ public void ZipShortestIsLazy() [Test] public void MoveNextIsNotCalledUnnecessarilyWhenFirstIsShorter() { - using (var s1 = TestingSequence.Of(1, 2)) - using (var s2 = MoreEnumerable.From(() => 4, - () => 5, - () => throw new TestException()) - .AsTestingSequence()) - { - var zipped = s1.ZipShortest(s2, Tuple.Create); - Assert.That(zipped, Is.Not.Null); - zipped.AssertSequenceEqual((1, 4), (2, 5)); - } + using var s1 = TestingSequence.Of(1, 2); + using var s2 = MoreEnumerable.From(() => 4, + () => 5, + () => throw new TestException()) + .AsTestingSequence(); + + var zipped = s1.ZipShortest(s2, Tuple.Create); + + Assert.That(zipped, Is.Not.Null); + zipped.AssertSequenceEqual((1, 4), (2, 5)); } [Test] public void ZipShortestNotIterateUnnecessaryElements() { - using (var s1 = MoreEnumerable.From(() => 4, - () => 5, - () => 6, - () => throw new TestException()) - .AsTestingSequence()) - using (var s2 = TestingSequence.Of(1, 2)) - { - var zipped = s1.ZipShortest(s2, Tuple.Create); - Assert.That(zipped, Is.Not.Null); - zipped.AssertSequenceEqual((4, 1), (5, 2)); - } + using var s1 = MoreEnumerable.From(() => 4, + () => 5, + () => 6, + () => throw new TestException()) + .AsTestingSequence(); + using var s2 = TestingSequence.Of(1, 2); + + var zipped = s1.ZipShortest(s2, Tuple.Create); + + Assert.That(zipped, Is.Not.Null); + zipped.AssertSequenceEqual((4, 1), (5, 2)); } [Test] public void ZipShortestDisposesInnerSequencesCaseGetEnumeratorThrows() { - using (var s1 = TestingSequence.Of(1, 2)) - { - Assert.Throws(() => - s1.ZipShortest(new BreakingSequence(), Tuple.Create).Consume()); - } + using var s1 = TestingSequence.Of(1, 2); + + Assert.That(() => s1.ZipShortest(new BreakingSequence(), Tuple.Create).Consume(), + Throws.BreakException); } } } diff --git a/MoreLinq.Test/coverlet.runsettings b/MoreLinq.Test/coverlet.runsettings new file mode 100644 index 000000000..bbe3b949d --- /dev/null +++ b/MoreLinq.Test/coverlet.runsettings @@ -0,0 +1,13 @@ + + + + + + opencover + [NUnit*]*,[MoreLinq]MoreLinq.Extensions.*,[MoreLinq]MoreLinq.Experimental.* + **/System.*.g.cs + + + + + diff --git a/MoreLinq.shfbproj b/MoreLinq.shfbproj index 26ada71e4..92e3b25c4 100644 --- a/MoreLinq.shfbproj +++ b/MoreLinq.shfbproj @@ -1,5 +1,6 @@ - - + + + Documentation Documentation Documentation + .NET Core/.NET Standard/.NET 5.0+ .\docs\api\ Help en-US MoreLINQ fills in a few gaps left by LINQ to Objects. + Website + Standard + Default2022 + True + True + False + False + OnlyWarningsAndErrors + 100 - - + + Website http://www.apache.org/licenses/LICENSE-2.0 @@ -30,26 +41,16 @@ MoreLinq Google Groups MoreLinq MemberName + AboveNamespaces + False + False + 2 False - - - - - - - - - VS2013 - .NET Framework 4.0 + Blank - - Provides types and extension methods that extend LINQ to Objects. - - Provides experimental types and extension methods that extend LINQ to Objects. + + Provides types and extension methods that extend LINQ to Objects. + Provides experimental types and extension methods that extend LINQ to Objects. THE METHODS ARE EXPERIMENTAL. @@ -63,6 +64,7 @@ THE METHODS ARE PUBLISHED FOR FIELD EXPERIMENTATION TO SOLICIT FEEDBACK ON THEIR UTILITY AND DESIGN/IMPLEMENTATION DEFECTS. + - + + + + + + + + + + + OnBuildSuccess + diff --git a/MoreLinq.sln b/MoreLinq.sln index 7d4944e58..ff1b65d6c 100644 --- a/MoreLinq.sln +++ b/MoreLinq.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{835F8FFA-471F-4322-B721-A897F27872FA}" ProjectSection(SolutionItems) = preProject @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution build.sh = build.sh builddocs.cmd = builddocs.cmd COPYING.txt = COPYING.txt + Directory.Build.props = Directory.Build.props global.json = global.json msbuild.cmd = msbuild.cmd pack.cmd = pack.cmd diff --git a/MoreLinq/Aggregate.g.tt b/MoreLinq/Aggregate.g.tt index 50dfdbf19..e9d41b3c5 100644 --- a/MoreLinq/Aggregate.g.tt +++ b/MoreLinq/Aggregate.g.tt @@ -1,7 +1,6 @@ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Core" #> -<#@ assembly name="System.Collections" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.Linq" #> #region License and Terms diff --git a/MoreLinq/AggregateRight.cs b/MoreLinq/AggregateRight.cs index 162b0e412..b3004919c 100644 --- a/MoreLinq/AggregateRight.cs +++ b/MoreLinq/AggregateRight.cs @@ -34,7 +34,7 @@ static partial class MoreEnumerable /// The final accumulator value. /// /// i.ToString()).AggregateRight((a, b) => string.Format("({0}/{1})", a, b)); + /// string result = Enumerable.Range(1, 5).Select(i => i.ToString()).AggregateRight((a, b) => $"({a}/{b})"); /// ]]> /// The result variable will contain "(1/(2/(3/(4/5))))". /// @@ -52,7 +52,7 @@ public static TSource AggregateRight(this IEnumerable source, if (list.Count == 0) throw new InvalidOperationException("Sequence contains no elements."); - return AggregateRightImpl(list, list[list.Count - 1], func, list.Count - 1); + return AggregateRightImpl(list, list[^1], func, list.Count - 1); } /// @@ -70,7 +70,7 @@ public static TSource AggregateRight(this IEnumerable source, /// /// string.Format("({0}/{1})", a, b)); + /// string result = numbers.AggregateRight("6", (a, b) => $"({a}/{b})"); /// ]]> /// The result variable will contain "(1/(2/(3/(4/(5/6)))))". /// @@ -106,7 +106,7 @@ public static TAccumulate AggregateRight(this IEnumerable< /// /// string.Format("({0}/{1})", a, b), str => str.Length); + /// int result = numbers.AggregateRight("6", (a, b) => $"({a}/{b})", str => str.Length); /// ]]> /// The result variable will contain 21. /// 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/AssertCount.cs b/MoreLinq/AssertCount.cs index 8296e166c..f9467c7c1 100644 --- a/MoreLinq/AssertCount.cs +++ b/MoreLinq/AssertCount.cs @@ -71,13 +71,11 @@ public static IEnumerable AssertCount(this IEnumerable errorSelector) => AssertCountImpl(source, count, errorSelector); - static Exception OnAssertCountFailure(int cmp, int count) - { - var message = cmp < 0 - ? "Sequence contains too few elements when exactly {0} were expected." - : "Sequence contains too many elements when exactly {0} were expected."; - return new SequenceException(string.Format(message, count.ToString("N0"))); - } + static Exception OnAssertCountFailure(int cmp, int count) => + new SequenceException(FormatSequenceLengthErrorMessage(cmp, count)); + + internal static string FormatSequenceLengthErrorMessage(int cmp, int count) => + $"Sequence contains too {(cmp < 0 ? "few" : "many")} elements when exactly {count:N0} {(count == 1 ? "was" : "were")} expected."; #endif @@ -89,7 +87,7 @@ static IEnumerable AssertCountImpl(IEnumerable source if (errorSelector == null) throw new ArgumentNullException(nameof(errorSelector)); return - source.TryGetCollectionCount() is int collectionCount + source.TryGetCollectionCount() is {} collectionCount ? collectionCount == count ? source : From(() => throw errorSelector(collectionCount.CompareTo(count), count)) diff --git a/MoreLinq/Assume.cs b/MoreLinq/Assume.cs new file mode 100644 index 000000000..08b60162a --- /dev/null +++ b/MoreLinq/Assume.cs @@ -0,0 +1,31 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. 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 +{ + using System.Runtime.CompilerServices; + + static class Assume + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T NotNull(T? obj) where T : class + { + Debug.Assert(obj is not null); + return obj; + } + } +} diff --git a/MoreLinq/Backsert.cs b/MoreLinq/Backsert.cs index 928eef47a..3b0a0b4bf 100644 --- a/MoreLinq/Backsert.cs +++ b/MoreLinq/Backsert.cs @@ -60,19 +60,22 @@ public static IEnumerable Backsert(this IEnumerable first, IEnumerable< { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); - if (index < 0) throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."); - if (index == 0) - return first.Concat(second); + return index switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(index), "Index cannot be negative."), + 0 => first.Concat(second), + _ => _() + }; - return _(); IEnumerable _() + IEnumerable _() { using var e = first.CountDown(index, ValueTuple.Create).GetEnumerator(); if (e.MoveNext()) { var (_, countdown) = e.Current; - if (countdown is int n && n != index - 1) + if (countdown is {} n && n != index - 1) throw new ArgumentOutOfRangeException(nameof(index), "Insertion index is greater than the length of the first sequence."); do diff --git a/MoreLinq/Batch.cs b/MoreLinq/Batch.cs index c6db41d82..5bb2553e7 100644 --- a/MoreLinq/Batch.cs +++ b/MoreLinq/Batch.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Linq; static partial class MoreEnumerable { @@ -49,7 +50,7 @@ static partial class MoreEnumerable public static IEnumerable> Batch(this IEnumerable source, int size) { - return Batch(source, size, x => x); + return Batch(source, size, IdFn); } /// @@ -61,6 +62,7 @@ public static IEnumerable> Batch(this IEnumerable< /// Size of buckets. /// The projection to apply to each bucket. /// A sequence of projections on equally sized buckets containing elements of the source collection. + /// /// /// This operator uses deferred execution and streams its results /// (buckets are streamed but their content buffered). @@ -76,6 +78,7 @@ public static IEnumerable> Batch(this IEnumerable< /// hoping for a single bucket, then it can lead to memory exhaustion /// (). /// + /// public static IEnumerable Batch(this IEnumerable source, int size, Func, TResult> resultSelector) @@ -86,6 +89,10 @@ public static IEnumerable Batch(this IEnumerable { Count: 0 }: + { + return Enumerable.Empty(); + } case ICollection collection when collection.Count <= size: { return _(); IEnumerable _() @@ -95,6 +102,10 @@ public static IEnumerable Batch(this IEnumerable { Count: 0 }: + { + return Enumerable.Empty(); + } case IReadOnlyList list when list.Count <= size: { return _(); IEnumerable _() @@ -116,14 +127,12 @@ public static IEnumerable Batch(this IEnumerable Batch(int size) { - TSource[] bucket = null; + TSource[]? bucket = null; var count = 0; foreach (var item in source) { - if (bucket == null) - bucket = new TSource[size]; - + bucket ??= new TSource[size]; bucket[count++] = item; // The bucket is fully buffered before it's yielded @@ -137,7 +146,7 @@ IEnumerable Batch(int size) } // Return the last bucket with all remaining elements - if (bucket != null && count > 0) + if (count > 0) { Array.Resize(ref bucket, count); yield return resultSelector(bucket); diff --git a/MoreLinq/Cartesian.g.tt b/MoreLinq/Cartesian.g.tt index c57605da0..8df601e46 100644 --- a/MoreLinq/Cartesian.g.tt +++ b/MoreLinq/Cartesian.g.tt @@ -1,7 +1,6 @@ <#@ template debug="false" hostspecific="false" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Core" #> -<#@ assembly name="System.Collections" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.Linq" #> #region License and Terms diff --git a/MoreLinq/Collections/Dictionary.cs b/MoreLinq/Collections/Dictionary.cs new file mode 100644 index 000000000..a325768d8 --- /dev/null +++ b/MoreLinq/Collections/Dictionary.cs @@ -0,0 +1,61 @@ +#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 a null key. + /// + + // Add members if and when needed to keep coverage. + + sealed class Dictionary + { + readonly System.Collections.Generic.Dictionary, TValue> _dict; + + public Dictionary(IEqualityComparer comparer) + { + var keyComparer = ReferenceEquals(comparer, EqualityComparer.Default) + ? null + : new ValueTupleItemComparer(comparer); + _dict = new System.Collections.Generic.Dictionary, TValue>(keyComparer); + } + + public TValue this[TKey key] + { + get => _dict[ValueTuple.Create(key)]; + set => _dict[ValueTuple.Create(key)] = value; + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => + _dict.TryGetValue(ValueTuple.Create(key), out value); + + sealed class ValueTupleItemComparer : IEqualityComparer> + { + readonly IEqualityComparer _comparer; + + public ValueTupleItemComparer(IEqualityComparer comparer) => _comparer = comparer; + public bool Equals(ValueTuple x, ValueTuple y) => _comparer.Equals(x.Item1, y.Item1); + public int GetHashCode(ValueTuple obj) => obj.Item1 is { } some ? _comparer.GetHashCode(some) : 0; + } + } +} diff --git a/MoreLinq/Consume.cs b/MoreLinq/Consume.cs index 924fc6709..0a65b87ba 100644 --- a/MoreLinq/Consume.cs +++ b/MoreLinq/Consume.cs @@ -32,7 +32,7 @@ static partial class MoreEnumerable public static void Consume(this IEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); - foreach (var element in source) + foreach (var _ in source) { } } diff --git a/MoreLinq/CountBy.cs b/MoreLinq/CountBy.cs index 11e22ba29..282ace5d4 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)); @@ -78,51 +78,26 @@ 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(); - var havePrevKey = false; - var prevKey = default(TKey); - var index = 0; foreach (var item in source) { 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)) + if (dic.TryGetValue(key, out var 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); } - - prevKey = key; - havePrevKey = true; } } } diff --git a/MoreLinq/CountDown.cs b/MoreLinq/CountDown.cs index 94dd43675..a46186a89 100644 --- a/MoreLinq/CountDown.cs +++ b/MoreLinq/CountDown.cs @@ -39,7 +39,7 @@ static partial class MoreEnumerable /// A function that receives the element and the current countdown /// value for the element and which returns those mapped to a /// result returned in the resulting sequence. For elements before - /// the last , the coundown value is + /// the last , the countdown value is /// null. /// /// A sequence of results returned by @@ -57,9 +57,9 @@ public static IEnumerable CountDown(this IEnumerable sou if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return source.TryAsListLike() is IListLike listLike + return source.TryAsListLike() is {} listLike ? IterateList(listLike) - : source.TryGetCollectionCount() is int collectionCount + : source.TryGetCollectionCount() is {} collectionCount ? IterateCollection(collectionCount) : IterateSequence(); diff --git a/MoreLinq/CountMethods.cs b/MoreLinq/CountMethods.cs index b55ee8656..d253dc7c8 100644 --- a/MoreLinq/CountMethods.cs +++ b/MoreLinq/CountMethods.cs @@ -55,7 +55,7 @@ public static bool AtLeast(this IEnumerable source, int count) /// /// Element type of sequence /// The source sequence - /// The maximun number of items a sequence must have for this + /// The maximum number of items a sequence must have for this /// function to return true /// is null /// is negative @@ -110,7 +110,7 @@ public static bool Exactly(this IEnumerable source, int count) /// The source sequence /// The minimum number of items a sequence must have for this /// function to return true - /// The maximun number of items a sequence must have for this + /// The maximum number of items a sequence must have for this /// function to return true /// is null /// is negative or is less than min @@ -167,11 +167,11 @@ public static int CompareCount(this IEnumerable first, if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); - if (first.TryGetCollectionCount() is int firstCount) + if (first.TryGetCollectionCount() is {} firstCount) { return firstCount.CompareTo(second.TryGetCollectionCount() ?? second.CountUpTo(firstCount + 1)); } - else if (second.TryGetCollectionCount() is int secondCount) + else if (second.TryGetCollectionCount() is {} secondCount) { return first.CountUpTo(secondCount + 1).CompareTo(secondCount); } diff --git a/MoreLinq/Debug.cs b/MoreLinq/Debug.cs new file mode 100644 index 000000000..6b36c21c7 --- /dev/null +++ b/MoreLinq/Debug.cs @@ -0,0 +1,31 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. 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 +{ + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.CompilerServices; + + static class Debug + { + [Conditional("DEBUG")] + public static void Assert([DoesNotReturnIf(false)] bool condition, + [CallerArgumentExpression(nameof(condition))] string? message = null) => + System.Diagnostics.Debug.Assert(condition); + } +} diff --git a/MoreLinq/Delegating.cs b/MoreLinq/Delegating.cs index 6ccbf6885..fcdda4e5a 100644 --- a/MoreLinq/Delegating.cs +++ b/MoreLinq/Delegating.cs @@ -33,14 +33,14 @@ 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); } sealed class DelegatingDisposable : IDisposable { - Action _delegatee; + Action? _delegatee; public DelegatingDisposable(Action delegatee) => _delegatee = delegatee ?? throw new ArgumentNullException(nameof(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/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/EmptyArray.cs b/MoreLinq/EmptyArray.cs new file mode 100644 index 000000000..fa49b3567 --- /dev/null +++ b/MoreLinq/EmptyArray.cs @@ -0,0 +1,14 @@ +namespace MoreLinq +{ + static class EmptyArray + { + public static readonly T[] Value = +#if NETSTANDARD1_6_OR_GREATER || NET6_0_OR_GREATER + System.Array.Empty(); +#else +#pragma warning disable CA1825 // Avoid zero-length array allocations + new T[0]; +#pragma warning restore CA1825 // Avoid zero-length array allocations +#endif + } +} diff --git a/MoreLinq/EndsWith.cs b/MoreLinq/EndsWith.cs index ca1de4e66..4c9664b79 100644 --- a/MoreLinq/EndsWith.cs +++ b/MoreLinq/EndsWith.cs @@ -66,7 +66,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) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); @@ -74,8 +74,8 @@ public static bool EndsWith(this IEnumerable first, IEnumerable second, comparer ??= EqualityComparer.Default; List secondList; - return second.TryGetCollectionCount() is int secondCount - ? first.TryGetCollectionCount() is int firstCount && secondCount > firstCount + return second.TryGetCollectionCount() is {} secondCount + ? first.TryGetCollectionCount() is {} firstCount && secondCount > firstCount ? false : Impl(second, secondCount) : Impl(secondList = second.ToList(), secondList.Count); diff --git a/MoreLinq/EquiZip.cs b/MoreLinq/EquiZip.cs index edede6ef1..61212f122 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 @@ -43,6 +42,10 @@ static partial class MoreEnumerable /// /// The input sequences are of different lengths. /// + /// + /// , , or is . + /// /// /// EquiZip( if (second == null) throw new ArgumentNullException(nameof(second)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return EquiZipImpl(first, second, null, null, (a, b, c, d) => resultSelector(a, b)); + return EquiZipImpl(first, second, null, null, (a, b, _, _) => resultSelector(a, b)); } /// @@ -89,6 +92,11 @@ public static IEnumerable EquiZip( /// /// The input sequences are of different lengths. /// + /// + /// , , , or is . + /// /// /// EquiZip( /// /// The input sequences are of different lengths. /// + /// + /// , , , , or is . + /// /// /// 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/Exclude.cs b/MoreLinq/Exclude.cs index 60c77ddf9..455861862 100644 --- a/MoreLinq/Exclude.cs +++ b/MoreLinq/Exclude.cs @@ -36,12 +36,15 @@ public static IEnumerable Exclude(this IEnumerable sequence, int startI { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex)); - if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - if (count == 0) - return sequence; + return count switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(count)), + 0 => sequence, + _ => _() + }; - return _(); IEnumerable _() + IEnumerable _() { var index = 0; var endIndex = startIndex + count; diff --git a/MoreLinq/Experimental/Await.cs b/MoreLinq/Experimental/Await.cs index 229fccbcb..54433155b 100644 --- a/MoreLinq/Experimental/Await.cs +++ b/MoreLinq/Experimental/Await.cs @@ -23,11 +23,11 @@ namespace MoreLinq.Experimental using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; + using Unit = System.ValueTuple; /// /// Represents options for a query whose results evaluate asynchronously. @@ -40,10 +40,9 @@ public sealed class AwaitQueryOptions /// asynchronously. /// - public static readonly AwaitQueryOptions Default = - new AwaitQueryOptions(null /* = unbounded concurrency */, - TaskScheduler.Default, - preserveOrder: false); + public static readonly AwaitQueryOptions Default = new(null /* = unbounded concurrency */, + TaskScheduler.Default, + preserveOrder: false); /// /// Gets a positive (non-zero) integer that specifies the maximum @@ -68,7 +67,7 @@ public sealed class AwaitQueryOptions AwaitQueryOptions(int? maxConcurrency, TaskScheduler scheduler, bool preserveOrder) { - MaxConcurrency = maxConcurrency == null || maxConcurrency > 0 + MaxConcurrency = maxConcurrency is null or > 0 ? maxConcurrency : throw new ArgumentOutOfRangeException( nameof(maxConcurrency), maxConcurrency, @@ -148,8 +147,11 @@ static partial class ExperimentalEnumerable /// The source sequence. /// The converted sequence. - public static IEnumerable AsSequential(this IAwaitQuery source) => - source.MaxConcurrency(1); + public static IEnumerable AsSequential(this IAwaitQuery source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + return MaxConcurrency(source, 1); + } /// /// Returns a query whose results evaluate asynchronously to use a @@ -162,8 +164,11 @@ public static IEnumerable AsSequential(this IAwaitQuery source) => /// A query whose results evaluate asynchronously using the given /// concurrency limit. - public static IAwaitQuery MaxConcurrency(this IAwaitQuery source, int value) => - source.WithOptions(source.Options.WithMaxConcurrency(value)); + public static IAwaitQuery MaxConcurrency(this IAwaitQuery source, int value) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + return source.WithOptions(source.Options.WithMaxConcurrency(value)); + } /// /// Returns a query whose results evaluate asynchronously and @@ -175,8 +180,11 @@ public static IAwaitQuery MaxConcurrency(this IAwaitQuery source, int v /// A query whose results evaluate asynchronously using no defined /// limitation on concurrency. - public static IAwaitQuery UnboundedConcurrency(this IAwaitQuery source) => - source.WithOptions(source.Options.WithMaxConcurrency(null)); + public static IAwaitQuery UnboundedConcurrency(this IAwaitQuery source) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + return source.WithOptions(source.Options.WithMaxConcurrency(null)); + } /// /// Returns a query whose results evaluate asynchronously and uses the @@ -242,8 +250,11 @@ public static IAwaitQuery AsUnordered(this IAwaitQuery source) => /// results ordered or unordered based on . /// - public static IAwaitQuery PreserveOrder(this IAwaitQuery source, bool value) => - source.WithOptions(source.Options.WithPreserveOrder(value)); + public static IAwaitQuery PreserveOrder(this IAwaitQuery source, bool value) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + return source.WithOptions(source.Options.WithPreserveOrder(value)); + } /// /// Creates a sequence query that streams the result of each task in @@ -363,14 +374,14 @@ select e.Task.IsFaulted /// completed task. /// /// The type of the source elements. - /// The type of the tasks's result. + /// The type of the task's result. /// The type of the result elements. /// The source sequence. /// A function to begin the asynchronous /// evaluation of each element, the second parameter of which is a /// that can be used to abort /// asynchronous operations. - /// A fucntion that projects the final + /// A function that projects the final /// result given the source item and its asynchronous completion /// result. /// @@ -436,43 +447,10 @@ 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; - - 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; - } - } + (Exception?, Exception?) lastCriticalErrors = default; var completed = false; var cancellationTokenSource = new CancellationTokenSource(); @@ -494,16 +472,51 @@ void PostNotice(Notice notice, { try { - await enumerator.StartAsync( - e => evaluator(e.Value, cancellationToken), - (e, r) => PostNotice(Notice.Result, (e.Key, e.Value, r), default), - () => PostNotice(Notice.End, default, default), - maxConcurrency, cancellationToken); + await enumerator.StartAsync(e => evaluator(e.Value, cancellationToken), + (e, r) => PostNotice(Notice.Result, (e.Key, e.Value, r), default), + () => PostNotice(Notice.End, default, default), + maxConcurrency, cancellationToken) + .ConfigureAwait(false); } +#pragma warning disable CA1031 // Do not catch general exception types catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types { 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 occurring, 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, @@ -527,15 +540,17 @@ await enumerator.StartAsync( catch (OperationCanceledException e) when (e.CancellationToken == consumerCancellationTokenSource.Token) { var (error1, error2) = lastCriticalErrors; +#pragma warning disable CA2201 // Do not raise reserved exception types throw new Exception("One or more critical errors have occurred.", - error2 != null ? new AggregateException(error1, error2) - : new AggregateException(error1)); + error2 != null ? new AggregateException(Assume.NotNull(error1), error2) + : new AggregateException(Assume.NotNull(error1))); +#pragma warning restore CA2201 // Do not raise reserved exception types } var (kind, result, error) = notice.Current; if (kind == Notice.Error) - error.Throw(); + Assume.NotNull(error).Throw(); if (kind == Notice.End) break; @@ -636,7 +651,7 @@ void OnPendingCompleted() onEnd(); } - var concurrencyGate = maxConcurrency is int count + var concurrencyGate = maxConcurrency is {} count ? new ConcurrencyGate(count) : ConcurrencyGate.Unbounded; @@ -644,7 +659,8 @@ void OnPendingCompleted() { try { - await concurrencyGate.EnterAsync(cancellationToken); + await concurrencyGate.EnterAsync(cancellationToken) + .ConfigureAwait(false); } catch (OperationCanceledException e) when (e.CancellationToken == cancellationToken) { @@ -656,7 +672,7 @@ void OnPendingCompleted() var item = enumerator.Current; var task = starter(item); - // Add a continutation that notifies completion of the task, + // Add a continuation that notifies completion of the task, // along with the necessary housekeeping, in case it // completes before maximum concurrency is reached. @@ -688,7 +704,7 @@ static class AwaitQuery public static IAwaitQuery Create( Func> impl, - AwaitQueryOptions options = null) => + AwaitQueryOptions? options = null) => new AwaitQuery(impl, options); } @@ -697,7 +713,7 @@ sealed class AwaitQuery : IAwaitQuery readonly Func> _impl; public AwaitQuery(Func> impl, - AwaitQueryOptions options = null) + AwaitQueryOptions? options = null) { _impl = impl; Options = options ?? AwaitQueryOptions.Default; @@ -729,15 +745,15 @@ static class TupleComparer static class CompletedTask { - #if NET451 || NETSTANDARD1_0 + #if NETSTANDARD1_0 - public static readonly Task Instance; + public static readonly Task Instance = CreateCompletedTask(); - static CompletedTask() + static Task CreateCompletedTask() { - var tcs = new TaskCompletionSource(); - tcs.SetResult(null); - Instance = tcs.Task; + var tcs = new TaskCompletionSource(); + tcs.SetResult(default); + return tcs.Task; } #else @@ -749,11 +765,11 @@ static CompletedTask() sealed class ConcurrencyGate { - public static readonly ConcurrencyGate Unbounded = new ConcurrencyGate(); + public static readonly ConcurrencyGate Unbounded = new(); - 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/Batch.cs b/MoreLinq/Experimental/Batch.cs new file mode 100644 index 000000000..49bb1f3f4 --- /dev/null +++ b/MoreLinq/Experimental/Batch.cs @@ -0,0 +1,293 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. 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 + +#if !NO_BUFFERS + +namespace MoreLinq.Experimental +{ + using System; + using System.Buffers; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + + static partial class ExperimentalEnumerable + { + /// + /// Batches the source sequence into sized buckets using an array pool + /// to rent arrays to back each bucket and returns a sequence of + /// elements projected from each bucket. + /// + /// + /// Type of elements in sequence. + /// + /// Type of elements of the resulting sequence. + /// + /// The source sequence. + /// Size of buckets. + /// The pool used to rent the array for each bucket. + /// A function that projects a result from + /// the current bucket. + /// + /// A sequence whose elements are projected from each bucket (returned by + /// ). + /// + /// + /// + /// This operator uses deferred execution and streams its results + /// (however, each bucket provided to is + /// buffered). + /// + /// + /// Each bucket is backed by a rented array that may be at least + /// in length. + /// + /// + /// When more than one bucket is produced, all buckets except the last + /// is guaranteed to have elements. The last + /// bucket may be smaller depending on the remaining elements in the + /// sequence. + /// Each bucket is pre-allocated to elements. + /// If is set to a very large value, e.g. + /// to effectively disable batching by just + /// hoping for a single bucket, then it can lead to memory exhaustion + /// (). + /// + /// + + public static IEnumerable + Batch(this IEnumerable source, int size, + ArrayPool pool, + Func, TResult> resultSelector) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (pool == null) throw new ArgumentNullException(nameof(pool)); + if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); + if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); + + return source.Batch(size, pool, current => current, + current => resultSelector((ICurrentBuffer)current)); + } + + /// + /// Batches the source sequence into sized buckets using an array pool + /// to rent arrays to back each bucket and returns a sequence of + /// elements projected from each bucket. + /// + /// + /// Type of elements in sequence. + /// + /// Type of elements in the sequence returned by . + /// + /// Type of elements of the resulting sequence. + /// + /// The source sequence. + /// Size of buckets. + /// The pool used to rent the array for each bucket. + /// A function that returns a + /// sequence projection to use for each bucket. It is called initially + /// before iterating over , but the resulting + /// projection is evaluated for each bucket. This has the same effect as + /// calling for each bucket, + /// but allows initialization of the transformation to happen only once. + /// + /// A function that projects a result from + /// the input sequence produced over a bucket. + /// + /// A sequence whose elements are projected from each bucket (returned by + /// ). + /// + /// + /// + /// This operator uses deferred execution and streams its results + /// (however, each bucket is buffered). + /// + /// + /// Each bucket is backed by a rented array that may be at least + /// in length. + /// + /// + /// When more than one bucket is produced, all buckets except the last + /// is guaranteed to have elements. The last + /// bucket may be smaller depending on the remaining elements in the + /// sequence. + /// Each bucket is pre-allocated to elements. + /// If is set to a very large value, e.g. + /// to effectively disable batching by just + /// hoping for a single bucket, then it can lead to memory exhaustion + /// (). + /// + /// + + public static IEnumerable + Batch( + this IEnumerable source, int size, ArrayPool pool, + Func, IEnumerable> bucketProjectionSelector, + Func, TResult> resultSelector) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (pool == null) throw new ArgumentNullException(nameof(pool)); + if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); + if (bucketProjectionSelector == null) throw new ArgumentNullException(nameof(bucketProjectionSelector)); + if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); + + return _(); IEnumerable _() + { + using var batch = source.Batch(size, pool); + var bucket = bucketProjectionSelector(batch.CurrentBuffer); + while (batch.UpdateWithNext()) + yield return resultSelector(bucket); + } + } + + static ICurrentBufferProvider + Batch(this IEnumerable source, int size, ArrayPool pool) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (pool == null) throw new ArgumentNullException(nameof(pool)); + if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size)); + + ICurrentBufferProvider Cursor(IEnumerator<(T[], int)> source) => + new CurrentPoolArrayProvider(source, pool); + + switch (source) + { + case ICollection { Count: 0 }: + { + return Cursor(Enumerable.Empty <(T[], int)>().GetEnumerator()); + } + case ICollection collection when collection.Count <= size: + { + var bucket = pool.Rent(collection.Count); + collection.CopyTo(bucket, 0); + return Cursor(MoreEnumerable.Return((bucket, collection.Count)).GetEnumerator()); + } + case IReadOnlyCollection { Count: 0 }: + { + return Cursor(Enumerable.Empty <(T[], int)>().GetEnumerator()); + } + case IReadOnlyList list when list.Count <= size: + { + return Cursor(_()); IEnumerator<(T[], int)> _() + { + var bucket = pool.Rent(list.Count); + for (var i = 0; i < list.Count; i++) + bucket[i] = list[i]; + yield return (bucket, list.Count); + } + } + case IReadOnlyCollection collection when collection.Count <= size: + { + return Cursor(Batch(collection.Count)); + } + default: + { + return Cursor(Batch(size)); + } + } + + IEnumerator<(T[], int)> Batch(int size) + { + T[]? bucket = null; + var count = 0; + + foreach (var item in source) + { + bucket ??= pool.Rent(size); + bucket[count++] = item; + + // The bucket is fully buffered before it's yielded + if (count != size) + continue; + + yield return (bucket, size); + + bucket = null; + count = 0; + } + + // Return the last bucket with all remaining elements + if (bucket is { } someBucket && count > 0) + yield return (someBucket, count); + } + } + + sealed class CurrentPoolArrayProvider : CurrentBuffer, ICurrentBufferProvider + { + bool _rented; + T[] _array = Array.Empty(); + int _count; + IEnumerator<(T[], int)>? _rental; + ArrayPool? _pool; + + public CurrentPoolArrayProvider(IEnumerator<(T[], int)> rental, ArrayPool pool) => + (_rental, _pool) = (rental, pool); + + ICurrentBuffer ICurrentBufferProvider.CurrentBuffer => this; + + public bool UpdateWithNext() + { + if (_rental is { Current: var (array, _) } rental) + { + Debug.Assert(_pool is not null); + if (_rented) + { + _pool.Return(array); + _rented = false; + } + + if (!rental.MoveNext()) + { + Dispose(); + return false; + } + + _rented = true; + (_array, _count) = rental.Current; + return true; + } + + return false; + } + + public override int Count => _count; + + public override T this[int index] + { + get => index >= 0 && index < Count ? _array[index] : throw new ArgumentOutOfRangeException(nameof(index)); + set => throw new NotSupportedException(); + } + + public void Dispose() + { + if (_rental is { Current: var (array, _) } enumerator) + { + Debug.Assert(_pool is not null); + if (_rented) + _pool.Return(array); + enumerator.Dispose(); + _array = Array.Empty(); + _count = 0; + _rental = null; + _pool = null; + } + } + } + } +} + +#endif // !NO_BUFFERS diff --git a/MoreLinq/Experimental/CurrentBuffer.cs b/MoreLinq/Experimental/CurrentBuffer.cs new file mode 100644 index 000000000..e6cb526a1 --- /dev/null +++ b/MoreLinq/Experimental/CurrentBuffer.cs @@ -0,0 +1,107 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Atif Aziz. 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 + +#if !NO_BUFFERS + +namespace MoreLinq.Experimental +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents a current buffered view of a larger result and which + /// is updated in-place (thus current) as it is moved through the overall + /// result. + /// + /// Type of elements in the list. + + public interface ICurrentBuffer : IList { } + + /// + /// A provider of current buffer that updates it in-place. + /// + /// Type of elements in the list. + + interface ICurrentBufferProvider : IDisposable + { + /// + /// Gets the current items of the list. + /// + /// + /// The returned list is updated in-place when + /// is called. + /// + + ICurrentBuffer CurrentBuffer { get; } + + /// + /// Update this instance with the next set of elements from the source. + /// + /// + /// A Boolean that is true if this instance was updated with + /// new elements; otherwise false to indicate that the end of + /// the bucket source has been reached. + /// + + bool UpdateWithNext(); + } + + abstract class CurrentBuffer : ICurrentBuffer + { + public abstract int Count { get; } + public abstract T this[int index] { get; set; } + + public virtual bool IsReadOnly => false; + + public virtual int IndexOf(T item) + { + var comparer = EqualityComparer.Default; + + for (var i = 0; i < Count; i++) + { + if (comparer.Equals(this[i], item)) + return i; + } + + return -1; + } + + public virtual bool Contains(T item) => IndexOf(item) >= 0; + + public virtual void CopyTo(T[] array, int arrayIndex) + { + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex), arrayIndex, null); + if (arrayIndex + Count > array.Length) throw new ArgumentException(null, nameof(arrayIndex)); + + for (int i = 0, j = arrayIndex; i < Count; i++, j++) + array[j] = this[i]; + } + + public virtual IEnumerator GetEnumerator() => this.Take(Count).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + void IList.Insert(int index, T item) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + void ICollection.Add(T item) => throw new NotSupportedException(); + void ICollection.Clear() => throw new NotSupportedException(); + bool ICollection.Remove(T item) => throw new NotSupportedException(); + } +} + +#endif // !NO_BUFFERS diff --git a/MoreLinq/Experimental/Memoize.cs b/MoreLinq/Experimental/Memoize.cs index 65d309fda..258037e9c 100644 --- a/MoreLinq/Experimental/Memoize.cs +++ b/MoreLinq/Experimental/Memoize.cs @@ -34,40 +34,42 @@ static partial class ExperimentalEnumerable /// Type of elements in . /// The source sequence. /// - /// Returns a sequence that corresponds to a cached version of the - /// input sequence. + /// Returns a sequence that corresponds to a cached version of the input + /// sequence. + /// + /// + /// is . + /// /// /// The returned will cache items from /// in a thread-safe manner. Each thread can /// call its to acquire an - /// iterator but the same iterator should not be used simultanesouly - /// from multiple threads. The sequence supplied in - /// is not expected to be thread-safe but it - /// is required to be thread-agnostic because different threads - /// (though never simultaneously) may iterate over the sequence. + /// iterator but the same iterator should not be used simultaneously + /// from multiple threads. The sequence supplied in is not expected to be thread-safe but it is required + /// to be thread-agnostic because different threads (though never + /// simultaneously) may iterate over the sequence. /// - public static IEnumerable Memoize(this IEnumerable source) - { - switch (source) + public static IEnumerable Memoize(this IEnumerable source) => + source switch { - case null: throw new ArgumentNullException(nameof(source)); - case ICollection _: // ... - case IReadOnlyCollection _: // ... - case MemoizedEnumerable _: return source; - default: return new MemoizedEnumerable(source); - } - } + null => throw new ArgumentNullException(nameof(source)), + ICollection => source, + IReadOnlyCollection => source, + MemoizedEnumerable => source, + _ => new MemoizedEnumerable(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 +117,7 @@ public IEnumerator GetEnumerator() if (index >= _cache.Count) { if (index == _errorIndex) - _error.Throw(); + Assume.NotNull(_error).Throw(); if (_sourceEnumerator == null) break; diff --git a/MoreLinq/TrySingle.cs b/MoreLinq/Experimental/TrySingle.cs similarity index 96% rename from MoreLinq/TrySingle.cs rename to MoreLinq/Experimental/TrySingle.cs index 24bfe35df..e72bfbc82 100644 --- a/MoreLinq/TrySingle.cs +++ b/MoreLinq/Experimental/TrySingle.cs @@ -15,13 +15,13 @@ // limitations under the License. #endregion -namespace MoreLinq +namespace MoreLinq.Experimental { using System; using System.Collections.Generic; using System.Linq; - partial class MoreEnumerable + partial class ExperimentalEnumerable { /// /// Returns a tuple with the cardinality of the sequence and the @@ -51,7 +51,7 @@ partial class MoreEnumerable /// than two elements from the sequence. /// - public static (TCardinality Cardinality, T Value) + public static (TCardinality Cardinality, T? Value) TrySingle(this IEnumerable source, TCardinality zero, TCardinality one, TCardinality many) => TrySingle(source, zero, one, many, ValueTuple.Create); @@ -96,7 +96,7 @@ public static (TCardinality Cardinality, T Value) public static TResult TrySingle(this IEnumerable source, TCardinality zero, TCardinality one, TCardinality many, - Func resultSelector) + Func resultSelector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); @@ -115,7 +115,7 @@ public static TResult TrySingle(this IEnumerable so }; return resultSelector(one, item); } - case int _: + case {}: return resultSelector(many, default); default: { diff --git a/MoreLinq/Extensions.ToDataTable.g.cs b/MoreLinq/Extensions.ToDataTable.g.cs index 7931061c6..b9165678e 100644 --- a/MoreLinq/Extensions.ToDataTable.g.cs +++ b/MoreLinq/Extensions.ToDataTable.g.cs @@ -17,11 +17,22 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. +#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 { using System; using System.CodeDom.Compiler; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Data; using System.Linq.Expressions; @@ -57,7 +68,7 @@ public static DataTable ToDataTable(this IEnumerable source) /// /// This operator uses immediate execution. - public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) + public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) => MoreEnumerable.ToDataTable(source, expressions); /// /// Appends elements in the sequence as rows of a given object. @@ -90,7 +101,7 @@ public static TTable ToDataTable(this IEnumerable source, TTable t /// /// This operator uses immediate execution. - public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) + public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) where TTable : DataTable => MoreEnumerable.ToDataTable(source, table, expressions); diff --git a/MoreLinq/Extensions.g.cs b/MoreLinq/Extensions.g.cs index 0b1211058..964d46902 100644 --- a/MoreLinq/Extensions.g.cs +++ b/MoreLinq/Extensions.g.cs @@ -17,11 +17,22 @@ // This code was generated by a tool. Any changes made manually will be lost // the next time this code is regenerated. +#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 { using System; using System.CodeDom.Compiler; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Collections; @@ -349,7 +360,7 @@ public static partial class AggregateRightExtension /// The final accumulator value. /// /// i.ToString()).AggregateRight((a, b) => string.Format("({0}/{1})", a, b)); + /// string result = Enumerable.Range(1, 5).Select(i => i.ToString()).AggregateRight((a, b) => $"({a}/{b})"); /// ]]> /// The result variable will contain "(1/(2/(3/(4/5))))". /// @@ -375,7 +386,7 @@ public static TSource AggregateRight(this IEnumerable source, /// /// string.Format("({0}/{1})", a, b)); + /// string result = numbers.AggregateRight("6", (a, b) => $"({a}/{b})"); /// ]]> /// The result variable will contain "(1/(2/(3/(4/(5/6)))))". /// @@ -404,7 +415,7 @@ public static TAccumulate AggregateRight(this IEnumerable< /// /// string.Format("({0}/{1})", a, b), str => str.Length); + /// int result = numbers.AggregateRight("6", (a, b) => $"({a}/{b})", str => str.Length); /// ]]> /// The result variable will contain 21. /// @@ -477,7 +488,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); } @@ -575,7 +586,7 @@ public static partial class AtMostExtension /// /// Element type of sequence /// The source sequence - /// The maximun number of items a sequence must have for this + /// The maximum number of items a sequence must have for this /// function to return true /// is null /// is negative @@ -679,6 +690,7 @@ public static IEnumerable> Batch(this IEnumerable< /// Size of buckets. /// The projection to apply to each bucket. /// A sequence of projections on equally sized buckets containing elements of the source collection. + /// /// /// This operator uses deferred execution and streams its results /// (buckets are streamed but their content buffered). @@ -694,6 +706,7 @@ public static IEnumerable> Batch(this IEnumerable< /// hoping for a single bucket, then it can lead to memory exhaustion /// (). /// + /// public static IEnumerable Batch(this IEnumerable source, int size, Func, TResult> resultSelector) @@ -1124,7 +1137,7 @@ public static partial class CountBetweenExtension /// The source sequence /// The minimum number of items a sequence must have for this /// function to return true - /// The maximun number of items a sequence must have for this + /// The maximum number of items a sequence must have for this /// function to return true /// is null /// is negative or is less than min @@ -1174,7 +1187,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); } @@ -1201,7 +1214,7 @@ public static partial class CountDownExtension /// A function that receives the element and the current countdown /// value for the element and which returns those mapped to a /// result returned in the resulting sequence. For elements before - /// the last , the coundown value is + /// the last , the countdown value is /// null. /// /// A sequence of results returned by @@ -1263,7 +1276,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); } @@ -1314,7 +1327,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); } @@ -1343,6 +1356,10 @@ public static partial class EquiZipExtension /// /// The input sequences are of different lengths. /// + /// + /// , , or is . + /// /// /// EquiZip( /// /// The input sequences are of different lengths. /// + /// + /// , , , or is . + /// /// /// EquiZip( /// /// The input sequences are of different lengths. /// + /// + /// , , , , or is . + /// /// /// ExceptBy(this IEnumerable ExceptBy(this IEnumerable first, IEnumerable second, Func keySelector, - IEqualityComparer keyComparer) + IEqualityComparer? keyComparer) => MoreEnumerable.ExceptBy(first, second, keySelector, keyComparer); } @@ -1751,7 +1778,7 @@ public static IEnumerable FillBackward(this IEnumerable source, Func /// Returns a sequence with each missing element in the source replaced /// with the following non-missing element in that sequence. Additional - /// parameters specifiy two functions, one used to determine if an + /// parameters specify two functions, one used to determine if an /// element is considered missing or not and another to provide the /// replacement for the missing element. /// @@ -1828,7 +1855,7 @@ public static IEnumerable FillForward(this IEnumerable source, Func /// Returns a sequence with each missing element in the source replaced /// with one based on the previous non-missing element seen in that - /// sequence. Additional parameters specifiy two functions, one used to + /// sequence. Additional parameters specify two functions, one used to /// determine if an element is considered missing or not and another /// to provide the replacement for the missing element. /// @@ -1894,7 +1921,7 @@ public static partial class FirstOrDefaultExtension /// otherwise, the first element in source. /// - public static T FirstOrDefault(this IExtremaEnumerable source) + public static T? FirstOrDefault(this IExtremaEnumerable source) => MoreEnumerable.FirstOrDefault(source); } @@ -1914,7 +1941,13 @@ public static partial class FlattenExtension /// /// is null. - public static IEnumerable Flatten(this IEnumerable source) => MoreEnumerable.Flatten(source); + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source) => MoreEnumerable. Flatten(source); /// /// Flattens a sequence containing arbitrarily-nested sequences. An @@ -1938,8 +1971,14 @@ public static partial class FlattenExtension /// /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func predicate) - => MoreEnumerable.Flatten(source, predicate); + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source, Func predicate) + => MoreEnumerable. Flatten(source, predicate); /// /// Flattens a sequence containing arbitrarily-nested sequences. An @@ -1963,8 +2002,20 @@ public static IEnumerable Flatten(this IEnumerable source, Func /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func selector) - => MoreEnumerable.Flatten(source, selector); + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source, + Func< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*....................*/ object, +#nullable restore +/*....................*/ IEnumerable?> selector) + => MoreEnumerable. Flatten(source, selector); } @@ -2379,7 +2430,7 @@ public static partial class FullGroupJoinExtension IEnumerable second, Func firstKeySelector, Func secondKeySelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.FullGroupJoin(first, second, firstKeySelector, secondKeySelector, comparer); /// @@ -2438,7 +2489,7 @@ public static IEnumerable FullGroupJoin Func firstKeySelector, Func secondKeySelector, Func, IEnumerable, TResult> resultSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.FullGroupJoin(first, second, firstKeySelector, secondKeySelector, resultSelector, comparer); } @@ -2533,7 +2584,7 @@ public static IEnumerable FullJoin( Func firstSelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.FullJoin(first, second, keySelector, firstSelector, secondSelector, bothSelector, comparer); /// @@ -2628,7 +2679,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); } @@ -2693,7 +2744,7 @@ public static IEnumerable> GroupAdjacent public static IEnumerable> GroupAdjacent( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, comparer); /// @@ -2796,7 +2847,7 @@ public static IEnumerable> GroupAdjacent source, Func keySelector, Func elementSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, elementSelector, comparer); /// @@ -2832,7 +2883,7 @@ public static IEnumerable GroupAdjacent( this IEnumerable source, Func keySelector, Func, TResult> resultSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.GroupAdjacent(source, keySelector, resultSelector, comparer); } @@ -2923,7 +2974,7 @@ public static IEnumerable> IndexBy( this IEnumerable source, Func keySelector, - IEqualityComparer comparer) => MoreEnumerable. IndexBy(source, keySelector, comparer); + IEqualityComparer? comparer) => MoreEnumerable. IndexBy(source, keySelector, comparer); } @@ -3013,7 +3064,7 @@ public static partial class LagExtension /// A projection function which accepts the current and lagged items (in that order) and returns a result /// A sequence produced by projecting each element of the sequence with its lagged pairing - public static IEnumerable Lag(this IEnumerable source, int offset, Func resultSelector) + public static IEnumerable Lag(this IEnumerable source, int offset, Func resultSelector) => MoreEnumerable.Lag(source, offset, resultSelector); /// @@ -3076,7 +3127,7 @@ public static partial class LastOrDefaultExtension /// otherwise, the last element in source. /// - public static T LastOrDefault(this IExtremaEnumerable source) + public static T? LastOrDefault(this IExtremaEnumerable source) => MoreEnumerable.LastOrDefault(source); } @@ -3101,7 +3152,7 @@ public static partial class LeadExtension /// A projection function which accepts the current and subsequent (lead) element (in that order) and produces a result /// A sequence produced by projecting each element of the sequence with its lead pairing - public static IEnumerable Lead(this IEnumerable source, int offset, Func resultSelector) + public static IEnumerable Lead(this IEnumerable source, int offset, Func resultSelector) => MoreEnumerable.Lead(source, offset, resultSelector); /// @@ -3203,7 +3254,7 @@ public static IEnumerable LeftJoin( Func keySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.LeftJoin(first, second, keySelector, firstSelector, bothSelector, comparer); /// @@ -3288,7 +3339,7 @@ public static IEnumerable LeftJoin( Func secondKeySelector, Func firstSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.LeftJoin(first, second, firstKeySelector, secondKeySelector, firstSelector, bothSelector, comparer); } @@ -3312,7 +3363,7 @@ public static partial class MaxByExtension /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare - /// The maximal element, according to the projection. + /// The sequence of maximal elements, according to the projection. /// or is null public static IExtremaEnumerable MaxBy(this IEnumerable source, @@ -3332,12 +3383,12 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// Source sequence /// Selector to use to pick the results to compare /// Comparer to use to compare projected values - /// The maximal element, according to the projection. + /// The sequence of maximal elements, according to the projection. /// , /// or is null public static IExtremaEnumerable MaxBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) => MoreEnumerable.MaxBy(source, selector, comparer); } @@ -3360,7 +3411,7 @@ public static partial class MinByExtension /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare - /// The minimal element, according to the projection. + /// The sequence of minimal elements, according to the projection. /// or is null public static IExtremaEnumerable MinBy(this IEnumerable source, @@ -3380,12 +3431,12 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// Source sequence /// Selector to use to pick the results to compare /// Comparer to use to compare projected values - /// The minimal element, according to the projection. + /// The sequence of minimal elements, according to the projection. /// , /// or is null public static IExtremaEnumerable MinBy(this IEnumerable source, - Func selector, IComparer comparer) + Func selector, IComparer? comparer) => MoreEnumerable.MinBy(source, selector, comparer); } @@ -3454,7 +3505,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); } @@ -3508,7 +3559,7 @@ public static IEnumerable OrderedMerge( public static IEnumerable OrderedMerge( this IEnumerable first, IEnumerable second, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.OrderedMerge(first, second, comparer); /// @@ -3612,7 +3663,7 @@ public static IEnumerable OrderedMerge( Func firstSelector, Func secondSelector, Func bothSelector, - IComparer comparer) + IComparer? comparer) => MoreEnumerable.OrderedMerge(first, second, keySelector, firstSelector, secondSelector, bothSelector, comparer); /// @@ -3701,7 +3752,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); } @@ -3734,7 +3785,7 @@ public static partial class PadExtension /// 123, 456, 789 and two zeroes, in turn. /// - public static IEnumerable Pad(this IEnumerable source, int width) + public static IEnumerable Pad(this IEnumerable source, int width) => MoreEnumerable.Pad(source, width); /// @@ -3820,7 +3871,7 @@ public static partial class PadStartExtension /// The result variable will contain { 0, 0, 123, 456, 789 }. /// - public static IEnumerable PadStart(this IEnumerable source, int width) + public static IEnumerable PadStart(this IEnumerable source, int width) => MoreEnumerable.PadStart(source, width); /// @@ -3924,7 +3975,7 @@ public static partial class PartialSortExtension { /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// /// Type of elements in the sequence. @@ -3941,7 +3992,7 @@ public static IEnumerable PartialSort(this IEnumerable source, int coun /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// An additional parameter specifies the direction of the sort /// @@ -3961,7 +4012,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. An additional parameter specifies how the /// elements compare to each other. /// @@ -3976,12 +4027,12 @@ 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); /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// Additional parameters specify how the elements compare to each other and /// the direction of the sort. @@ -3998,7 +4049,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); } @@ -4011,7 +4062,7 @@ public static partial class PartialSortByExtension /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// /// Type of elements in the sequence. /// Type of keys. @@ -4031,7 +4082,7 @@ public static IEnumerable PartialSortBy( /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// An additional parameter specifies the direction of the sort /// /// Type of elements in the sequence. @@ -4053,7 +4104,7 @@ public static IEnumerable PartialSortBy( /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// An additional parameter specifies how the keys compare to each other. /// /// Type of elements in the sequence. @@ -4071,12 +4122,12 @@ 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); /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// Additional parameters specify how the elements compare to each other and /// the direction of the sort. /// @@ -4096,7 +4147,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); @@ -4114,9 +4165,11 @@ public static partial class PartitionExtension /// The predicate function. /// Type of source elements. /// - /// A tuple of elements staisfying the predicate and those that do not, + /// A tuple of elements satisfying the predicate and those that do not, /// respectively. /// + /// is + /// . /// /// True, IEnumerable False) /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, Func, IEnumerable, TResult> resultSelector) @@ -4164,6 +4221,10 @@ public static TResult Partition(this IEnumerable> /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, Func, IEnumerable, IEnumerable, TResult> resultSelector) @@ -4185,6 +4246,10 @@ public static TResult Partition(this IEnumerable /// /// The return value from . /// + /// + /// , , or + /// is . + /// /// /// (this IEnumerable source, /// matching a key and those groups that do not. /// /// Type of keys in source groupings. - /// Type of elements in source groupings. + /// Type of elements in source + /// groupings. /// Type of the result. /// The source sequence. /// The key to partition. /// /// Function that projects the result from sequences of elements - /// matching and those groups that do not (in - /// the order in which they appear in ), - /// passed as arguments. + /// matching and those groups that do not (in the + /// order in which they appear in ), passed as + /// arguments. /// /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key, @@ -4243,6 +4313,10 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key1, TKey key2, @@ -4268,9 +4342,13 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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); @@ -4295,6 +4373,10 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key1, TKey key2, TKey key3, @@ -4322,9 +4404,13 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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); @@ -4351,9 +4437,13 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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); } @@ -4544,7 +4634,7 @@ public static IEnumerable Rank(this IEnumerable source) /// A object that defines comparison semantics for the elements in the sequence /// A sequence of position integers representing the ranks of the corresponding items in the sequence - public static IEnumerable Rank(this IEnumerable source, IComparer comparer) + public static IEnumerable Rank(this IEnumerable source, IComparer? comparer) => MoreEnumerable.Rank(source, comparer); } @@ -4577,7 +4667,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); } @@ -4690,7 +4780,7 @@ public static IEnumerable RightJoin( Func keySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.RightJoin(first, second, keySelector, secondSelector, bothSelector, comparer); /// @@ -4775,7 +4865,7 @@ public static IEnumerable RightJoin( Func secondKeySelector, Func secondSelector, Func bothSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.RightJoin(first, second, firstKeySelector, secondKeySelector, secondSelector, bothSelector, comparer); } @@ -4806,7 +4896,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); } @@ -4933,7 +5023,7 @@ public static IEnumerable> ScanBy keySelector, Func seedSelector, Func accumulator, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ScanBy(source, keySelector, seedSelector, accumulator, comparer); } @@ -4944,7 +5034,7 @@ public static IEnumerable> ScanBy - /// Peforms a right-associative scan (inclusive prefix) on a sequence of elements. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. /// This operator is the right-associative version of the /// LINQ operator. /// @@ -4957,7 +5047,7 @@ public static partial class ScanRightExtension /// The scanned sequence. /// /// i.ToString()).ScanRight((a, b) => string.Format("({0}/{1})", a, b)); + /// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. /// @@ -4970,7 +5060,7 @@ public static IEnumerable ScanRight(this IEnumerable => MoreEnumerable.ScanRight(source, func); /// - /// Peforms a right-associative scan (inclusive prefix) on a sequence of elements. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. /// The specified seed value is used as the initial accumulator value. /// This operator is the right-associative version of the /// LINQ operator. @@ -4983,7 +5073,7 @@ public static IEnumerable ScanRight(this IEnumerable /// The scanned sequence. /// /// string.Format("({0}/{1})", a, b)); + /// var result = Enumerable.Range(1, 4).ScanRight("5", (a, b) => $"({a}+{b})"); /// ]]> /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. /// @@ -5115,6 +5205,7 @@ public static partial class SingleExtension /// The single element of the input sequence. /// +#pragma warning disable CA1720 // Identifier contains type name public static T Single(this IExtremaEnumerable source) => MoreEnumerable.Single(source); @@ -5139,7 +5230,7 @@ public static partial class SingleOrDefaultExtension /// if the sequence contains no elements. /// - public static T SingleOrDefault(this IExtremaEnumerable source) + public static T? SingleOrDefault(this IExtremaEnumerable source) => MoreEnumerable.SingleOrDefault(source); } @@ -5275,7 +5366,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); } @@ -5337,7 +5428,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); /// @@ -5369,7 +5460,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); /// @@ -5494,7 +5585,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); @@ -5548,7 +5639,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); } @@ -5776,7 +5867,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); } @@ -6324,7 +6415,8 @@ public static partial class ToDictionaryExtension /// mapped to their keys. /// - public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) => MoreEnumerable.ToDictionary(source); + public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) + where TKey : notnull => MoreEnumerable.ToDictionary(source); /// /// Creates a from a sequence of /// elements. @@ -6337,7 +6429,8 @@ public static partial class ToDictionaryExtension /// mapped to their keys. /// - public static Dictionary ToDictionary(this IEnumerable> source) => MoreEnumerable.ToDictionary(source); + public static Dictionary ToDictionary(this IEnumerable> source) + where TKey : notnull => MoreEnumerable.ToDictionary(source); /// /// Creates a from a sequence of @@ -6354,7 +6447,8 @@ public static partial class ToDictionaryExtension /// public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) + where TKey : notnull => MoreEnumerable.ToDictionary(source, comparer); /// @@ -6372,7 +6466,8 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) + where TKey : notnull => MoreEnumerable.ToDictionary(source, comparer); } @@ -6410,7 +6505,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); } @@ -6464,7 +6559,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); /// @@ -6482,7 +6577,7 @@ public static ILookup ToLookup(this IEnumerable<(TKe /// public static ILookup ToLookup(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) => MoreEnumerable.ToLookup(source, comparer); } @@ -6526,7 +6621,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); /// @@ -6588,99 +6683,17 @@ public static IEnumerable> Transpose(this IEnumerableTrySingle extension. - - [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] - public static partial class TrySingleExtension - { - /// - /// Returns a tuple with the cardinality of the sequence and the - /// single element in the sequence if it contains exactly one element. - /// similar to . - /// - /// The source sequence. - /// - /// The value that is returned in the tuple if the sequence has zero - /// elements. - /// - /// The value that is returned in the tuple if the sequence has a - /// single element only. - /// - /// The value that is returned in the tuple if the sequence has two or - /// more elements. - /// - /// The type of the elements of . - /// - /// The type that expresses cardinality. - /// - /// A tuple containing the identified - /// and either the single value of in the sequence - /// or its default value. - /// - /// This operator uses immediate execution, but never consumes more - /// than two elements from the sequence. - /// - - public static (TCardinality Cardinality, T Value) - TrySingle(this IEnumerable source, - TCardinality zero, TCardinality one, TCardinality many) => MoreEnumerable. TrySingle(source, zero, one, many); - - /// - /// Returns a result projected from the the cardinality of the sequence - /// and the single element in the sequence if it contains exactly one - /// element. - /// - /// The source sequence. - /// - /// The value that is passed as the first argument to - /// if the sequence has zero - /// elements. - /// - /// The value that is passed as the first argument to - /// if the sequence has a - /// single element only. - /// - /// The value that is passed as the first argument to - /// if the sequence has two or - /// more elements. - /// - /// A function that receives the cardinality and, if the - /// sequence has just one element, the value of that element as - /// argument and projects a resulting value of type - /// . - /// - /// The type of the elements of . - /// - /// The type that expresses cardinality. - /// - /// The type of the result value returned by the - /// function. - /// - /// The value returned by . - /// - /// - /// This operator uses immediate execution, but never consumes more - /// than two elements from the sequence. - /// - - public static TResult TrySingle(this IEnumerable source, - TCardinality zero, TCardinality one, TCardinality many, - Func resultSelector) - => MoreEnumerable.TrySingle(source, zero, one, many, resultSelector); - - } - /// Window extension. [GeneratedCode("MoreLinq.ExtensionsGenerator", "1.0.0.0")] public static partial class WindowExtension { /// - /// Processes a sequence into a series of subsequences representing a windowed subset of the original + /// Processes a sequence into a series of sub-sequences representing a windowed subset of the original /// /// /// The number of sequences returned is: Max(0, sequence.Count() - windowSize) + 1
- /// Returned subsequences are buffered, but the overall operation is streamed.
+ /// Returned sub-sequences are buffered, but the overall operation is streamed.
///
/// The type of the elements of the source sequence /// The sequence to evaluate a sliding window over @@ -6805,6 +6818,10 @@ public static partial class ZipLongestExtension /// A sequence that contains elements of the two input sequences, /// combined by . /// + /// + /// , , or is . + /// /// /// ZipLongest( this IEnumerable first, IEnumerable second, - Func resultSelector) + Func resultSelector) => MoreEnumerable.ZipLongest(first, second, resultSelector); /// @@ -6844,6 +6861,11 @@ public static IEnumerable ZipLongest( /// A sequence that contains elements of the three input sequences, /// combined by . /// + /// + /// , , , or is . + /// /// /// ZipLongest( this IEnumerable first, IEnumerable second, IEnumerable third, - Func resultSelector) + Func resultSelector) => MoreEnumerable.ZipLongest(first, second, third, resultSelector); /// @@ -6887,6 +6909,11 @@ public static IEnumerable ZipLongest( /// A sequence that contains elements of the four input sequences, /// combined by . /// + /// + /// , , , , or is . + /// /// /// ZipLongest( IEnumerable second, IEnumerable third, IEnumerable fourth, - Func resultSelector) + Func resultSelector) => MoreEnumerable.ZipLongest(first, second, third, fourth, resultSelector); } @@ -6933,6 +6960,10 @@ public static partial class ZipShortestExtension /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. /// + /// + /// , , or is . + /// /// ZipShortest( /// /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. + /// + /// , , , or is . + /// /// /// ZipShortest( /// /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. + /// + /// , , , , or is . + /// /// /// FallbackIfEmpty(this IEnumerable source, IEnu } static IEnumerable FallbackIfEmptyImpl(IEnumerable source, - int? count, T fallback1, T fallback2, T fallback3, T fallback4, - IEnumerable fallback) + int? count, T? fallback1, T? fallback2, T? fallback3, T? fallback4, + IEnumerable? fallback) { - return source.TryGetCollectionCount() is int collectionCount + return source.TryGetCollectionCount() is {} collectionCount ? collectionCount == 0 ? Fallback() : source : _(); @@ -183,19 +183,16 @@ 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 ?? FallbackOnArgs(); IEnumerable FallbackOnArgs() { - yield return fallback1; - if (count > 1) yield return fallback2; - if (count > 2) yield return fallback3; - if (count > 3) yield return fallback4; + Debug.Assert(count is >= 1 and <= 4); + + yield return fallback1!; + if (count > 1) yield return fallback2!; + if (count > 2) yield return fallback3!; + if (count > 3) yield return fallback4!; } } } diff --git a/MoreLinq/FillBackward.cs b/MoreLinq/FillBackward.cs index 1b4cc5514..c07c8a81b 100644 --- a/MoreLinq/FillBackward.cs +++ b/MoreLinq/FillBackward.cs @@ -74,7 +74,7 @@ public static IEnumerable FillBackward(this IEnumerable source, Func /// Returns a sequence with each missing element in the source replaced /// with the following non-missing element in that sequence. Additional - /// parameters specifiy two functions, one used to determine if an + /// parameters specify two functions, one used to determine if an /// element is considered missing or not and another to provide the /// replacement for the missing element. /// @@ -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) { diff --git a/MoreLinq/FillForward.cs b/MoreLinq/FillForward.cs index f3561d1fd..dd4b52aad 100644 --- a/MoreLinq/FillForward.cs +++ b/MoreLinq/FillForward.cs @@ -74,7 +74,7 @@ public static IEnumerable FillForward(this IEnumerable source, Func /// Returns a sequence with each missing element in the source replaced /// with one based on the previous non-missing element seen in that - /// sequence. Additional parameters specifiy two functions, one used to + /// sequence. Additional parameters specify two functions, one used to /// determine if an element is considered missing or not and another /// to provide the replacement for the missing element. /// @@ -104,24 +104,23 @@ 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); + (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; } } diff --git a/MoreLinq/Flatten.cs b/MoreLinq/Flatten.cs index e98466c3e..8de35aaf7 100644 --- a/MoreLinq/Flatten.cs +++ b/MoreLinq/Flatten.cs @@ -34,8 +34,14 @@ static partial class MoreEnumerable /// /// is null. - public static IEnumerable Flatten(this IEnumerable source) => - Flatten(source, obj => !(obj is string)); + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source) => + Flatten(source, obj => obj is not string); /// /// Flattens a sequence containing arbitrarily-nested sequences. An @@ -59,7 +65,13 @@ public static IEnumerable Flatten(this IEnumerable source) => /// /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func predicate) + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source, Func predicate) { if (predicate == null) throw new ArgumentNullException(nameof(predicate)); @@ -88,12 +100,31 @@ public static IEnumerable Flatten(this IEnumerable source, Func /// is null. - public static IEnumerable Flatten(this IEnumerable source, Func selector) + public static IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*.............................*/ object +#nullable restore +/*...................................*/ > + Flatten(this IEnumerable source, + Func< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*....................*/ object, +#nullable restore +/*....................*/ IEnumerable?> selector) { if (source == null) throw new ArgumentNullException(nameof(source)); if (selector == null) throw new ArgumentNullException(nameof(selector)); - return _(); IEnumerable _() + return _(); + + IEnumerable< +// Just like "IEnumerable.Current" is null-oblivious, so is this: +#nullable disable +/*...................*/ object +#nullable restore +/*.........................*/ > _() { var e = source.GetEnumerator(); var stack = new Stack(); @@ -110,7 +141,7 @@ public static IEnumerable Flatten(this IEnumerable source, Func(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)); @@ -60,43 +60,40 @@ static TResult FoldImpl(IEnumerable source, int count, || count == 16 && folder16 == null ) { // ReSharper disable NotResolvedInText +#pragma warning disable CA2208 // Instantiate argument exceptions correctly throw new ArgumentNullException("folder"); // ReSharper restore NotResolvedInText +#pragma warning restore CA2208 // Instantiate argument exceptions correctly } var elements = new T[count]; foreach (var e in AssertCountImpl(source.Index(), count, OnFolderSourceSizeErrorSelector)) elements[e.Key] = e.Value; - switch (count) + return count switch { - 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(); - } + 1 => Assume.NotNull(folder1 )(elements[0]), + 2 => Assume.NotNull(folder2 )(elements[0], elements[1]), + 3 => Assume.NotNull(folder3 )(elements[0], elements[1], elements[2]), + 4 => Assume.NotNull(folder4 )(elements[0], elements[1], elements[2], elements[3]), + 5 => Assume.NotNull(folder5 )(elements[0], elements[1], elements[2], elements[3], elements[4]), + 6 => Assume.NotNull(folder6 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5]), + 7 => Assume.NotNull(folder7 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6]), + 8 => Assume.NotNull(folder8 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7]), + 9 => Assume.NotNull(folder9 )(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8]), + 10 => Assume.NotNull(folder10)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9]), + 11 => Assume.NotNull(folder11)(elements[0], elements[1], elements[2], elements[3], elements[4], elements[5], elements[6], elements[7], elements[8], elements[9], elements[10]), + 12 => Assume.NotNull(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]), + 13 => Assume.NotNull(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]), + 14 => Assume.NotNull(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]), + 15 => Assume.NotNull(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]), + 16 => Assume.NotNull(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]), + _ => throw new NotSupportedException() + }; } static readonly Func OnFolderSourceSizeErrorSelector = OnFolderSourceSizeError; - static Exception OnFolderSourceSizeError(int cmp, int count) - { - var message = cmp < 0 - ? "Sequence contains too few elements when exactly {0} {1} expected." - : "Sequence contains too many elements when exactly {0} {1} expected."; - return new InvalidOperationException(string.Format(message, count.ToString("N0"), count == 1 ? "was" : "were")); - } + static Exception OnFolderSourceSizeError(int cmp, int count) => + new InvalidOperationException(FormatSequenceLengthErrorMessage(cmp, count)); } } diff --git a/MoreLinq/From.cs b/MoreLinq/From.cs index 5e6c35360..b9bf76faa 100644 --- a/MoreLinq/From.cs +++ b/MoreLinq/From.cs @@ -38,7 +38,9 @@ public static IEnumerable From(Func function) { return _(); IEnumerable _() { +#pragma warning disable CA1062 // Validate arguments of public methods yield return function(); +#pragma warning restore CA1062 // Validate arguments of public methods } } @@ -59,8 +61,10 @@ public static IEnumerable From(Func function1, Func function2) { return _(); IEnumerable _() { +#pragma warning disable CA1062 // Validate arguments of public methods yield return function1(); yield return function2(); +#pragma warning restore CA1062 // Validate arguments of public methods } } @@ -82,9 +86,11 @@ public static IEnumerable From(Func function1, Func function2, Func< { return _(); IEnumerable _() { +#pragma warning disable CA1062 // Validate arguments of public methods yield return function1(); yield return function2(); yield return function3(); +#pragma warning restore CA1062 // Validate arguments of public methods } } diff --git a/MoreLinq/FullGroupJoin.cs b/MoreLinq/FullGroupJoin.cs index c1a331057..7ce9b687f 100644 --- a/MoreLinq/FullGroupJoin.cs +++ b/MoreLinq/FullGroupJoin.cs @@ -76,7 +76,7 @@ static partial class MoreEnumerable IEnumerable second, Func firstKeySelector, Func secondKeySelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { return FullGroupJoin(first, second, firstKeySelector, secondKeySelector, ValueTuple.Create, comparer); } @@ -139,7 +139,7 @@ public static IEnumerable FullGroupJoin Func firstKeySelector, Func secondKeySelector, Func, IEnumerable, TResult> resultSelector, - IEqualityComparer comparer) + IEqualityComparer? comparer) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); @@ -151,7 +151,7 @@ public static IEnumerable FullGroupJoin IEnumerable _(IEqualityComparer comparer) { - var alookup = Lookup.CreateForJoin(first, firstKeySelector, comparer); + var alookup = Lookup.CreateForJoin(first, firstKeySelector, comparer); var blookup = Lookup.CreateForJoin(second, secondKeySelector, comparer); foreach (var a in alookup) diff --git a/MoreLinq/FullJoin.cs b/MoreLinq/FullJoin.cs index 5f3f782c2..877e35799 100644 --- a/MoreLinq/FullJoin.cs +++ b/MoreLinq/FullJoin.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copysecond (c) 2017 Atif Aziz. All seconds reserved. +// Copyright (c) 2017 Atif Aziz. All seconds reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -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/GlobalRandom.cs b/MoreLinq/GlobalRandom.cs new file mode 100644 index 000000000..900c2c5a2 --- /dev/null +++ b/MoreLinq/GlobalRandom.cs @@ -0,0 +1,78 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2010 Leopold Bushkin. 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 + +#pragma warning disable CA5394 // Do not use insecure randomness + +namespace MoreLinq +{ + using System; +#if !NET6_0_OR_GREATER + using System.Threading; +#endif + + public static partial class MoreEnumerable + { + /// + /// is not thread-safe so the following + /// implementation uses thread-local + /// instances to create the illusion of a global + /// implementation. For some background, + /// see Getting + /// random numbers in a thread-safe way. + /// On .NET 6+, delegates to Random.Shared. + /// + + partial class GlobalRandom { } + +#if NET6_0_OR_GREATER + static partial class GlobalRandom + { + public static Random Instance => System.Random.Shared; + } +#else // NET6_0_OR_GREATER + sealed partial class GlobalRandom : Random + { + public static Random Instance { get; } = new GlobalRandom(); + + static int _seed = Environment.TickCount; + [ThreadStatic] static Random? _threadRandom; + static Random ThreadRandom => _threadRandom ??= new Random(Interlocked.Increment(ref _seed)); + + GlobalRandom() { } + + public override int Next() => ThreadRandom.Next(); + public override int Next(int minValue, int maxValue) => ThreadRandom.Next(minValue, maxValue); + public override int Next(int maxValue) => ThreadRandom.Next(maxValue); + public override double NextDouble() => ThreadRandom.NextDouble(); + public override void NextBytes(byte[] buffer) => ThreadRandom.NextBytes(buffer); + + protected override double Sample() + { + // All the NextXXX calls are hijacked above to use the Random + // instance allocated for the thread so no call from the base + // class should ever end up here. If Random introduces new + // virtual members in the future that call into Sample and + // which end up getting used in the implementation of a + // randomizing operator from the outer class then they will + // need to be overriden. + + throw new NotImplementedException(); + } + } +#endif // NET6_0_OR_GREATER + } +} diff --git a/MoreLinq/GroupAdjacent.cs b/MoreLinq/GroupAdjacent.cs index 3523bb690..162d23735 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 @@ -83,12 +82,12 @@ 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)); - return GroupAdjacent(source, keySelector, e => e, comparer); + return GroupAdjacent(source, keySelector, IdFn, comparer); } /// @@ -160,7 +159,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)); @@ -209,7 +208,7 @@ public static IEnumerable GroupAdjacent( // This should be removed once the target framework is bumped to something that supports covariance TResult ResultSelectorWrapper(TKey key, IList group) => resultSelector(key, group); - return GroupAdjacentImpl(source, keySelector, i => i, ResultSelectorWrapper, + return GroupAdjacentImpl(source, keySelector, IdFn, ResultSelectorWrapper, EqualityComparer.Default); } @@ -246,7 +245,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)); @@ -254,7 +253,7 @@ public static IEnumerable GroupAdjacent( // This should be removed once the target framework is bumped to something that supports covariance TResult ResultSelectorWrapper(TKey key, IList group) => resultSelector(key, group); - return GroupAdjacentImpl(source, keySelector, i => i, ResultSelectorWrapper, + return GroupAdjacentImpl(source, keySelector, IdFn, ResultSelectorWrapper, comparer ?? EqualityComparer.Default); } @@ -265,48 +264,44 @@ 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; + (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 (var 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 (var k, {} members)) + yield return resultSelector(k, members); + } } - static IGrouping CreateGroupAdjacentGrouping(TKey key, IList members) - { - Debug.Assert(members != null); - 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 { public static Grouping Create(TKey key, IEnumerable members) => - new Grouping(key, members); + new(key, members); } #if !NO_SERIALIZATION_ATTRIBUTES @@ -318,7 +313,6 @@ sealed class Grouping : IGrouping public Grouping(TKey key, IEnumerable members) { - Debug.Assert(members != null); Key = key; _members = members; } diff --git a/MoreLinq/IdFn.cs b/MoreLinq/IdFn.cs new file mode 100644 index 000000000..74a32f491 --- /dev/null +++ b/MoreLinq/IdFn.cs @@ -0,0 +1,24 @@ +#region License and Terms +// MoreLINQ - Extensions to LINQ to Objects +// Copyright (c) 2022 Turning Code, LLC. 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 +{ + static partial class MoreEnumerable + { + static T IdFn(T x) => x; + } +} diff --git a/MoreLinq/IndexBy.cs b/MoreLinq/IndexBy.cs index 1a407b07c..aba93f82f 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, _ => (Index: -1, Item: default(TSource)), (s, _, 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 cd19e19a6..47ec2af7e 100644 --- a/MoreLinq/Interleave.cs +++ b/MoreLinq/Interleave.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; public static partial class MoreEnumerable @@ -45,127 +44,63 @@ public static partial class MoreEnumerable /// A sequence of interleaved elements from all of the source sequences public static IEnumerable Interleave(this IEnumerable sequence, params IEnumerable[] otherSequences) - { - return Interleave(sequence, ImbalancedInterleaveStrategy.Skip, otherSequences); - } - - /// - /// Interleaves the elements of two or more sequences into a single sequence, applying the specified strategy when sequences are of unequal length - /// - /// - /// Interleave combines sequences by visiting each in turn, and returning the first element of each, followed - /// by the second, then the third, and so on. So, for example:
- /// { 1,2,3,1,2,3,1,2,3 } - /// ]]> - /// This operator behaves in a deferred and streaming manner.
- /// When sequences are of unequal length, this method will use the imbalance strategy specified to - /// decide how to continue interleaving the remaining sequences. See - /// for more information.
- /// The sequences are interleaved in the order that they appear in the - /// collection, with as the first sequence. - ///
- /// The type of the elements of the source sequences - /// The first sequence in the interleave group - /// Defines the behavior of the operator when sequences are of unequal length - /// The other sequences in the interleave group - /// A sequence of interleaved elements from all of the source sequences - - static IEnumerable Interleave(this IEnumerable sequence, ImbalancedInterleaveStrategy imbalanceStrategy, params IEnumerable[] otherSequences) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); if (otherSequences == null) throw new ArgumentNullException(nameof(otherSequences)); if (otherSequences.Any(s => s == null)) throw new ArgumentNullException(nameof(otherSequences), "One or more sequences passed to Interleave was null."); - return _(); IEnumerable _() - { - var sequences = new[] { sequence }.Concat(otherSequences); + return _(otherSequences.Prepend(sequence)); - // produce an iterator collection for all IEnumerable instancess passed to us - var iterators = sequences.Select(e => e.GetEnumerator()).Acquire(); - List> iteratorList = null; + static IEnumerable _(IEnumerable> sequences) + { + var enumerators = new LinkedList>(); try { - iteratorList = new List>(iterators); - iterators = null; - var shouldContinue = true; - var consumedIterators = 0; - var iterCount = iteratorList.Count; + // First, yield first element of each sequence. - while (shouldContinue) + foreach (var sequence in sequences) { - // advance every iterator and verify a value exists to be yielded - for (var index = 0; index < iterCount; index++) - { - if (!iteratorList[index].MoveNext()) - { - // check if all iterators have been consumed and we can terminate - // or if the imbalance strategy informs us that we MUST terminate - if (++consumedIterators == iterCount || imbalanceStrategy == ImbalancedInterleaveStrategy.Stop) - { - shouldContinue = false; - break; - } - - iteratorList[index].Dispose(); // dispose the iterator sice we no longer need it + var enumerator = sequence.GetEnumerator(); - // otherwise, apply the imbalance strategy - switch (imbalanceStrategy) - { - case ImbalancedInterleaveStrategy.Pad: - var newIter = iteratorList[index] = Generate(default(T), x => default).GetEnumerator(); - newIter.MoveNext(); - break; + enumerators.AddLast(enumerator); + if (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + else // Dispose and remove empty sequence + { + enumerator.Dispose(); + enumerators.Remove(enumerator); + } + } - case ImbalancedInterleaveStrategy.Skip: - iteratorList.RemoveAt(index); // no longer visit this particular iterator - --iterCount; // reduce the expected number of iterators to visit - --index; // decrement iterator index to compensate for index shifting - --consumedIterators; // decrement consumer iterator count to stay in balance - break; - } + // Then, yield remaining elements from each sequence. - } + var node = enumerators.First; + while (node is { Value: var enumerator, Next: var nextNode }) + { + if (enumerator.MoveNext()) + { + yield return enumerator.Current; } - - if (shouldContinue) // only if all iterators could be advanced + else { - // yield the values of each iterator's current position - for (var index = 0; index < iterCount; index++) - { - yield return iteratorList[index].Current; - } + enumerator.Dispose(); + enumerators.Remove(node); } + + // Work on next node or restart from first one. + node = nextNode ?? enumerators.First; } } finally { - Debug.Assert(iteratorList != null || iterators != null); - foreach (var iter in (iteratorList ?? (IList>) iterators)) - iter.Dispose(); + foreach (var enumerator in enumerators) + enumerator.Dispose(); } } } - - /// - /// Defines the strategies available when Interleave is passed sequences of unequal length - /// - enum ImbalancedInterleaveStrategy - { - /// - /// Extends a sequence by padding its tail with default(T) - /// - Pad, - /// - /// Removes the sequence from the interleave set, and continues interleaving remaining sequences. - /// - Skip, - /// - /// Stops the interleave operation. - /// - Stop, - } } } diff --git a/MoreLinq/Lag.cs b/MoreLinq/Lag.cs index 51cdf5808..66bd4a1e7 100644 --- a/MoreLinq/Lag.cs +++ b/MoreLinq/Lag.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Linq; public static partial class MoreEnumerable { @@ -36,9 +37,13 @@ public static partial class MoreEnumerable /// A projection function which accepts the current and lagged items (in that order) and returns a result /// A sequence produced by projecting each element of the sequence with its lagged pairing - public static IEnumerable Lag(this IEnumerable source, int offset, Func resultSelector) + public static IEnumerable Lag(this IEnumerable source, int offset, Func resultSelector) { - return Lag(source, offset, default, resultSelector); + if (source == null) throw new ArgumentNullException(nameof(source)); + if (resultSelector is null) throw new ArgumentNullException(nameof(resultSelector)); + + return source.Select(Some) + .Lag(offset, default, (curr, lag) => resultSelector(curr.Value, lag is (true, var some) ? some : default)); } /// diff --git a/MoreLinq/Lead.cs b/MoreLinq/Lead.cs index a354aea30..25c7637c7 100644 --- a/MoreLinq/Lead.cs +++ b/MoreLinq/Lead.cs @@ -19,6 +19,7 @@ namespace MoreLinq { using System; using System.Collections.Generic; + using System.Linq; public static partial class MoreEnumerable { @@ -37,9 +38,13 @@ public static partial class MoreEnumerable /// A projection function which accepts the current and subsequent (lead) element (in that order) and produces a result /// A sequence produced by projecting each element of the sequence with its lead pairing - public static IEnumerable Lead(this IEnumerable source, int offset, Func resultSelector) + public static IEnumerable Lead(this IEnumerable source, int offset, Func resultSelector) { - return Lead(source, offset, default, resultSelector); + if (source is null) throw new ArgumentNullException(nameof(source)); + if (resultSelector is null) throw new ArgumentNullException(nameof(resultSelector)); + + return source.Select(Some) + .Lead(offset, default, (curr, lead) => resultSelector(curr.Value, lead is (true, var some) ? some : default)); } /// diff --git a/MoreLinq/LeftJoin.cs b/MoreLinq/LeftJoin.cs index d4af068fb..b20fe47a5 100644 --- a/MoreLinq/LeftJoin.cs +++ b/MoreLinq/LeftJoin.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copysecond (c) 2017 Atif Aziz. All seconds reserved. +// Copyright (c) 2017 Atif Aziz. All seconds reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -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..d0d4c44de 100644 --- a/MoreLinq/ListLike.cs +++ b/MoreLinq/ListLike.cs @@ -36,11 +36,14 @@ static class ListLike public static IListLike ToListLike(this IEnumerable source) => source.TryAsListLike() ?? new List(source.ToList()); - 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; + public static IListLike? TryAsListLike(this IEnumerable source) => + source switch + { + null => throw new ArgumentNullException(nameof(source)), + IList list => new List(list), + IReadOnlyList list => new ReadOnlyList(list), + _ => null + }; sealed class List : IListLike { diff --git a/MoreLinq/Lookup.cs b/MoreLinq/Lookup.cs index 7c873a64e..a25b104ec 100644 --- a/MoreLinq/Lookup.cs +++ b/MoreLinq/Lookup.cs @@ -29,250 +29,245 @@ namespace MoreLinq using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Linq; /// /// A implementation that preserves insertion order /// - /// The type of the keys in the - /// The type of the elements in the sequences that make up the values in the /// - /// This implementation preserves insertion order of keys and elements within each - /// Copied over from CoreFX on 2015-10-27 - /// https://github.com/dotnet/corefx/blob/6f1c2a86fb8fa1bdaee7c6e70a684d27842d804c/src/System.Linq/src/System/Linq/Enumerable.cs#L3230-L3403 - /// Modified to remove internal interfaces + /// This implementation preserves insertion order of keys and elements within each . Copied and modified from + /// Lookup.cs /// - internal class Lookup : IEnumerable>, ILookup + + [DebuggerDisplay("Count = {Count}")] + internal sealed class Lookup : ILookup { - private IEqualityComparer _comparer; - private Grouping[] _groupings; - private Grouping _lastGrouping; - private int _count; + readonly IEqualityComparer _comparer; + Grouping[] _groupings; + Grouping? _lastGrouping; + int _count; - internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer comparer) + internal static Lookup Create(IEnumerable source, Func keySelector, Func elementSelector, IEqualityComparer? comparer) { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); - Lookup lookup = new Lookup(comparer); - foreach (TSource item in source) { - lookup.GetGrouping(keySelector(item), true).Add(elementSelector(item)); + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); + Debug.Assert(elementSelector is not null); + + var lookup = new Lookup(comparer); + + foreach (var item in source) + { + var grouping = Assume.NotNull(lookup.GetGrouping(keySelector(item), create: true)); + grouping.Add(elementSelector(item)); } + return lookup; } - internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer comparer) + internal static Lookup Create(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - Lookup lookup = new Lookup(comparer); - foreach (TElement item in source) { - TKey key = keySelector(item); - if (key != null) lookup.GetGrouping(key, true).Add(item); + Debug.Assert(source is not null); + Debug.Assert(keySelector is not null); + + var lookup = new Lookup(comparer); + + foreach (var item in source) + { + var grouping = Assume.NotNull(lookup.GetGrouping(keySelector(item), create: true)); + grouping.Add(item); } + return lookup; } - private Lookup(IEqualityComparer comparer) + internal static Lookup CreateForJoin(IEnumerable source, Func keySelector, IEqualityComparer? comparer) { - if (comparer == null) comparer = EqualityComparer.Default; - _comparer = comparer; - _groupings = new Grouping[7]; + var lookup = new Lookup(comparer); + + foreach (var item in source) + { + if (keySelector(item) is { } key) + { + var grouping = Assume.NotNull(lookup.GetGrouping(key, create: true)); + grouping.Add(item); + } + } + + return lookup; } - public int Count + Lookup(IEqualityComparer? comparer) { - get { return _count; } + _comparer = comparer ?? EqualityComparer.Default; + _groupings = new Grouping[7]; } + public int Count => _count; + public IEnumerable this[TKey key] { get { - Grouping grouping = GetGrouping(key, false); - if (grouping != null) return grouping; - return Enumerable.Empty(); + var grouping = GetGrouping(key, create: false); + return grouping ?? Enumerable.Empty(); } } - public bool Contains(TKey key) - { - return _count > 0 && GetGrouping(key, false) != null; - } + public bool Contains(TKey key) => GetGrouping(key, create: false) is not null; public IEnumerator> GetEnumerator() { - Grouping g = _lastGrouping; - if (g != null) { - do { - g = g.next; + var g = _lastGrouping; + if (g is not null) + { + do + { + g = Assume.NotNull(g._next); yield return g; - } while (g != _lastGrouping); - } - } - - public IEnumerable ApplyResultSelector(Func, TResult> resultSelector) - { - Grouping g = _lastGrouping; - if (g != null) { - do { - g = g.next; - if (g.count != g.elements.Length) { Array.Resize(ref g.elements, g.count); } - yield return resultSelector(g.key, g.elements); - } while (g != _lastGrouping); + } + while (g != _lastGrouping); } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal int InternalGetHashCode(TKey key) - { + int InternalGetHashCode(TKey key) => // Handle comparer implementations that throw when passed null - return (key == null) ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; - } + key is null ? 0 : _comparer.GetHashCode(key) & 0x7FFFFFFF; - internal Grouping GetGrouping(TKey key, bool create) + internal Grouping? GetGrouping(TKey key, bool create) { - int hashCode = InternalGetHashCode(key); - for (Grouping g = _groupings[hashCode % _groupings.Length]; g != null; g = g.hashNext) - if (g.hashCode == hashCode && _comparer.Equals(g.key, key)) return g; - if (create) { - if (_count == _groupings.Length) Resize(); - int index = hashCode % _groupings.Length; - Grouping g = new Grouping(); - g.key = key; - g.hashCode = hashCode; - g.elements = new TElement[1]; - g.hashNext = _groupings[index]; + var hashCode = InternalGetHashCode(key); + for (var g = _groupings[hashCode % _groupings.Length]; g is not null; g = g._hashNext) + { + if (g._hashCode == hashCode && _comparer.Equals(g._key, key)) + return g; + } + + if (create) + { + if (_count == _groupings.Length) + { + Resize(); + } + + var index = hashCode % _groupings.Length; + var g = new Grouping(key, hashCode); + g._hashNext = _groupings[index]; _groupings[index] = g; - if (_lastGrouping == null) { - g.next = g; + if (_lastGrouping is null) + { + g._next = g; } - else { - g.next = _lastGrouping.next; - _lastGrouping.next = g; + else + { + g._next = _lastGrouping._next; + _lastGrouping._next = g; } + _lastGrouping = g; _count++; return g; } + return null; } - private void Resize() + void Resize() { - int newSize = checked(_count * 2 + 1); - Grouping[] newGroupings = new Grouping[newSize]; - Grouping g = _lastGrouping; - do { - g = g.next; - int index = g.hashCode % newSize; - g.hashNext = newGroupings[index]; + var newSize = checked((_count * 2) + 1); + var newGroupings = new Grouping[newSize]; + var g = Assume.NotNull(_lastGrouping); + do + { + g = Assume.NotNull(g._next); + var index = g._hashCode % newSize; + g._hashNext = newGroupings[index]; newGroupings[index] = g; - } while (g != _lastGrouping); + } + while (g != _lastGrouping); + _groupings = newGroupings; } } - internal class Grouping : IGrouping, IList + // Modified from: + // https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.Linq/src/System/Linq/Grouping.cs#L48-L141 + + [DebuggerDisplay("Key = {Key}")] + internal sealed class Grouping : IGrouping, IList { - internal TKey key; - internal int hashCode; - internal TElement[] elements; - internal int count; - internal Grouping hashNext; - internal Grouping next; - - internal Grouping() + internal readonly TKey _key; + internal readonly int _hashCode; + internal TElement[] _elements; + internal int _count; + internal Grouping? _hashNext; + internal Grouping? _next; + + internal Grouping(TKey key, int hashCode) { + _key = key; + _hashCode = hashCode; + _elements = new TElement[1]; } internal void Add(TElement element) { - if (elements.Length == count) Array.Resize(ref elements, checked(count * 2)); - elements[count] = element; - count++; + if (_elements.Length == _count) + Array.Resize(ref _elements, checked(_count * 2)); + + _elements[_count] = element; + _count++; } - public IEnumerator GetEnumerator() + internal void Trim() { - for (int i = 0; i < count; i++) yield return elements[i]; + if (_elements.Length != _count) + Array.Resize(ref _elements, _count); } - IEnumerator IEnumerable.GetEnumerator() + public IEnumerator GetEnumerator() { - return GetEnumerator(); + for (var i = 0; i < _count; i++) + yield return _elements[i]; } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + // DDB195907: implement IGrouping<>.Key implicitly // so that WPF binding works on this property. - public TKey Key - { - get { return key; } - } + public TKey Key => _key; - int ICollection.Count - { - get { return count; } - } + int ICollection.Count => _count; - bool ICollection.IsReadOnly - { - get { return true; } - } + bool ICollection.IsReadOnly => true; - void ICollection.Add(TElement item) - { - throw new NotSupportedException("Lookup is immutable"); - } + bool ICollection.Contains(TElement item) => Array.IndexOf(_elements, item, 0, _count) >= 0; - void ICollection.Clear() - { - throw new NotSupportedException("Lookup is immutable"); - } + void ICollection.CopyTo(TElement[] array, int arrayIndex) => + Array.Copy(_elements, 0, array, arrayIndex, _count); - bool ICollection.Contains(TElement item) - { - return Array.IndexOf(elements, item, 0, count) >= 0; - } + int IList.IndexOf(TElement item) => Array.IndexOf(_elements, item, 0, _count); - void ICollection.CopyTo(TElement[] array, int arrayIndex) - { - Array.Copy(elements, 0, array, arrayIndex, count); - } - - bool ICollection.Remove(TElement item) + TElement IList.this[int index] { - throw new NotSupportedException("Lookup is immutable"); - } + get => index < 0 || index >= _count + ? throw new ArgumentOutOfRangeException(nameof(index)) + : _elements[index]; - int IList.IndexOf(TElement item) - { - return Array.IndexOf(elements, item, 0, count); + set => ThrowModificationNotSupportedException(); } - void IList.Insert(int index, TElement item) - { - throw new NotSupportedException("Lookup is immutable"); - } + void ICollection.Add(TElement item) => ThrowModificationNotSupportedException(); + void ICollection.Clear() => ThrowModificationNotSupportedException(); + bool ICollection.Remove(TElement item) { ThrowModificationNotSupportedException(); return false; } + void IList.Insert(int index, TElement item) => ThrowModificationNotSupportedException(); + void IList.RemoveAt(int index) => ThrowModificationNotSupportedException(); - void IList.RemoveAt(int index) - { - throw new NotSupportedException("Lookup is immutable"); - } - - TElement IList.this[int index] - { - get - { - if (index < 0 || index >= count) throw new ArgumentOutOfRangeException(nameof(index)); - return elements[index]; - } - set - { - throw new NotSupportedException("Lookup is immutable"); - } - } + [DoesNotReturn] + static void ThrowModificationNotSupportedException() => throw new NotSupportedException("Grouping is immutable."); } } diff --git a/MoreLinq/MaxBy.cs b/MoreLinq/MaxBy.cs index f9f3ab3f9..0ffe2f5de 100644 --- a/MoreLinq/MaxBy.cs +++ b/MoreLinq/MaxBy.cs @@ -87,7 +87,7 @@ public static T First(this IExtremaEnumerable source) /// otherwise, the first element in source. /// - public static T FirstOrDefault(this IExtremaEnumerable source) + public static T? FirstOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Take(1).AsEnumerable().FirstOrDefault(); @@ -123,10 +123,10 @@ public static T Last(this IExtremaEnumerable source) /// otherwise, the last element in source. /// - public static T LastOrDefault(this IExtremaEnumerable source) + public static T? LastOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); - return source.Take(1).AsEnumerable().LastOrDefault(); + return source.TakeLast(1).AsEnumerable().LastOrDefault(); } /// @@ -142,7 +142,9 @@ public static T LastOrDefault(this IExtremaEnumerable source) /// The single element of the input sequence. /// +#pragma warning disable CA1720 // Identifier contains type name public static T Single(this IExtremaEnumerable source) +#pragma warning restore CA1720 // Identifier contains type name { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Take(2).AsEnumerable().Single(); @@ -161,7 +163,7 @@ public static T Single(this IExtremaEnumerable source) /// if the sequence contains no elements. /// - public static T SingleOrDefault(this IExtremaEnumerable source) + public static T? SingleOrDefault(this IExtremaEnumerable source) { if (source == null) throw new ArgumentNullException(nameof(source)); return source.Take(2).AsEnumerable().SingleOrDefault(); @@ -180,7 +182,7 @@ public static T SingleOrDefault(this IExtremaEnumerable source) /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare - /// The maximal element, according to the projection. + /// The sequence of maximal elements, according to the projection. /// or is null public static IExtremaEnumerable MaxBy(this IEnumerable source, @@ -202,12 +204,12 @@ public static IExtremaEnumerable MaxBy(this IEnumerable< /// Source sequence /// Selector to use to pick the results to compare /// Comparer to use to compare projected values - /// The maximal element, according to the projection. + /// The sequence of maximal elements, according to the projection. /// , /// or is null 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)); @@ -235,39 +237,56 @@ public IEnumerator GetEnumerator() => IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IEnumerable Take(int count) - => count == 0 ? Enumerable.Empty() - : count == 1 ? ExtremaBy(_source, Extremum.First, 1 , _selector, _comparer) - : ExtremaBy(_source, Extrema.First , count, _selector, _comparer); + public IEnumerable Take(int count) => + count switch + { + 0 => Enumerable.Empty(), + 1 => ExtremaBy(_source, Extremum.First, 1 , _selector, _comparer), + _ => ExtremaBy(_source, Extrema.First , count, _selector, _comparer) + }; - public IEnumerable TakeLast(int count) - => count == 0 ? Enumerable.Empty() - : count == 1 ? ExtremaBy(_source, Extremum.Last, 1 , _selector, _comparer) - : ExtremaBy(_source, Extrema.Last , count, _selector, _comparer); + public IEnumerable TakeLast(int count) => + count switch + { + 0 => Enumerable.Empty(), + 1 => ExtremaBy(_source, Extremum.Last, 1 , _selector, _comparer), + _ => ExtremaBy(_source, Extrema.Last , count, _selector, _comparer) + }; 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); @@ -275,19 +294,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); } } } @@ -321,16 +338,16 @@ IEnumerable Extrema() { var item = e.Current; var key = selector(item); - var comparison = comparer(key, extremaKey); - if (comparison > 0) + switch (comparer(key, extremaKey)) { - extrema.Restart(ref store); - extrema.Add(ref store, limit, item); - extremaKey = key; - } - else if (comparison == 0) - { - extrema.Add(ref store, limit, item); + case > 0: + extrema.Restart(ref store); + extrema.Add(ref store, limit, item); + extremaKey = key; + break; + case 0: + extrema.Add(ref store, limit, item); + break; } } @@ -340,25 +357,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); } } } diff --git a/MoreLinq/MinBy.cs b/MoreLinq/MinBy.cs index ed016132b..625d698df 100644 --- a/MoreLinq/MinBy.cs +++ b/MoreLinq/MinBy.cs @@ -35,7 +35,7 @@ static partial class MoreEnumerable /// Type of the projected element /// Source sequence /// Selector to use to pick the results to compare - /// The minimal element, according to the projection. + /// The sequence of minimal elements, according to the projection. /// or is null public static IExtremaEnumerable MinBy(this IEnumerable source, @@ -57,12 +57,12 @@ public static IExtremaEnumerable MinBy(this IEnumerable< /// Source sequence /// Selector to use to pick the results to compare /// Comparer to use to compare projected values - /// The minimal element, according to the projection. + /// The sequence of minimal elements, according to the projection. /// , /// or is null 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)); diff --git a/MoreLinq/MoreEnumerable.cs b/MoreLinq/MoreEnumerable.cs index 09831c851..b80983c3e 100644 --- a/MoreLinq/MoreEnumerable.cs +++ b/MoreLinq/MoreEnumerable.cs @@ -27,14 +27,14 @@ namespace MoreLinq public static partial class MoreEnumerable { - static int? TryGetCollectionCount(this IEnumerable source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - - return source is ICollection collection ? collection.Count - : source is IReadOnlyCollection readOnlyCollection ? readOnlyCollection.Count - : (int?)null; - } + internal static int? TryGetCollectionCount(this IEnumerable source) => + source switch + { + null => throw new ArgumentNullException(nameof(source)), + ICollection collection => collection.Count, + IReadOnlyCollection collection => collection.Count, + _ => null + }; static int CountUpTo(this IEnumerable source, int max) { @@ -43,15 +43,15 @@ static int CountUpTo(this IEnumerable source, int max) var count = 0; - using (var e = source.GetEnumerator()) - { - while (count < max && e.MoveNext()) - { - count++; - } - } + using var e = source.GetEnumerator(); + while (count < max && e.MoveNext()) + count++; return count; } + + // See https://github.com/atifaziz/Optuple + + static (bool HasValue, T Value) Some(T value) => (true, value); } } diff --git a/MoreLinq/MoreLinq.csproj b/MoreLinq/MoreLinq.csproj index dbe55aa50..b07b9a2bd 100644 --- a/MoreLinq/MoreLinq.csproj +++ b/MoreLinq/MoreLinq.csproj @@ -29,6 +29,7 @@ - DistinctBy - EndsWith - EquiZip + - Evaluate - Exactly - ExceptBy - Exclude @@ -45,13 +46,14 @@ - GenerateByIndex - GroupAdjacent - Index + - IndexBy - Insert - Interleave - Lag - Lead - LeftJoin - MaxBy - - Memoize + - Memoize (EXPERIMENTAL) - MinBy - Move - OrderBy @@ -76,6 +78,7 @@ - RightJoin - RunLengthEncode - Scan + - ScanBy - ScanRight - Segment - Sequence @@ -102,7 +105,7 @@ - Transpose - TraverseBreadthFirst - TraverseDepthFirst - - TrySingle + - TrySingle (EXPERIMENTAL) - Unfold - Window - WindowLeft @@ -114,18 +117,19 @@ $([System.Text.RegularExpressions.Regex]::Replace($(Copyright), `\s+`, ` `).Trim()) MoreLINQ en-US - 3.3.0 + 3.3.2 MoreLINQ Developers. - net451;netstandard1.0;netstandard2.0 - 8 - true + net462;netstandard1.0;netstandard2.0;netstandard2.1;net6.0 + + false portable true MoreLinq Library key.snk true - true + true morelinq linq;extensions https://morelinq.github.io/ @@ -143,15 +147,17 @@ - + runtime; build; native; contentfiles; analyzers all - + + + TextTemplatingFileGenerator Aggregate.g.cs @@ -174,27 +180,31 @@ - + - + $(DefineConstants);MORELINQ - - $(DefineConstants);MORELINQ;NO_SERIALIZATION_ATTRIBUTES;NO_EXCEPTION_SERIALIZATION;NO_TRACING;NO_COM;NO_ASYNC + + $(DefineConstants);NO_BUFFERS + + + + $(DefineConstants);NO_BUFFERS;NO_SERIALIZATION_ATTRIBUTES;NO_EXCEPTION_SERIALIZATION;NO_TRACING;NO_COM;NO_ASYNC - + - + @@ -238,8 +248,33 @@ - - - + + + + %(None.LastGenOutput) + + + + + + + + + + + + + + + + + + + + diff --git a/MoreLinq/Move.cs b/MoreLinq/Move.cs index f95f022d4..ea6aaa808 100644 --- a/MoreLinq/Move.cs +++ b/MoreLinq/Move.cs @@ -66,26 +66,25 @@ IEnumerable _(int bufferStartIndex, int bufferSize, int bufferYieldIndex) bool hasMore = true; bool MoveNext(IEnumerator e) => hasMore && (hasMore = e.MoveNext()); - using (var e = source.GetEnumerator()) - { - for (var i = 0; i < bufferStartIndex && MoveNext(e); i++) - yield return e.Current; + using var e = source.GetEnumerator(); - var buffer = new T[bufferSize]; - var length = 0; + for (var i = 0; i < bufferStartIndex && MoveNext(e); i++) + yield return e.Current; - for (; length < bufferSize && MoveNext(e); length++) - buffer[length] = e.Current; + var buffer = new T[bufferSize]; + var length = 0; - for (var i = 0; i < bufferYieldIndex && MoveNext(e); i++) - yield return e.Current; + for (; length < bufferSize && MoveNext(e); length++) + buffer[length] = e.Current; - for (var i = 0; i < length; i++) - yield return buffer[i]; + for (var i = 0; i < bufferYieldIndex && MoveNext(e); i++) + yield return e.Current; - while (MoveNext(e)) - yield return e.Current; - } + for (var i = 0; i < length; i++) + yield return buffer[i]; + + while (MoveNext(e)) + yield return e.Current; } } } diff --git a/MoreLinq/NestedLoops.cs b/MoreLinq/NestedLoops.cs index fd5409259..cb8d74d7a 100644 --- a/MoreLinq/NestedLoops.cs +++ b/MoreLinq/NestedLoops.cs @@ -42,7 +42,7 @@ static IEnumerable NestedLoops(this Action action, IEnumerable loop return _(); IEnumerable _() { var count = loopCounts.Assert(n => n >= 0, - n => new InvalidOperationException("Invalid loop count (must be greater than or equal to zero).")) + _ => new InvalidOperationException("Invalid loop count (must be greater than or equal to zero).")) .DefaultIfEmpty() .Aggregate((acc, x) => acc * x); diff --git a/MoreLinq/OrderBy.cs b/MoreLinq/OrderBy.cs index 4e5ce8565..02820c4bb 100644 --- a/MoreLinq/OrderBy.cs +++ b/MoreLinq/OrderBy.cs @@ -49,7 +49,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) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -84,7 +84,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) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); diff --git a/MoreLinq/OrderedMerge.cs b/MoreLinq/OrderedMerge.cs index bce2a5ac3..0e30e1517 100644 --- a/MoreLinq/OrderedMerge.cs +++ b/MoreLinq/OrderedMerge.cs @@ -68,9 +68,9 @@ 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); + return OrderedMerge(first, second, IdFn, IdFn, IdFn, (a, _) => a, comparer); } /// @@ -98,7 +98,7 @@ public static IEnumerable OrderedMerge( IEnumerable second, Func keySelector) { - return OrderedMerge(first, second, keySelector, a => a, b => b, (a, _) => a, null); + return OrderedMerge(first, second, keySelector, IdFn, IdFn, (a, _) => a, null); } /// @@ -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)); @@ -300,23 +300,21 @@ IEnumerable _(IComparer comparer) var key1 = firstKeySelector(element1); var element2 = e2.Current; var key2 = secondKeySelector(element2); - var comparison = comparer.Compare(key1, key2); - - if (comparison < 0) - { - yield return firstSelector(element1); - gotFirst = e1.MoveNext(); - } - else if (comparison > 0) - { - yield return secondSelector(element2); - gotSecond = e2.MoveNext(); - } - else + switch (comparer.Compare(key1, key2)) { - yield return bothSelector(element1, element2); - gotFirst = e1.MoveNext(); - gotSecond = e2.MoveNext(); + case < 0: + yield return firstSelector(element1); + gotFirst = e1.MoveNext(); + break; + case > 0: + yield return secondSelector(element2); + gotSecond = e2.MoveNext(); + break; + default: + yield return bothSelector(element1, element2); + gotFirst = e1.MoveNext(); + gotSecond = e2.MoveNext(); + break; } } else if (gotSecond) diff --git a/MoreLinq/Pad.cs b/MoreLinq/Pad.cs index d45f2f380..9fc728c35 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 { @@ -46,7 +45,7 @@ static partial class MoreEnumerable /// 123, 456, 789 and two zeroes, in turn. /// - public static IEnumerable Pad(this IEnumerable source, int width) + public static IEnumerable Pad(this IEnumerable source, int width) { return Pad(source, width, default(TSource)); } @@ -114,10 +113,9 @@ public static IEnumerable Pad(this IEnumerable source return PadImpl(source, width, default, paddingSelector); } - static IEnumerable PadImpl(IEnumerable source, - int width, T padding, Func paddingSelector) + static IEnumerable PadImpl(IEnumerable source, int width, + T? padding, Func? paddingSelector) { - Debug.Assert(source != null); Debug.Assert(width >= 0); var count = 0; @@ -128,7 +126,7 @@ static IEnumerable PadImpl(IEnumerable source, } while (count < width) { - yield return paddingSelector != null ? paddingSelector(count) : padding; + yield return paddingSelector != null ? paddingSelector(count) : padding!; count++; } } diff --git a/MoreLinq/PadStart.cs b/MoreLinq/PadStart.cs index db676b082..c561c678f 100644 --- a/MoreLinq/PadStart.cs +++ b/MoreLinq/PadStart.cs @@ -45,7 +45,7 @@ static partial class MoreEnumerable /// The result variable will contain { 0, 0, 123, 456, 789 }. /// - public static IEnumerable PadStart(this IEnumerable source, int width) + public static IEnumerable PadStart(this IEnumerable source, int width) { return PadStart(source, width, default(TSource)); } @@ -116,14 +116,14 @@ public static IEnumerable PadStart(this IEnumerable s } static IEnumerable PadStartImpl(IEnumerable source, - int width, T padding, Func paddingSelector) + int width, T? padding, Func? paddingSelector) { return - source.TryGetCollectionCount() is int collectionCount + source.TryGetCollectionCount() is {} collectionCount ? collectionCount >= width ? source : Enumerable.Range(0, width - collectionCount) - .Select(i => paddingSelector != null ? paddingSelector(i) : padding) + .Select(i => paddingSelector != null ? paddingSelector(i) : padding!) .Concat(source) : _(); IEnumerable _() { @@ -150,7 +150,7 @@ static IEnumerable PadStartImpl(IEnumerable source, var len = width - count; for (var i = 0; i < len; i++) - yield return paddingSelector != null ? paddingSelector(i) : padding; + yield return paddingSelector != null ? paddingSelector(i) : padding!; for (var i = 0; i < count; i++) yield return array[i]; diff --git a/MoreLinq/PartialSort.cs b/MoreLinq/PartialSort.cs index eb09b545e..ecf8b1aec 100644 --- a/MoreLinq/PartialSort.cs +++ b/MoreLinq/PartialSort.cs @@ -19,14 +19,13 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable { /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// /// Type of elements in the sequence. @@ -45,7 +44,7 @@ public static IEnumerable PartialSort(this IEnumerable source, int coun /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// An additional parameter specifies the direction of the sort /// @@ -67,7 +66,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. An additional parameter specifies how the /// elements compare to each other. /// @@ -82,7 +81,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); @@ -90,7 +89,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// /// Combines , - /// where each element is its key, and + /// where each element is its key, and /// in a single operation. /// Additional parameters specify how the elements compare to each other and /// the direction of the sort. @@ -107,7 +106,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) { comparer ??= Comparer.Default; if (direction == OrderByDirection.Descending) @@ -117,7 +116,7 @@ public static IEnumerable PartialSort(this IEnumerable source, /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// /// Type of elements in the sequence. /// Type of keys. @@ -139,7 +138,7 @@ public static IEnumerable PartialSortBy( /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// An additional parameter specifies the direction of the sort /// /// Type of elements in the sequence. @@ -163,7 +162,7 @@ public static IEnumerable PartialSortBy( /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// An additional parameter specifies how the keys compare to each other. /// /// Type of elements in the sequence. @@ -181,7 +180,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)); @@ -190,7 +189,7 @@ public static IEnumerable PartialSortBy( /// /// Combines , - /// and in a single operation. + /// and in a single operation. /// Additional parameters specify how the elements compare to each other and /// the direction of the sort. /// @@ -210,7 +209,7 @@ public static IEnumerable PartialSortBy( public static IEnumerable PartialSortBy( this IEnumerable source, int count, Func keySelector, - IComparer comparer, + IComparer? comparer, OrderByDirection direction) { comparer ??= Comparer.Default; @@ -221,41 +220,42 @@ 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); - 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 = Assume.NotNull(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 diff --git a/MoreLinq/Partition.cs b/MoreLinq/Partition.cs index 0cda564ab..aa72f8725 100644 --- a/MoreLinq/Partition.cs +++ b/MoreLinq/Partition.cs @@ -19,7 +19,6 @@ namespace MoreLinq { using System; using System.Collections.Generic; - using System.Diagnostics; using System.Linq; static partial class MoreEnumerable @@ -31,9 +30,11 @@ static partial class MoreEnumerable /// The predicate function. /// Type of source elements. /// - /// A tuple of elements staisfying the predicate and those that do not, + /// A tuple of elements satisfying the predicate and those that do not, /// respectively. /// + /// is + /// . /// /// True, IEnumerable False) /// /// The return value from . /// + /// + /// , , or + /// is . + /// /// /// (this IEnumerable source, { if (source == null) throw new ArgumentNullException(nameof(source)); if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); + return source.GroupBy(predicate).Partition(resultSelector); } @@ -97,12 +104,16 @@ public static TResult Partition(this IEnumerable source, /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, Func, IEnumerable, TResult> resultSelector) { if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return source.Partition(true, false, (t, f, _) => resultSelector(t, f)); + return source.Partition(key1: true, key2: false, (t, f, _) => resultSelector(t, f)); } /// @@ -120,12 +131,16 @@ public static TResult Partition(this IEnumerable> /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, Func, IEnumerable, IEnumerable, TResult> resultSelector) { if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return source.Partition(true, false, null, (t, f, n, _) => resultSelector(t, f, n)); + return source.Partition(key1: true, key2: false, key3: null, (t, f, n, _) => resultSelector(t, f, n)); } /// @@ -133,24 +148,29 @@ public static TResult Partition(this IEnumerable /// matching a key and those groups that do not. /// /// Type of keys in source groupings. - /// Type of elements in source groupings. + /// Type of elements in source + /// groupings. /// Type of the result. /// The source sequence. /// The key to partition. /// /// Function that projects the result from sequences of elements - /// matching and those groups that do not (in - /// the order in which they appear in ), - /// passed as arguments. + /// matching and those groups that do not (in the + /// order in which they appear in ), passed as + /// arguments. /// /// /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key, Func, IEnumerable>, TResult> resultSelector) => - Partition(source, key, null, resultSelector); + Partition(source, key, comparer: null, resultSelector); /// /// Partitions a grouping and projects a result from group elements @@ -172,14 +192,19 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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, - (a, b, c, rest) => resultSelector(a, rest)); + + return PartitionImpl(source, 1, key, key2: default, key3: default, comparer, + (a, _, _, rest) => resultSelector(a, rest)); } /// @@ -202,11 +227,15 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key1, TKey key2, Func, IEnumerable, IEnumerable>, TResult> resultSelector) => - Partition(source, key1, key2, null, resultSelector); + Partition(source, key1, key2, comparer: null, resultSelector); /// /// Partitions a grouping and projects a result from elements of @@ -230,13 +259,18 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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, key3: default, comparer, (a, b, c, rest) => resultSelector(a, b, rest)); } @@ -261,11 +295,15 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// public static TResult Partition(this IEnumerable> source, TKey key1, TKey key2, TKey key3, Func, IEnumerable, IEnumerable, IEnumerable>, TResult> resultSelector) => - Partition(source, key1, key2, key3, null, resultSelector); + Partition(source, key1, key2, key3, comparer: null, resultSelector); /// /// Partitions a grouping and projects a result from elements groups @@ -290,24 +328,28 @@ public static TResult Partition(this IEnumerable /// The return value from . /// + /// + /// or is + /// . + /// 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); + Debug.Assert(count is > 0 and <= 3); if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); comparer ??= EqualityComparer.Default; - List> etc = null; + List>? etc = null; var groups = new[] { @@ -318,9 +360,9 @@ static TResult PartitionImpl(IEnumerable 0 && comparer.Equals(e.Key, key1) ? 0 - : count > 1 && comparer.Equals(e.Key, key2) ? 1 - : count > 2 && comparer.Equals(e.Key, key3) ? 2 + var i = count > 0 && comparer.Equals(e.Key, key1!) ? 0 + : count > 1 && comparer.Equals(e.Key, key2!) ? 1 + : count > 2 && comparer.Equals(e.Key, key3!) ? 2 : -1; if (i < 0) diff --git a/MoreLinq/PendNode.cs b/MoreLinq/PendNode.cs index 28ebe8c42..65f53e20a 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); @@ -91,7 +91,7 @@ public IEnumerator GetEnumerator() case 1: concat2 = item.Value; break; case 2: concat3 = item.Value; break; case 3: concat4 = item.Value; break; - default: throw new IndexOutOfRangeException(); + default: throw new UnreachableException(); } 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!; 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 afede3963..e60316179 100644 --- a/MoreLinq/Permutations.cs +++ b/MoreLinq/Permutations.cs @@ -20,6 +20,7 @@ namespace MoreLinq using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; public static partial class MoreEnumerable @@ -28,7 +29,7 @@ public static partial class MoreEnumerable /// The private implementation class that produces permutations of a sequence. /// - class PermutationEnumerator : IEnumerator> + sealed class PermutationEnumerator : IEnumerator> { // NOTE: The algorithm used to generate permutations uses the fact that any set // can be put into 1-to-1 correspondence with the set of ordinals number (0..n). @@ -50,7 +51,7 @@ class PermutationEnumerator : IEnumerator> // However, there's a fly in the ointment. The factorial function grows VERY rapidly. // 13! overflows the range of a Int32; while 28! overflows the range of decimal. // To overcome these limitations, the algorithm relies on the fact that the factorial - // of N is equivalent to the evaluation of N-1 nested loops. Unfortunatley, you can't + // of N is equivalent to the evaluation of N-1 nested loops. Unfortunately, you can't // just code up a variable number of nested loops ... this is where .NET generators // with their elegant 'yield return' syntax come to the rescue. // @@ -73,6 +74,8 @@ class PermutationEnumerator : IEnumerator> IEnumerator _generatorIterator; bool _hasMoreResults; + IList? _current; + public PermutationEnumerator(IEnumerable valueSet) { _valueSet = valueSet.ToArray(); @@ -84,40 +87,49 @@ public PermutationEnumerator(IEnumerable valueSet) Reset(); } + [MemberNotNull(nameof(_generatorIterator))] public void Reset() { + _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 + // start a new iteration over the nested loop generator _generatorIterator = _generator.GetEnumerator(); - // we must advance the nestedloop iterator to the initial element, + // we must advance the nested loop 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 } - public IList Current { get; private set; } + public IList Current + { + get + { + Debug.Assert(_current is not null); + return _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(); if (_hasMoreResults) _generatorIterator.Current(); // produce the next permutation ordering // we return prevResult rather than m_HasMoreResults because there is always - // at least one permtuation: the original set. Also, this provides a simple way + // at least one permutation: the original set. Also, this provides a simple way // to deal with the disparity between sets that have only one loop level (size 0-2) // and those that have two or more (size > 2). return prevResult; } - void IDisposable.Dispose() { } + void IDisposable.Dispose() => _generatorIterator.Dispose(); /// /// Transposes elements in the cached permutation array to produce the next permutation @@ -135,23 +147,11 @@ void NextPermutation() while (_permutation[j] > _permutation[k]) k--; - // interchange m_Permutation[j] and m_Permutation[k] - var oldValue = _permutation[k]; - _permutation[k] = _permutation[j]; - _permutation[j] = oldValue; + (_permutation[j], _permutation[k]) = (_permutation[k], _permutation[j]); // move the tail of the permutation after the jth position in increasing order - var x = _permutation.Length - 1; - var y = j + 1; - - while (x > y) - { - oldValue = _permutation[y]; - _permutation[y] = _permutation[x]; - _permutation[x] = oldValue; - x--; - y++; - } + for (int x = _permutation.Length - 1, y = j + 1; x > y; x--, y++) + (_permutation[x], _permutation[y]) = (_permutation[y], _permutation[x]); } /// diff --git a/MoreLinq/Random.cs b/MoreLinq/Random.cs index eaf66bca1..fd4c88757 100644 --- a/MoreLinq/Random.cs +++ b/MoreLinq/Random.cs @@ -15,11 +15,12 @@ // limitations under the License. #endregion +#pragma warning disable CA5394 // Do not use insecure randomness + namespace MoreLinq { using System; using System.Collections.Generic; - using System.Threading; public static partial class MoreEnumerable { @@ -29,6 +30,7 @@ public static partial class MoreEnumerable /// /// An infinite sequence of random integers /// + /// /// The implementation internally uses a shared, thread-local instance of /// to generate a random number on each /// iteration. The actual instance used @@ -41,7 +43,9 @@ public static partial class MoreEnumerable /// in the generation of the sequence of random numbers. Because the /// instance is shared, if multiple sequences /// are generated on the same thread, the order of enumeration affects the - /// resulting sequences. + /// resulting sequences. + /// + /// On .NET 6 or later, System.Random.Shared is used. /// public static IEnumerable Random() @@ -71,6 +75,7 @@ public static IEnumerable Random(Random rand) /// exclusive upper bound for the random values returned /// An infinite sequence of random integers /// + /// /// The implementation internally uses a shared, thread-local instance of /// to generate a random number on each /// iteration. The actual instance used @@ -83,7 +88,9 @@ public static IEnumerable Random(Random rand) /// in the generation of the sequence of random numbers. Because the /// instance is shared, if multiple sequences /// are generated on the same thread, the order of enumeration affects the - /// resulting sequences. + /// resulting sequences. + /// + /// On .NET 6 or later, System.Random.Shared is used. /// public static IEnumerable Random(int maxValue) @@ -118,6 +125,7 @@ public static IEnumerable Random(Random rand, int maxValue) /// Exclusive upper bound of the values returned /// An infinite sequence of random integers /// + /// /// The implementation internally uses a shared, thread-local instance of /// to generate a random number on each /// iteration. The actual instance used @@ -130,7 +138,9 @@ public static IEnumerable Random(Random rand, int maxValue) /// in the generation of the sequence of random numbers. Because the /// instance is shared, if multiple sequences /// are generated on the same thread, the order of enumeration affects the - /// resulting sequences. + /// resulting sequences. + /// + /// On .NET 6 or later, System.Random.Shared is used. /// public static IEnumerable Random(int minValue, int maxValue) @@ -140,7 +150,7 @@ public static IEnumerable Random(int minValue, int maxValue) /// /// Returns an infinite sequence of random integers between a given - /// minumum and a maximum using the supplied random number generator. + /// minimum and a maximum using the supplied random number generator. /// /// Generator used to produce random numbers /// Inclusive lower bound of the values returned @@ -166,6 +176,7 @@ public static IEnumerable Random(Random rand, int minValue, int maxValue) /// /// An infinite sequence of random doubles /// + /// /// The implementation internally uses a shared, thread-local instance of /// to generate a random number on each /// iteration. The actual instance used @@ -178,7 +189,9 @@ public static IEnumerable Random(Random rand, int minValue, int maxValue) /// in the generation of the sequence of random numbers. Because the /// instance is shared, if multiple sequences /// are generated on the same thread, the order of enumeration affects the - /// resulting sequences. + /// resulting sequences. + /// + /// On .NET 6 or later, System.Random.Shared is used. /// public static IEnumerable RandomDouble() @@ -215,44 +228,5 @@ static IEnumerable RandomImpl(Random rand, Func nextValue) while (true) yield return nextValue(rand); } - - /// - /// is not thread-safe so the following - /// implementation uses thread-local - /// instances to create the illusion of a global - /// implementation. For some background, - /// see Getting - /// random numbers in a thread-safe way - /// - - sealed class GlobalRandom : Random - { - public static readonly Random Instance = new GlobalRandom(); - - static int _seed = Environment.TickCount; - [ThreadStatic] static Random _threadRandom; - static Random ThreadRandom => _threadRandom ??= new Random(Interlocked.Increment(ref _seed)); - - GlobalRandom() { } - - public override int Next() => ThreadRandom.Next(); - public override int Next(int minValue, int maxValue) => ThreadRandom.Next(minValue, maxValue); - public override int Next(int maxValue) => ThreadRandom.Next(maxValue); - public override double NextDouble() => ThreadRandom.NextDouble(); - public override void NextBytes(byte[] buffer) => ThreadRandom.NextBytes(buffer); - - protected override double Sample() - { - // All the NextXXX calls are hijacked above to use the Random - // instance allocated for the thread so no call from the base - // class should ever end up here. If Random introduces new - // virtual members in the future that call into Sample and - // which end up getting used in the implementation of a - // randomizing operator from the outer class then they will - // need to be overriden. - - throw new NotImplementedException(); - } - } } } diff --git a/MoreLinq/RandomSubset.cs b/MoreLinq/RandomSubset.cs index 1c27e901c..d0716f57d 100644 --- a/MoreLinq/RandomSubset.cs +++ b/MoreLinq/RandomSubset.cs @@ -37,7 +37,7 @@ public static partial class MoreEnumerable public static IEnumerable RandomSubset(this IEnumerable source, int subsetSize) { - return RandomSubset(source, subsetSize, new Random()); + return RandomSubset(source, subsetSize, GlobalRandom.Instance); } /// @@ -66,7 +66,7 @@ public static IEnumerable RandomSubset(this IEnumerable source, int sub static IEnumerable RandomSubsetImpl(IEnumerable source, Random rand, Func, (T[], int)> seeder) { - // The simplest and most efficient way to return a random subet is to perform + // The simplest and most efficient way to return a random subset is to perform // an in-place, partial Fisher-Yates shuffle of the sequence. While we could do // a full shuffle, it would be wasteful in the cases where subsetSize is shorter // than the length of the sequence. @@ -87,15 +87,15 @@ static IEnumerable RandomSubsetImpl(IEnumerable source, Random rand, Fu // perform in-place, partial Fisher-Yates shuffle while (m < subsetSize) { +#pragma warning disable CA5394 // Do not use insecure randomness var k = g - rand.Next(w); - var tmp = array[k]; - array[k] = array[m]; - array[m] = tmp; +#pragma warning restore CA5394 // Do not use insecure randomness + (array[k], array[m]) = (array[m], array[k]); ++m; --w; } - // yield the random subet as a new sequence + // yield the random subset as a new sequence for (var i = 0; i < subsetSize; i++) yield return array[i]; } diff --git a/MoreLinq/Rank.cs b/MoreLinq/Rank.cs index b0d218276..4e5586382 100644 --- a/MoreLinq/Rank.cs +++ b/MoreLinq/Rank.cs @@ -32,7 +32,7 @@ public static partial class MoreEnumerable public static IEnumerable Rank(this IEnumerable source) { - return source.RankBy(x => x); + return source.RankBy(IdFn); } /// @@ -43,9 +43,9 @@ public static IEnumerable Rank(this IEnumerable source) /// A object that defines comparison semantics for the elements in the sequence /// A sequence of position integers representing the ranks of the corresponding items in the sequence - public static IEnumerable Rank(this IEnumerable source, IComparer comparer) + public static IEnumerable Rank(this IEnumerable source, IComparer? comparer) { - return source.RankBy(x => x, comparer); + return source.RankBy(IdFn, comparer); } /// @@ -72,7 +72,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) { if (source == null) throw new ArgumentNullException(nameof(source)); if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); @@ -83,11 +83,13 @@ IEnumerable _(IComparer comparer) { source = source.ToArray(); // avoid enumerating source twice - var rankDictionary = source.Distinct() - .OrderByDescending(keySelector, comparer) - .Index(1) - .ToDictionary(item => item.Value, - item => item.Key); + var rankDictionary = new Collections.Dictionary(EqualityComparer.Default); + var i = 1; + foreach (var item in source.Distinct() + .OrderByDescending(keySelector, comparer)) + { + rankDictionary[item] = i++; + } // The following loop should not be be converted to a query to // keep this RankBy lazy. 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..bef962abb 100644 --- a/MoreLinq/Reactive/Subject.cs +++ b/MoreLinq/Reactive/Subject.cs @@ -23,9 +23,9 @@ namespace MoreLinq.Reactive 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>(); @@ -66,7 +66,7 @@ public IDisposable Subscribe(IObserver observer) if (observers[i] == observer) { if (_shouldDeleteObserver) - observers[i] = null; + observers[i] = null!; else observers.RemoveAt(i); break; @@ -119,7 +119,7 @@ public void OnError(Exception error) => public void OnCompleted() => OnFinality(ref _completed, true, (observer, _) => observer.OnCompleted()); - void OnFinality(ref TState state, TState value, Action, TState> action) + void OnFinality(ref TState? state, TState value, Action, TState> action) { if (IsMuted) return; diff --git a/MoreLinq/Repeat.cs b/MoreLinq/Repeat.cs index 3da6f838b..8e789b9cf 100644 --- a/MoreLinq/Repeat.cs +++ b/MoreLinq/Repeat.cs @@ -59,8 +59,12 @@ static IEnumerable RepeatImpl(IEnumerable sequence, int? count) { while (count == null || count-- > 0) { +#pragma warning disable CA1851 // Possible multiple enumerations of 'IEnumerable' collection foreach (var item in memo) +#pragma warning restore CA1851 // Possible multiple enumerations of 'IEnumerable' collection + { yield return item; + } } } } diff --git a/MoreLinq/Return.cs b/MoreLinq/Return.cs index a09a688c8..35ffa3adc 100644 --- a/MoreLinq/Return.cs +++ b/MoreLinq/Return.cs @@ -43,7 +43,7 @@ sealed class SingleElementList : IList, IReadOnlyList public T this[int index] { - get => index == 0 ? _item : throw new ArgumentOutOfRangeException(); + get => index == 0 ? _item : throw new ArgumentOutOfRangeException(nameof(index)); set => throw ReadOnlyException(); } @@ -63,8 +63,7 @@ public T this[int index] public void Insert(int index, T item) => throw ReadOnlyException(); public void RemoveAt(int index) => throw ReadOnlyException(); - static NotSupportedException ReadOnlyException() => - new NotSupportedException("Single element list is immutable."); + static NotSupportedException ReadOnlyException() => new("Single element list is immutable."); } } } diff --git a/MoreLinq/ReverseComparer.cs b/MoreLinq/ReverseComparer.cs index 859a1c645..00086383d 100644 --- a/MoreLinq/ReverseComparer.cs +++ b/MoreLinq/ReverseComparer.cs @@ -23,12 +23,17 @@ sealed class ReverseComparer : IComparer { readonly IComparer _underlying; - public ReverseComparer(IComparer underlying) + public ReverseComparer(IComparer? underlying) { _underlying = underlying ?? Comparer.Default; } - public int Compare(T x, T y) + public int Compare +#if NETCOREAPP3_1_OR_GREATER + (T? x, T? y) +#else + (T x, T y) +#endif { var result = _underlying.Compare(x, y); return result < 0 ? 1 : result > 0 ? -1 : 0; diff --git a/MoreLinq/RightJoin.cs b/MoreLinq/RightJoin.cs index 3c7fa1738..7d19fbc39 100644 --- a/MoreLinq/RightJoin.cs +++ b/MoreLinq/RightJoin.cs @@ -1,6 +1,6 @@ #region License and Terms // MoreLINQ - Extensions to LINQ to Objects -// Copysecond (c) 2017 Atif Aziz. All seconds reserved. +// Copyright (c) 2017 Atif Aziz. All seconds reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -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 d60b9f131..566e97a8a 100644 --- a/MoreLinq/RunLengthEncode.cs +++ b/MoreLinq/RunLengthEncode.cs @@ -45,7 +45,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) { if (sequence == null) throw new ArgumentNullException(nameof(sequence)); diff --git a/MoreLinq/Scan.cs b/MoreLinq/Scan.cs index a14ffe599..50a8ac4d0 100644 --- a/MoreLinq/Scan.cs +++ b/MoreLinq/Scan.cs @@ -86,7 +86,7 @@ public static IEnumerable Scan(this IEnumerable (true, seed)); + return ScanImpl(source, transformation, _ => (true, seed)); } static IEnumerable ScanImpl(IEnumerable source, diff --git a/MoreLinq/ScanBy.cs b/MoreLinq/ScanBy.cs index 2f5de2e03..3ca138ced 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)); @@ -89,46 +89,15 @@ public static IEnumerable> ScanBy> _(IEqualityComparer comparer) { - var stateByKey = new Dictionary(comparer); - var prevKey = (HasValue: false, Value: default(TKey)); - var nullKeyState = (HasValue: false, Value: default(TState)); - var state = default(TState); - - bool TryGetState(TKey key, out TState value) - { - if (key == null) - { - value = nullKeyState.Value; - return nullKeyState.HasValue; - } - - return stateByKey.TryGetValue(key, out value); - } + var stateByKey = new Collections.Dictionary(comparer); foreach (var item in source) { 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))) - { - state = seedSelector(key); - } - + var state = stateByKey.TryGetValue(key, out var s) ? s : seedSelector(key); state = accumulator(state, key, item); - - if (key != null) - stateByKey[key] = state; - else - nullKeyState = (true, state); - + stateByKey[key] = state; yield return new KeyValuePair(key, state); - - prevKey = (true, key); } } } diff --git a/MoreLinq/ScanRight.cs b/MoreLinq/ScanRight.cs index 648aaa470..8afefcece 100644 --- a/MoreLinq/ScanRight.cs +++ b/MoreLinq/ScanRight.cs @@ -23,7 +23,7 @@ namespace MoreLinq static partial class MoreEnumerable { /// - /// Peforms a right-associative scan (inclusive prefix) on a sequence of elements. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. /// This operator is the right-associative version of the /// LINQ operator. /// @@ -36,7 +36,7 @@ static partial class MoreEnumerable /// The scanned sequence. /// /// i.ToString()).ScanRight((a, b) => string.Format("({0}/{1})", a, b)); + /// var result = Enumerable.Range(1, 5).Select(i => i.ToString()).ScanRight((a, b) => $"({a}+{b})"); /// ]]> /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. /// @@ -50,14 +50,11 @@ public static IEnumerable ScanRight(this IEnumerable if (source == null) throw new ArgumentNullException(nameof(source)); if (func == null) throw new ArgumentNullException(nameof(func)); - return ScanRightImpl(source, func, - list => list.Count > 0 - ? (list[list.Count - 1], list.Count - 1) - : ((TSource, int)?) null); + return ScanRightImpl(source, func, list => list.Count > 0 ? (list[^1], list.Count - 1) : null); } /// - /// Peforms a right-associative scan (inclusive prefix) on a sequence of elements. + /// Performs a right-associative scan (inclusive prefix) on a sequence of elements. /// The specified seed value is used as the initial accumulator value. /// This operator is the right-associative version of the /// LINQ operator. @@ -70,7 +67,7 @@ public static IEnumerable ScanRight(this IEnumerable /// The scanned sequence. /// /// string.Format("({0}/{1})", a, b)); + /// var result = Enumerable.Range(1, 4).ScanRight("5", (a, b) => $"({a}+{b})"); /// ]]> /// The result variable will contain [ "(1+(2+(3+(4+5))))", "(2+(3+(4+5)))", "(3+(4+5))", "(4+5)", "5" ]. /// diff --git a/MoreLinq/Segment.cs b/MoreLinq/Segment.cs index 198d8d076..72de5fc4b 100644 --- a/MoreLinq/Segment.cs +++ b/MoreLinq/Segment.cs @@ -37,7 +37,7 @@ public static IEnumerable> Segment(this IEnumerable source, { if (newSegmentPredicate == null) throw new ArgumentNullException(nameof(newSegmentPredicate)); - return Segment(source, (curr, prev, index) => newSegmentPredicate(curr)); + return Segment(source, (curr, _, _) => newSegmentPredicate(curr)); } /// @@ -55,7 +55,7 @@ public static IEnumerable> Segment(this IEnumerable source, { if (newSegmentPredicate == null) throw new ArgumentNullException(nameof(newSegmentPredicate)); - return Segment(source, (curr, prev, index) => newSegmentPredicate(curr, index)); + return Segment(source, (curr, _, index) => newSegmentPredicate(curr, index)); } /// @@ -76,44 +76,36 @@ public static IEnumerable> Segment(this IEnumerable source, return _(); IEnumerable> _() { - var index = -1; - using var iter = source.GetEnumerator(); + using var e = source.GetEnumerator(); - var segment = new List(); - var prevItem = default(T); + if (!e.MoveNext()) // break early (it's empty) + yield break; - // ensure that the first item is always part - // of the first segment. This is an intentional - // behavior. Segmentation always begins with - // the second element in the sequence. - if (iter.MoveNext()) - { - ++index; - segment.Add(iter.Current); - prevItem = iter.Current; - } + // Ensure that the first item is always part of the first + // segment. This is an intentional behavior. Segmentation always + // begins with the second element in the sequence. + + var previous = e.Current; + var segment = new List { previous }; - while (iter.MoveNext()) + for (var index = 1; e.MoveNext(); index++) { - ++index; - // check if the item represents the start of a new segment - var isNewSegment = newSegmentPredicate(iter.Current, prevItem, index); - prevItem = iter.Current; + var current = e.Current; - if (!isNewSegment) + if (newSegmentPredicate(current, previous, index)) { - // if not a new segment, append and continue - segment.Add(iter.Current); - continue; + yield return segment; // yield the completed segment + segment = new List { current }; // start a new segment + } + else // not a new segment, append and continue + { + segment.Add(current); } - yield return segment; // yield the completed segment - // start a new segment... - segment = new List { iter.Current }; + previous = current; } - // handle the case of the sequence ending before new segment is detected - if (segment.Count > 0) - yield return segment; + + yield return segment; } } } diff --git a/MoreLinq/Sequence.cs b/MoreLinq/Sequence.cs index 5eaae4ed3..6b7f94502 100644 --- a/MoreLinq/Sequence.cs +++ b/MoreLinq/Sequence.cs @@ -71,7 +71,7 @@ public static IEnumerable Sequence(int start, int stop, int step) : stop <= current) { yield return (int)current; - current = current + step; + current += step; } } } 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/Shuffle.cs b/MoreLinq/Shuffle.cs index 3b2f3df57..c7cac7d6e 100644 --- a/MoreLinq/Shuffle.cs +++ b/MoreLinq/Shuffle.cs @@ -42,7 +42,7 @@ public static partial class MoreEnumerable public static IEnumerable Shuffle(this IEnumerable source) { - return Shuffle(source, new Random()); + return Shuffle(source, GlobalRandom.Instance); } /// diff --git a/MoreLinq/SkipLast.cs b/MoreLinq/SkipLast.cs index 6aebe4c3f..cfee629b3 100644 --- a/MoreLinq/SkipLast.cs +++ b/MoreLinq/SkipLast.cs @@ -41,7 +41,7 @@ public static IEnumerable SkipLast(this IEnumerable source, int count) return source; return - source.TryGetCollectionCount() is int collectionCount + source.TryGetCollectionCount() is {} collectionCount ? source.Take(collectionCount - count) : source.CountDown(count, (e, cd) => (Element: e, Countdown: cd )) .TakeWhile(e => e.Countdown == null) diff --git a/MoreLinq/SkipUntil.cs b/MoreLinq/SkipUntil.cs index debbc04a8..7a738c35c 100644 --- a/MoreLinq/SkipUntil.cs +++ b/MoreLinq/SkipUntil.cs @@ -59,16 +59,17 @@ public static IEnumerable SkipUntil(this IEnumerable return _(); IEnumerable _() { - using var iterator = source.GetEnumerator(); + using var enumerator = source.GetEnumerator(); - while (iterator.MoveNext()) + do { - if (predicate(iterator.Current)) - break; + if (!enumerator.MoveNext()) + yield break; } + while (!predicate(enumerator.Current)); - while (iterator.MoveNext()) - yield return iterator.Current; + while (enumerator.MoveNext()) + yield return enumerator.Current; } } } diff --git a/MoreLinq/Slice.cs b/MoreLinq/Slice.cs index c0db188d8..356ac356d 100644 --- a/MoreLinq/Slice.cs +++ b/MoreLinq/Slice.cs @@ -45,9 +45,12 @@ public static IEnumerable Slice(this IEnumerable sequence, int startInd if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex)); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); - return sequence is IList list ? SliceList(list.Count, i => list[i]) - : sequence is IReadOnlyList readOnlyList ? SliceList(readOnlyList.Count, i => readOnlyList[i]) - : sequence.Skip(startIndex).Take(count); + return sequence switch + { + IList list => SliceList(list.Count, i => list[i]), + IReadOnlyList list => SliceList(list.Count, i => list[i]), + var seq => seq.Skip(startIndex).Take(count) + }; IEnumerable SliceList(int listCount, Func indexer) { diff --git a/MoreLinq/SortedMerge.cs b/MoreLinq/SortedMerge.cs index 7b68a2327..b19273c15 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)); diff --git a/MoreLinq/Split.cs b/MoreLinq/Split.cs index 69b1c5f30..1e101be66 100644 --- a/MoreLinq/Split.cs +++ b/MoreLinq/Split.cs @@ -50,7 +50,7 @@ public static IEnumerable> Split(this IEnumerable< public static IEnumerable> Split(this IEnumerable source, TSource separator, int count) { - return Split(source, separator, count, s => s); + return Split(source, separator, count, IdFn); } /// @@ -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,9 +127,9 @@ 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); + return Split(source, separator, comparer, count, IdFn); } /// @@ -175,7 +175,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) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -216,7 +216,7 @@ public static IEnumerable> Split(this IEnumerable< public static IEnumerable> Split(this IEnumerable source, Func separatorFunc, int count) { - return Split(source, separatorFunc, count, s => s); + return Split(source, separatorFunc, count, IdFn); } /// @@ -275,7 +275,7 @@ public static IEnumerable Split(this IEnumerable items = null; + List? items = null; foreach (var item in source) { @@ -287,14 +287,12 @@ public static IEnumerable Split(this IEnumerable(); - + items ??= new List(); items.Add(item); } } - if (items != null && items.Count > 0) + if (items is { Count: > 0 }) yield return resultSelector(items); } } diff --git a/MoreLinq/StartsWith.cs b/MoreLinq/StartsWith.cs index 2bedebaf0..a15c17479 100644 --- a/MoreLinq/StartsWith.cs +++ b/MoreLinq/StartsWith.cs @@ -68,13 +68,13 @@ 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)); - if (first.TryGetCollectionCount() is int firstCount && - second.TryGetCollectionCount() is int secondCount && + if (first.TryGetCollectionCount() is {} firstCount && + second.TryGetCollectionCount() is {} secondCount && secondCount > firstCount) { return false; diff --git a/MoreLinq/Subsets.cs b/MoreLinq/Subsets.cs index 1491903bd..1443f97f1 100644 --- a/MoreLinq/Subsets.cs +++ b/MoreLinq/Subsets.cs @@ -66,7 +66,7 @@ public static IEnumerable> Subsets(this IEnumerable sequence) yield return subset; } - yield return sequenceAsList; // the last subet is the original set itself + yield return sequenceAsList; // the last subset is the original set itself } } } @@ -95,9 +95,9 @@ public static IEnumerable> Subsets(this IEnumerable sequence, int if (subsetSize < 0) throw new ArgumentOutOfRangeException(nameof(subsetSize), "Subset size must be >= 0"); - // NOTE: Theres an interesting trade-off that we have to make in this operator. + // NOTE: There's an interesting trade-off that we have to make in this operator. // Ideally, we would throw an exception here if the {subsetSize} parameter is - // greater than the sequence length. Unforunately, determining the length of a + // greater than the sequence length. Unfortunately, determining the length of a // sequence is not always possible without enumerating it. Herein lies the rub. // We want Subsets() to be a deferred operation that only iterates the sequence // when the caller is ready to consume the results. However, this forces us to @@ -123,7 +123,7 @@ sealed class SubsetGenerator : IEnumerable> /// predetermined size less than or equal to the original set size. /// - class SubsetEnumerator : IEnumerator> + sealed class SubsetEnumerator : IEnumerator> { readonly IList _set; // the original set of elements readonly T[] _subset; // the current subset to return @@ -136,7 +136,7 @@ class SubsetEnumerator : IEnumerator> int _m2; // current swap index (lower index) int _k; // size of the subset being produced int _n; // size of the original set (sequence) - int _z; // count of items excluded from the subet + int _z; // count of items excluded from the subset public SubsetEnumerator(IList set, int subsetSize) { @@ -209,7 +209,7 @@ void ExtractSubset() public SubsetGenerator(IEnumerable sequence, int subsetSize) { - if (sequence == null) + if (sequence is null) throw new ArgumentNullException(nameof(sequence)); if (subsetSize < 0) throw new ArgumentOutOfRangeException(nameof(subsetSize), "{subsetSize} must be between 0 and set.Count()"); diff --git a/MoreLinq/TagFirstLast.cs b/MoreLinq/TagFirstLast.cs index e37367582..61d90f6e7 100644 --- a/MoreLinq/TagFirstLast.cs +++ b/MoreLinq/TagFirstLast.cs @@ -59,8 +59,24 @@ public static IEnumerable TagFirstLast(this IEnumerab if (source == null) throw new ArgumentNullException(nameof(source)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return source.Index() // count-up - .CountDown(1, (e, cd) => resultSelector(e.Value, e.Key == 0, cd == 0)); + return _(); IEnumerable _() + { + using var enumerator = source.GetEnumerator(); + + if (!enumerator.MoveNext()) + yield break; + + var current = enumerator.Current; + var hasNext = enumerator.MoveNext(); + yield return resultSelector(current, true, !hasNext); + + while (hasNext) + { + current = enumerator.Current; + hasNext = enumerator.MoveNext(); + yield return resultSelector(current, false, !hasNext); + } + } } } } diff --git a/MoreLinq/TakeEvery.cs b/MoreLinq/TakeEvery.cs index 32b778cab..dc0f26042 100644 --- a/MoreLinq/TakeEvery.cs +++ b/MoreLinq/TakeEvery.cs @@ -47,7 +47,7 @@ public static IEnumerable TakeEvery(this IEnumerable { if (source == null) throw new ArgumentNullException(nameof(source)); if (step <= 0) throw new ArgumentOutOfRangeException(nameof(step)); - return source.Where((e, i) => i % step == 0); + return source.Where((_, i) => i % step == 0); } } } diff --git a/MoreLinq/TakeLast.cs b/MoreLinq/TakeLast.cs index 257c4d834..dba1d1ebb 100644 --- a/MoreLinq/TakeLast.cs +++ b/MoreLinq/TakeLast.cs @@ -54,7 +54,7 @@ public static IEnumerable TakeLast(this IEnumerable s return Enumerable.Empty(); return - source.TryGetCollectionCount() is int collectionCount + source.TryGetCollectionCount() is {} collectionCount ? source.Slice(Math.Max(0, collectionCount - count), int.MaxValue) : source.CountDown(count, (e, cd) => (Element: e, Countdown: cd)) .SkipWhile(e => e.Countdown == null) diff --git a/MoreLinq/ToArrayByIndex.cs b/MoreLinq/ToArrayByIndex.cs index 4bbad61ab..5fa513ab5 100644 --- a/MoreLinq/ToArrayByIndex.cs +++ b/MoreLinq/ToArrayByIndex.cs @@ -121,21 +121,27 @@ 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) { - var i = indexSelector(e); - if (i < 0) - throw new IndexOutOfRangeException(); - lastIndex = Math.Max(i, lastIndex); - Indexed().Add(new KeyValuePair(i, e)); + switch (indexSelector(e)) + { + case < 0: +#pragma warning disable CA2201 // Do not raise reserved exception types + throw new IndexOutOfRangeException(); +#pragma warning restore CA2201 // Do not raise reserved exception types + case var i: + lastIndex = Math.Max(i, lastIndex); + Indexed().Add(new KeyValuePair(i, e)); + break; + } } var length = lastIndex + 1; return length == 0 - ? new TResult[0] + ? EmptyArray.Value : Indexed().ToArrayByIndex(length, e => e.Key, e => resultSelector(e.Value, e.Key)); } @@ -242,8 +248,14 @@ public static TResult[] ToArrayByIndex(this IEnumerable source, i foreach (var e in source) { var i = indexSelector(e); - if (i < 0 || i > array.Length) + + if (i < 0 || i >= array.Length) + { +#pragma warning disable CA2201 // Do not raise reserved exception types throw new IndexOutOfRangeException(); +#pragma warning restore CA2201 // Do not raise reserved exception types + } + array[i] = resultSelector(e, i); } return array; diff --git a/MoreLinq/ToDataTable.cs b/MoreLinq/ToDataTable.cs index fa2719739..2342ae0f1 100644 --- a/MoreLinq/ToDataTable.cs +++ b/MoreLinq/ToDataTable.cs @@ -41,7 +41,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, EmptyArray>>.Value); } /// @@ -57,7 +57,7 @@ public static TTable ToDataTable(this IEnumerable source, TTable t /// /// This operator uses immediate execution. - public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) + public static DataTable ToDataTable(this IEnumerable source, params Expression>[] expressions) { return ToDataTable(source, new DataTable(), expressions); } @@ -92,12 +92,16 @@ public static DataTable ToDataTable(this IEnumerable source) /// /// This operator uses immediate execution. - public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) + public static TTable ToDataTable(this IEnumerable source, TTable table, params Expression>[] expressions) where TTable : DataTable { 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); @@ -126,18 +130,17 @@ public static TTable ToDataTable(this IEnumerable source, TTable t return table; } - static IEnumerable PrepareMemberInfos(ICollection>> expressions) + static IEnumerable PrepareMemberInfos(ICollection>> expressions) { // // If no lambda expressions supplied then reflect them off the source element type. // - if (expressions == null || expressions.Count == 0) + if (expressions.Count == 0) { return from m in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance) where m.MemberType == MemberTypes.Field - || m is PropertyInfo p && p.CanRead - && p.GetIndexParameters().Length == 0 + || m is PropertyInfo { CanRead: true } p && p.GetIndexParameters().Length == 0 select m; } @@ -156,19 +159,18 @@ static IEnumerable PrepareMemberInfos(ICollection(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/ToDictionary.cs b/MoreLinq/ToDictionary.cs index 82a541317..47db8bf76 100644 --- a/MoreLinq/ToDictionary.cs +++ b/MoreLinq/ToDictionary.cs @@ -35,7 +35,8 @@ static partial class MoreEnumerable /// mapped to their keys. /// - public static Dictionary ToDictionary(this IEnumerable> source) => + public static Dictionary ToDictionary(this IEnumerable> source) + where TKey : notnull => source.ToDictionary(null); /// @@ -53,7 +54,8 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) + where TKey : notnull { if (source == null) throw new ArgumentNullException(nameof(source)); return source.ToDictionary(e => e.Key, e => e.Value, comparer); @@ -72,7 +74,8 @@ public static Dictionary ToDictionary(this IEnumerab /// mapped to their keys. /// - public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) => + public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source) + where TKey : notnull => source.ToDictionary(null); /// @@ -90,7 +93,8 @@ public static Dictionary ToDictionary(this IEnumerab /// public static Dictionary ToDictionary(this IEnumerable<(TKey Key, TValue Value)> source, - IEqualityComparer comparer) + IEqualityComparer? comparer) + where TKey : notnull { 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..d7917a8c6 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,14 +58,13 @@ 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)); - return TraceImpl(source, - string.IsNullOrEmpty(format) - ? (Func) (x => x == null ? string.Empty : x.ToString()) - : (x => string.Format(format, x))); + return TraceImpl(source, string.IsNullOrEmpty(format) + ? x => x?.ToString() ?? string.Empty + : x => string.Format(null, format, x)); } /// @@ -93,9 +91,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/Transpose.cs b/MoreLinq/Transpose.cs index 0234c9224..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,16 +68,17 @@ public static IEnumerable> Transpose(this IEnumerable + /// Exception thrown when the program executes an instruction that was thought to be unreachable. + /// + +#if !NETSTANDARD1_0 + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif +#pragma warning disable CA1064 // Exceptions should be public + sealed class UnreachableException : Exception +#pragma warning restore CA1064 // Exceptions should be public + { + public UnreachableException() : + this(null) { } + + public UnreachableException(string? message) : + base(message, null) { } + + public UnreachableException(string? message, Exception? innerException) : + base(message ?? "The program executed an instruction that was thought to be unreachable.", + innerException) { } + } +} + +#endif // !NET7_0_OR_GREATER diff --git a/MoreLinq/Window.cs b/MoreLinq/Window.cs index c377f90d3..011793d1f 100644 --- a/MoreLinq/Window.cs +++ b/MoreLinq/Window.cs @@ -23,11 +23,11 @@ namespace MoreLinq public static partial class MoreEnumerable { /// - /// Processes a sequence into a series of subsequences representing a windowed subset of the original + /// Processes a sequence into a series of sub-sequences representing a windowed subset of the original /// /// /// The number of sequences returned is: Max(0, sequence.Count() - windowSize) + 1
- /// Returned subsequences are buffered, but the overall operation is streamed.
+ /// Returned sub-sequences are buffered, but the overall operation is streamed.
///
/// The type of the elements of the source sequence /// The sequence to evaluate a sliding window over @@ -52,30 +52,29 @@ public static IEnumerable> Window(this IEnumerable - /// Processes a sequence into a series of subsequences representing a windowed subset of the original + /// Processes a sequence into a series of sub-sequences representing a windowed subset of the original ///
/// /// The number of sequences returned is: Max(0, sequence.Count() - windowSize) + 1
- /// Returned subsequences are buffered, but the overall operation is streamed.
+ /// Returned sub-sequences are buffered, but the overall operation is streamed.
///
/// The type of the elements of the source sequence /// The sequence to evaluate a sliding window over diff --git a/MoreLinq/WindowLeft.cs b/MoreLinq/WindowLeft.cs index 3365e768d..fdcd72803 100644 --- a/MoreLinq/WindowLeft.cs +++ b/MoreLinq/WindowLeft.cs @@ -71,13 +71,18 @@ public static IEnumerable> WindowLeft(this IEnumerable(window.Skip(1)); yield return window; - window = new List(window.Skip(1)); + window = nextWindow; } while (window.Count > 0) { + // prepare next window before exposing data + var nextWindow = new List(window.Skip(1)); yield return window; - window = new List(window.Skip(1)); + window = nextWindow; } } } diff --git a/MoreLinq/WindowRight.cs b/MoreLinq/WindowRight.cs index 9f9d76a91..4172c3d6d 100644 --- a/MoreLinq/WindowRight.cs +++ b/MoreLinq/WindowRight.cs @@ -84,8 +84,11 @@ static IEnumerable> WindowRightWhile( foreach (var item in source) { window.Add(item); + + // prepare next window before exposing data + var nextWindow = new List(predicate(item, window.Count) ? window : window.Skip(1)); yield return window; - window = new List(predicate(item, window.Count) ? window : window.Skip(1)); + window = nextWindow; } } } diff --git a/MoreLinq/ZipImpl.cs b/MoreLinq/ZipImpl.cs index 2005660cb..1f008a1dc 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/ZipLongest.cs b/MoreLinq/ZipLongest.cs index ec5a240e6..38e5fdefc 100644 --- a/MoreLinq/ZipLongest.cs +++ b/MoreLinq/ZipLongest.cs @@ -40,6 +40,10 @@ static partial class MoreEnumerable /// A sequence that contains elements of the two input sequences, /// combined by . /// + /// + /// , , or is . + /// /// /// ZipLongest( this IEnumerable first, IEnumerable second, - Func resultSelector) + Func resultSelector) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return ZipImpl(first, second, null, null, (a, b, c, d) => resultSelector(a, b), 1); + return ZipImpl(first, second, null, null, (a, b, _, _) => resultSelector(a, b), 1); } /// @@ -85,6 +89,11 @@ public static IEnumerable ZipLongest( /// A sequence that contains elements of the three input sequences, /// combined by . /// + /// + /// , , , or is . + /// /// /// ZipLongest( this IEnumerable first, IEnumerable second, IEnumerable third, - Func resultSelector) + Func resultSelector) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); if (third == null) throw new ArgumentNullException(nameof(third)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return ZipImpl(first, second, third, null, (a, b, c, d) => resultSelector(a, b, c), 2); + return ZipImpl(first, second, third, null, (a, b, c, _) => resultSelector(a, b, c), 2); } /// @@ -135,6 +144,11 @@ public static IEnumerable ZipLongest( /// A sequence that contains elements of the four input sequences, /// combined by . /// + /// + /// , , , , or is . + /// /// /// ZipLongest( IEnumerable second, IEnumerable third, IEnumerable fourth, - Func resultSelector) + Func resultSelector) { if (first == null) throw new ArgumentNullException(nameof(first)); if (second == null) throw new ArgumentNullException(nameof(second)); diff --git a/MoreLinq/ZipShortest.cs b/MoreLinq/ZipShortest.cs index 06920e2df..fbb28d046 100644 --- a/MoreLinq/ZipShortest.cs +++ b/MoreLinq/ZipShortest.cs @@ -38,6 +38,10 @@ static partial class MoreEnumerable /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. /// + /// + /// , , or is . + /// /// ZipShortest( if (second == null) throw new ArgumentNullException(nameof(second)); if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); - return ZipImpl(first, second, null, null, (a, b, c, d) => resultSelector(a, b)); + return ZipImpl(first, second, null, null, (a, b, _, _) => resultSelector(a, b)); } /// @@ -84,6 +88,11 @@ public static IEnumerable ZipShortest( /// /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. + /// + /// , , , or is . + /// /// /// ZipShortest( /// /// A projection of tuples, where each tuple contains the N-th element /// from each of the argument sequences. + /// + /// , , , , or is . + /// /// /// 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/MoreLinq/tt.cmd b/MoreLinq/tt.cmd deleted file mode 100644 index cbb7ac281..000000000 --- a/MoreLinq/tt.cmd +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -pushd "%~dp0" -for /f "tokens=*" %%f in ('dir /s /b *.tt') do ( - echo>&2 dotnet t4 "%%f" - dotnet t4 "%%f" || goto :end -) -:end -popd diff --git a/MoreLinq/tt.sh b/MoreLinq/tt.sh deleted file mode 100755 index 49be91c70..000000000 --- a/MoreLinq/tt.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -set -e -cd "$(dirname "$0")" -find . -name "*.tt" -print0 | xargs -0 -t -L 1 sh -c '(dotnet t4 "$0" || exit 255)' diff --git a/README.md b/README.md index 29661bedf..e3ba59bb4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ MoreLINQ is available for download and installation as [NuGet packages](https://www.nuget.org/packages/morelinq/). Documentation for the stable and beta releases can be found at -[morelinq.github.io](http://morelinq.github.io/). +[morelinq.github.io](https://morelinq.github.io/). ## Usage @@ -54,7 +54,7 @@ extension methods as well as all the regular static methods on [lag]: https://morelinq.github.io/2.0/ref/api/html/Overload_MoreLinq_MoreEnumerable_Lag.htm [lead]: https://morelinq.github.io/2.0/ref/api/html/Overload_MoreLinq_MoreEnumerable_Lead.htm [using-static]: https://docs.microsoft.com/en-us/dotnet/articles/csharp/whats-new/csharp-6#using-static -[netzip]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.zip--3 +[netzip]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.zip#System_Linq_Enumerable_Zip__3_System_Collections_Generic_IEnumerable___0__System_Collections_Generic_IEnumerable___1__System_Func___0___1___2__ [zip]: https://morelinq.github.io/1.x/ref/api/html/M_MoreLinq_MoreEnumerable_Zip__3.htm [unfold]: https://morelinq.github.io/2.3/ref/api/html/M_MoreLinq_MoreEnumerable_Unfold__3.htm [random]: https://morelinq.github.io/2.0/ref/api/html/Overload_MoreLinq_MoreEnumerable_Random.htm @@ -63,13 +63,8 @@ extension methods as well as all the regular static methods on ## Building -To build MoreLINQ from sources, you will need: - -- [.NET Core 2.0 with SDK 2.1][dotnet-2.0-sdk-2.1] -- [Mono][mono] 5.0 if building on other platforms than Windows - -Then run either `build.cmd` if building on Windows or `build.sh` if -building on macOS or a Linux distribution supported by .NET Core. +Run either `build.cmd` if building on Windows or `build.sh` if building on macOS +or a [Linux distribution supported by .NET][dotnet-linux]. Some code in the project is generated using [T4][t4] templates. To regenerate the code from modified templates, run `MoreLinq\tt.cmd` (Windows) or @@ -79,13 +74,11 @@ Building the documentation is supported on Windows only and requires [Sandcastle Help File Builder (SHFB)][shfb]. Executing `builddocs.cmd` generates the documentation in the `docs/api` directory. It can be browsed locally using any HTTP server of static files, like -[http-server][http-server]. - +[dotnet-serve][dotnet-serve]. -[mono]: https://www.mono-project.com/ -[dotnet-2.0-sdk-2.1]: https://github.com/dotnet/core/blob/master/release-notes/download-archives/2.1.2-sdk-download.md -[shfb]: https://github.com/EWSoftware/SHFB/releases/tag/v2017.12.30.2 -[http-server]: https://www.npmjs.com/package/http-server +[dotnet-linux]: https://learn.microsoft.com/en-us/dotnet/core/install/linux +[shfb]: https://github.com/EWSoftware/SHFB/releases/tag/v2022.12.30.0 +[dotnet-serve]: https://www.nuget.org/packages/dotnet-serve [t4]: https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates @@ -101,7 +94,7 @@ acquired till that point are disposed. Applies multiple accumulators sequentially in a single pass over a sequence. -This method has 8 overloads. +This method has 7 overloads. ### AggregateRight @@ -148,7 +141,7 @@ the third-last element and so on. Batches the source sequence into sized buckets. -This method has 2 overloads. +This method has 4 overloads, 2 of which are experimental. ### Cartesian @@ -156,7 +149,7 @@ Returns the Cartesian product of two or more sequences by combining each element from the sequences and applying a user-defined projection to the set. -This method has 8 overloads. +This method has 7 overloads. ### Choose @@ -245,6 +238,8 @@ Excludes elements from a sequence starting at a given index Returns the elements of a sequence and falls back to another if the original sequence is empty. +This method has 6 overloads. + ### FillBackward Returns a sequence with each null reference or value in the source replaced @@ -309,7 +304,7 @@ Returns a sequence of values based on indexes Groups the adjacent elements of a sequence according to a specified key selector function. -This method has 4 overloads. +This method has 6 overloads. ### ~~Incremental~~ @@ -342,8 +337,6 @@ Inserts the elements of a sequence into another sequence at a specified index. Interleaves the elements of two or more sequences into a single sequence, skipping sequences as they are consumed. -This method has 2 overloads. - ### Lag Produces a projection of a sequence by evaluating pairs of elements separated @@ -422,10 +415,14 @@ which is only returned as the predecessor of the second element Combines `OrderBy` (where element is key) and `Take` in a single operation. +This method has 4 overloads. + ### PartialSortBy Combines `OrderBy` and `Take` in a single operation. +This method has 4 overloads. + ### Partition Partitions a sequence by a predicate, or a grouping by Boolean keys or up to 3 @@ -633,7 +630,7 @@ This method has 4 overloads. Creates a delimited string from a sequence of values. The delimiter used depends on the current culture of the executing thread. -This method has 30 overloads. +This method has 15 overloads. ### ToDictionary @@ -676,22 +673,12 @@ Traces the elements of a source sequence for diagnostics. This method has 3 overloads. -### TrySingle - -Returns the only element of a sequence that has just one element. If the -sequence has zero or multiple elements, then returns a user-defined value -that indicates the cardinality of the result sequence. - -This method has 2 overloads. - ### Unfold Returns a sequence generated by applying a state to the generator function, and from its result, determines if the sequence should have a next element and its value, and the next state in the recursive call. -This method has 2 overloads. - ### Window Processes a sequence into a series of subsequences representing a windowed @@ -768,6 +755,14 @@ Creates a sequence that lazily caches the source as it is iterated for the first time, reusing the cache thereafter for future re-iterations. If the source is already cached or buffered then it is returned verbatim. +### TrySingle + +Returns the only element of a sequence that has just one element. If the +sequence has zero or multiple elements, then returns a user-defined value +that indicates the cardinality of the result sequence. + +This method has 2 overloads. + [#122]: https://github.com/morelinq/MoreLINQ/issues/122 [dict]: https://docs.microsoft.com/en-us/dotnet/api/System.Collections.Generic.Dictionary-2 diff --git a/appveyor.yml b/appveyor.yml index 4c3ea3d1c..4cff2558c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,65 +1,96 @@ version: '{build}' -image: Visual Studio 2019 +image: +- Visual Studio 2022 +- Ubuntu1604 +- macos +environment: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true +for: +- + matrix: + only: + - image: Visual Studio 2022 + environment: + IMAGE_NAME: win + deploy: + - provider: NuGet + server: https://www.myget.org/F/morelinq/api/v2/package + api_key: + secure: fhGwXyO35FSshRzs5GWmF1LJTrd1sIqmS/jNCSfO2LfOciuYAKiXuFMYZFGiTAl+ + symbol_server: https://www.myget.org/F/morelinq/symbols/api/v2/package + on: + branch: deploy + notifications: + - provider: Email + to: + - morelinq-roll@googlegroups.com + on_build_success: true + on_build_failure: true + on_build_status_changed: false +- + matrix: + only: + - image: Ubuntu1604 + environment: + IMAGE_NAME: ubuntu-16.04 +- + matrix: + only: + - image: macos + environment: + IMAGE_NAME: macos skip_commits: files: - '*.md' - '*.txt' - '.editorconfig' - lic/* -environment: - DOTNET_CLI_TELEMETRY_OPTOUT: true - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true install: -- ps: Install-Product node 6 - npm install -g eclint - git rm .editorconfig - 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) +- ps: if ($isWindows) { tools\dotnet-install.ps1 -JSonFile global.json } +- ps: if ($isWindows) { tools\dotnet-install.ps1 -Runtime dotnet -Version 3.1.10 -SkipNonVersionedFiles } +- ps: if ($isWindows) { tools\dotnet-install.ps1 -Runtime dotnet -Version 6.0.11 -SkipNonVersionedFiles } +- sh: ./tools/dotnet-install.sh --jsonfile global.json +- sh: ./tools/dotnet-install.sh --runtime dotnet --version 3.1.10 --skip-non-versioned-files +- sh: ./tools/dotnet-install.sh --runtime dotnet --version 6.0.11 --skip-non-versioned-files +- sh: export PATH="~/.dotnet:$PATH" before_build: - dotnet --info build_script: -- ps: >- - grep --extended-regexp '^[[:space:]]*using[[:space:]]+System\.Linq;' (dir MoreLinq.Test\*Test.cs) - +- pwsh: | + grep --extended-regexp '^[[:space:]]*using[[:space:]]+System\.Linq;' (dir -Recurse -File -Filter *Test.cs MoreLinq.Test) if ($LASTEXITCODE -eq 0) { throw 'Unit tests should not import System.Linq' + } else { + $LASTEXITCODE = 0 } - +- ps: | $id = $env:APPVEYOR_REPO_COMMIT_TIMESTAMP -replace '([-:]|\.0+Z)', '' - $id = $id.Substring(0, 13) - - cmd /c call pack.cmd ci-$id - + if ($isWindows) { cmd /c call pack.cmd ci-$id } else { ./pack.sh ci-$id } if ($LASTEXITCODE -ne 0) { throw "Building/Packing failed with an exit code of $LASTEXITCODE." } - $diff = git diff --ignore-all-space --exit-code 2>&1 - $diff | % { if ($_ -is [string]) { $_ } else { [string]$_ } } | echo - if ($LASTEXITCODE -ne 0) { throw "New code was generated during build that's not been committed." } test_script: - cmd: test.cmd +- sh: ./test.sh +- ps: dotnet reportgenerator -reports:MoreLinq.Test/TestResults/coverage-*.opencover.xml -targetdir:tmp/cover -tag:(git show -q --pretty=%H) +- ps: | + cd tmp/cover + tar -cz -f "../../coverage-report-${env:IMAGE_NAME}.tar.gz" * +- sh: curl -sSL https://codecov.io/bash > codecov +- sh: chmod +x codecov +- sh: if [ "$CI_LINUX" = "true" ]; then ./codecov; fi artifacts: - path: dist\*.nupkg -deploy: -- provider: NuGet - server: https://www.myget.org/F/morelinq/api/v2/package - api_key: - secure: fhGwXyO35FSshRzs5GWmF1LJTrd1sIqmS/jNCSfO2LfOciuYAKiXuFMYZFGiTAl+ - symbol_server: https://www.myget.org/F/morelinq/symbols/api/v2/package - on: - branch: master -notifications: -- provider: Email - to: - - morelinq-roll@googlegroups.com - on_build_success: true - on_build_failure: true - on_build_status_changed: false +- path: coverage-report-* diff --git a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj index b43ac7fd7..faf971cfd 100644 --- a/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj +++ b/bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj @@ -1,12 +1,11 @@  Exe - netcoreapp3.0 - 8 + net7.0 false - - + + diff --git a/bld/ExtensionsGenerator/Program.cs b/bld/ExtensionsGenerator/Program.cs index 6c7820e9b..03b85ab08 100644 --- a/bld/ExtensionsGenerator/Program.cs +++ b/bld/ExtensionsGenerator/Program.cs @@ -14,267 +14,283 @@ // #endregion -namespace MoreLinq.ExtensionsGenerator +#pragma warning disable CA2201 // Do not raise reserved exception types + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +// Disable CA1852 due to the following false negative: +// Type 'Program' can be sealed because it has no subtypes in its containing assembly and is not externally visible +#pragma warning disable CA1852 // Seal internal types +try +#pragma warning restore CA1852 // Seal internal types { - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Text.RegularExpressions; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - - static class Program - { - static void Run(IEnumerable args) - { - var dir = Directory.GetCurrentDirectory(); + Run(args); + return 0; +} +#pragma warning disable CA1031 // Do not catch general exception types +catch (Exception e) +#pragma warning restore CA1031 // Do not catch general exception types +{ + Console.Error.WriteLine(e.ToString()); + return 0xbad; +} + +static void Run(IEnumerable args) +{ + var dir = Directory.GetCurrentDirectory(); - string includePattern = null; - string excludePattern = null; - var debug = false; - var usings = new List(); - var noClassLead = false; + string? includePattern = null; + string? excludePattern = null; + var debug = false; + var usings = new List(); + var noClassLead = false; - using (var arg = args.GetEnumerator()) + using (var arg = args.GetEnumerator()) + { + while (arg.MoveNext()) + { + switch (arg.Current) { - while (arg.MoveNext()) - { - switch (arg.Current) - { - case "-i": - case "--include": - includePattern = Read(arg, MissingArgValue); - break; - case "-x": - case "--exclude": - excludePattern = Read(arg, MissingArgValue); - break; - case "-u": - case "--using": - usings.Add(Read(arg, MissingArgValue)); - break; - case "--no-class-lead": - noClassLead = true; - break; - case "-d": - case "--debug": - debug = true; - break; - case "": - continue; - default: - dir = arg.Current[0] != '-' - ? arg.Current - : throw new Exception("Invalid argument: " + arg.Current); - break; - } - } - - static Exception MissingArgValue() => - new InvalidOperationException("Missing argument value."); + case "-i": + case "--include": + includePattern = Read(arg, MissingArgValue); + break; + case "-x": + case "--exclude": + excludePattern = Read(arg, MissingArgValue); + break; + case "-u": + case "--using": + usings.Add(Read(arg, MissingArgValue)); + break; + case "--no-class-lead": + noClassLead = true; + break; + case "-d": + case "--debug": + debug = true; + break; + case "": + continue; + default: + dir = arg.Current[0] != '-' + ? arg.Current + : throw new Exception("Invalid argument: " + arg.Current); + break; } + } - static Func - PredicateFromPattern(string pattern, bool @default) => - string.IsNullOrEmpty(pattern) - ? delegate { return @default; } - : new Func(new Regex(pattern).IsMatch); + static Exception MissingArgValue() => + new InvalidOperationException("Missing argument value."); + } - var includePredicate = PredicateFromPattern(includePattern, true); - var excludePredicate = PredicateFromPattern(excludePattern, false); + static Func + PredicateFromPattern(string? pattern, bool @default) => + string.IsNullOrEmpty(pattern) + ? delegate { return @default; } + : new Func(new Regex(pattern).IsMatch); - var thisAssemblyName = typeof(Program).GetTypeInfo().Assembly.GetName(); + var includePredicate = PredicateFromPattern(includePattern, true); + var excludePredicate = PredicateFromPattern(excludePattern, false); - // - // Type abbreviations are used to abbreviate all generic type - // parameters into a letter from the alphabet. So for example, a - // method with generic type parameters `TSource` and `TResult` will - // become `a` and `b`. This is used later for sorting and stabilizes - // the sort irrespective of how the type parameters are named or - // renamed in the source. - // + var thisAssemblyName = typeof(TypeKey).GetTypeInfo().Assembly.GetName(); - var abbreviatedTypeNodes = Enumerable - .Range(0, 26) - .Select(a => (char) ('a' + a)) - .Select(ch => new SimpleTypeKey(ch.ToString())) - .ToArray(); + // + // Type abbreviations are used to abbreviate all generic type + // parameters into a letter from the alphabet. So for example, a + // method with generic type parameters `TSource` and `TResult` will + // become `a` and `b`. This is used later for sorting and stabilizes + // the sort irrespective of how the type parameters are named or + // renamed in the source. + // - var q = + var abbreviatedTypeNodes = Enumerable + .Range(0, 26) + .Select(a => (char) ('a' + a)) + .Select(ch => new SimpleTypeKey(ch.ToString())) + .ToArray(); - from ms in new[] - { - from fp in Directory.EnumerateFiles(dir, "*.cs") - where !excludePredicate(fp) && includePredicate(fp) - orderby fp - // - // Find all class declarations where class name is - // `MoreEnumerable`. Note that this is irrespective of - // namespace, which is out of sheer laziness. - // - from cd in - CSharpSyntaxTree - .ParseText(File.ReadAllText(fp), CSharpParseOptions.Default.WithPreprocessorSymbols("MORELINQ")) - .GetRoot() - .SyntaxTree - .GetCompilationUnitRoot() - .DescendantNodes().OfType() - where (string) cd.Identifier.Value == "MoreEnumerable" - // - // Get all method declarations where method: - // - // - has at least one parameter - // - extends a type (first parameter uses the `this` modifier) - // - is public - // - isn't marked as being obsolete - // - from md in cd.DescendantNodes().OfType() - let mn = (string) md.Identifier.Value - where md.ParameterList.Parameters.Count > 0 - && md.ParameterList.Parameters.First().Modifiers.Any(m => (string)m.Value == "this") - && md.Modifiers.Any(m => (string)m.Value == "public") - && md.AttributeLists.SelectMany(al => al.Attributes).All(a => a.Name.ToString() != "Obsolete") - // - // Build a dictionary of type abbreviations (e.g. TSource -> a, - // TResult -> b, etc.) for the method's type parameters. If the - // method is non-generic, then this will be null! - // - let typeParameterAbbreviationByName = - md.TypeParameterList - ?.Parameters - .Select((e, i) => (Original: e.Identifier.ValueText, Alias: abbreviatedTypeNodes[i])) - .ToDictionary(e => e.Original, e => e.Alias) - // - // Put everything together. While we mostly care about the - // method declaration, the rest of the information is captured - // for the purpose of stabilizing the code generation order and - // debugging (--debug). - // - select new - { - Syntax = md, - Name = md.Identifier.ToString(), - TypeParameterCount = md.TypeParameterList?.Parameters.Count ?? 0, - TypeParameterAbbreviationByName = typeParameterAbbreviationByName, - ParameterCount = md.ParameterList.Parameters.Count, - SortableParameterTypes = - from p in md.ParameterList.Parameters - select CreateTypeKey(p.Type, - n => typeParameterAbbreviationByName != null - && typeParameterAbbreviationByName.TryGetValue(n, out var a) ? a : null), - } - } - from e in ms.Select((m, i) => (SourceOrder: i + 1, Method: m)) - orderby - e.Method.Name, - e.Method.TypeParameterCount, - e.Method.ParameterCount, - new TupleTypeKey(ImmutableList.CreateRange(e.Method.SortableParameterTypes)) + var q = + + from ms in new[] + { + from fp in Directory.EnumerateFiles(dir, "*.cs") + where !excludePredicate(fp) && includePredicate(fp) + orderby fp + // + // Find all class declarations where class name is + // `MoreEnumerable`. Note that this is irrespective of + // namespace, which is out of sheer laziness. + // + from cd in + CSharpSyntaxTree + .ParseText(File.ReadAllText(fp), CSharpParseOptions.Default.WithPreprocessorSymbols("MORELINQ")) + .GetRoot() + .SyntaxTree + .GetCompilationUnitRoot() + .DescendantNodes().OfType() + where cd.Identifier.Value is "MoreEnumerable" + // + // Get all method declarations where method: + // + // - has at least one parameter + // - extends a type (first parameter uses the `this` modifier) + // - is public + // - isn't marked as being obsolete + // + from md in cd.DescendantNodes().OfType() + where md.ParameterList.Parameters.Count > 0 + && md.ParameterList.Parameters.First().Modifiers.Any(m => m.Value is "this") + && md.Modifiers.Any(m => m.Value is "public") + && md.AttributeLists.SelectMany(al => al.Attributes).All(a => a.Name.ToString() != "Obsolete") + // + // Build a dictionary of type abbreviations (e.g. TSource -> a, + // TResult -> b, etc.) for the method's type parameters. If the + // method is non-generic, then this will be null! + // + let typeParameterAbbreviationByName = + md.TypeParameterList + ?.Parameters + .Select((e, i) => (Original: e.Identifier.ValueText, Alias: abbreviatedTypeNodes[i])) + .ToDictionary(e => e.Original, e => e.Alias) + // + // Put everything together. While we mostly care about the + // method declaration, the rest of the information is captured + // for the purpose of stabilizing the code generation order and + // debugging (--debug). + // select new { - e.Method, - e.SourceOrder, - }; + Syntax = md, + Name = md.Identifier.ToString(), + TypeParameterCount = md.TypeParameterList?.Parameters.Count ?? 0, + TypeParameterAbbreviationByName = typeParameterAbbreviationByName, + ParameterCount = md.ParameterList.Parameters.Count, + SortableParameterTypes = + from p in md.ParameterList.Parameters + select CreateTypeKey(p.Type ?? throw new NullReferenceException(), + n => typeParameterAbbreviationByName is { } someTypeParameterAbbreviationByName + && someTypeParameterAbbreviationByName.TryGetValue(n, out var a) ? a : null), + } + } + from e in ms.Select((m, i) => (SourceOrder: i + 1, Method: m)) + orderby + e.Method.Name, + e.Method.TypeParameterCount, + e.Method.ParameterCount, + new TupleTypeKey(ImmutableList.CreateRange(e.Method.SortableParameterTypes)) + select new + { + e.Method, + e.SourceOrder, + }; - q = q.ToArray(); + q = q.ToArray(); - if (debug) + if (debug) + { + var ms = + // + // Example of what this is designed to produce: + // + // 083: Lag(IEnumerable, int, Func) where a = TSource, b = TResult + // 084: Lag(IEnumerable, int, a, Func) where a = TSource, b = TResult + // 085: Lead(IEnumerable, int, Func) where a = TSource, b = TResult + // 086: Lead(IEnumerable, int, a, Func) where a = TSource, b = TResult + // + from e in q + let m = e.Method + select new { - var ms = - // - // Example of what this is designed to produce: - // - // 083: Lag(IEnumerable, int, Func) where a = TSource, b = TResult - // 084: Lag(IEnumerable, int, a, Func) where a = TSource, b = TResult - // 085: Lead(IEnumerable, int, Func) where a = TSource, b = TResult - // 086: Lead(IEnumerable, int, a, Func) where a = TSource, b = TResult - // - from e in q - let m = e.Method - select new - { - m.Name, - - SourceOrder = e.SourceOrder.ToString("000", CultureInfo.InvariantCulture), - - TypeParameters = - m.TypeParameterCount == 0 - ? string.Empty - : "<" + string.Join(", ", from a in m.TypeParameterAbbreviationByName - select a.Value) + ">", - Parameters = - "(" + string.Join(", ", m.SortableParameterTypes) + ")", - - Abbreviations = - m.TypeParameterCount == 0 - ? string.Empty - : " where " + string.Join(", ", from a in m.TypeParameterAbbreviationByName - select a.Value + " = " + a.Key), - } - into e - select e.SourceOrder + ": " - + e.Name + e.TypeParameters + e.Parameters + e.Abbreviations; - - foreach (var m in ms) - Console.Error.WriteLine(m); + m.Name, + + SourceOrder = e.SourceOrder.ToString("000", CultureInfo.InvariantCulture), + + TypeParameters = + m.TypeParameterCount == 0 + ? string.Empty + : "<" + string.Join(", ", from a in m.TypeParameterAbbreviationByName + select a.Value) + ">", + Parameters = + "(" + string.Join(", ", m.SortableParameterTypes) + ")", + + Abbreviations = + m.TypeParameterCount == 0 + ? string.Empty + : " where " + string.Join(", ", from a in m.TypeParameterAbbreviationByName + select a.Value + " = " + a.Key), } + into e + select e.SourceOrder + ": " + + e.Name + e.TypeParameters + e.Parameters + e.Abbreviations; - var indent = new string(' ', 4); - var indent2 = indent + indent; - var indent3 = indent2 + indent; + foreach (var m in ms) + Console.Error.WriteLine(m); + } - var baseImports = new [] - { - "System", - "System.CodeDom.Compiler", - "System.Collections.Generic", - }; - - var imports = - from ns in baseImports.Concat(usings) - select indent + $"using {ns};"; - - var classes = - from md in q - select md.Method.Syntax into md - group md by (string) md.Identifier.Value into g - select new - { - Name = g.Key, - Overloads = - from md in g - select - MethodDeclaration(md.ReturnType, md.Identifier) - .WithAttributeLists(md.AttributeLists) - .WithModifiers(md.Modifiers) - .WithTypeParameterList(md.TypeParameterList) - .WithConstraintClauses(md.ConstraintClauses) - .WithParameterList(md.ParameterList) - .WithExpressionBody( - ArrowExpressionClause( - InvocationExpression( - MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - IdentifierName("MoreEnumerable"), - IdentifierName(md.Identifier)), - ArgumentList( - SeparatedList( - from p in md.ParameterList.Parameters - select Argument(IdentifierName(p.Identifier)), - Enumerable.Repeat(ParseToken(",").WithTrailingTrivia(Space), - md.ParameterList.Parameters.Count - 1)))) - .WithLeadingTrivia(Space)) - .WithLeadingTrivia(Whitespace(indent3))) - .WithSemicolonToken(ParseToken(";").WithTrailingTrivia(LineFeed)) - } - into m - select (!noClassLead ? $@" + var indent = new string(' ', 4); + var indent2 = indent + indent; + var indent3 = indent2 + indent; + + var baseImports = new [] + { + "System", + "System.CodeDom.Compiler", + "System.Collections.Generic", + "System.Diagnostics.CodeAnalysis", + }; + + var imports = + from ns in baseImports.Concat(usings) + select indent + $"using {ns};"; + + var classes = + from md in q + select md.Method.Syntax into md + group md by md.Identifier.Value is string id ? id : throw new NullReferenceException() + into g + select new + { + Name = g.Key, + Overloads = + from md in g + select + MethodDeclaration(md.ReturnType, md.Identifier) + .WithAttributeLists(md.AttributeLists) + .WithModifiers(md.Modifiers) + .WithTypeParameterList(md.TypeParameterList) + .WithConstraintClauses(md.ConstraintClauses) + .WithParameterList(md.ParameterList) + .WithExpressionBody( + ArrowExpressionClause( + InvocationExpression( + MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + IdentifierName("MoreEnumerable"), + IdentifierName(md.Identifier)), + ArgumentList( + SeparatedList( + from p in md.ParameterList.Parameters + select Argument(IdentifierName(p.Identifier)), + Enumerable.Repeat(ParseToken(",").WithTrailingTrivia(Space), + md.ParameterList.Parameters.Count - 1)))) + .WithLeadingTrivia(Space)) + .WithLeadingTrivia(Whitespace(indent3))) + .WithSemicolonToken(ParseToken(";").WithTrailingTrivia(LineFeed)) + } + into m + select (!noClassLead ? $@" /// {m.Name} extension. [GeneratedCode(""{thisAssemblyName.Name}"", ""{thisAssemblyName.Version}"")]" : null) + $@" @@ -283,7 +299,7 @@ public static partial class {m.Name}Extension {string.Join(null, from mo in m.Overloads select mo.ToFullString())} }}"; - var template = $@" + var template = $@" #region License and Terms // MoreLINQ - Extensions to LINQ to Objects // @@ -303,163 +319,152 @@ 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 // 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 {{ {string.Join("\n", imports)} {string.Join("\n", classes)} }} - "; - - Console.WriteLine(template.Trim() - // normalize line endings - .Replace("\r", string.Empty) - .Replace("\n", Environment.NewLine)); - } + "; - public static TypeKey CreateTypeKey(TypeSyntax root, - Func abbreviator = null) - { - return Walk(root ?? throw new ArgumentNullException(nameof(root))); + Console.WriteLine(template.Trim() + // normalize line endings + .Replace("\r", string.Empty, StringComparison.Ordinal) + .Replace("\n", Environment.NewLine, StringComparison.Ordinal)); +} - TypeKey Walk(TypeSyntax ts) => ts switch - { - PredefinedTypeSyntax pts => new SimpleTypeKey(pts.ToString()), - NullableTypeSyntax nts => new NullableTypeKey(Walk(nts.ElementType)), - IdentifierNameSyntax ins => abbreviator?.Invoke(ins.Identifier.ValueText) - ?? new SimpleTypeKey(ins.ToString()), - GenericNameSyntax gns => - new GenericTypeKey(gns.Identifier.ToString(), - ImmutableList.CreateRange(gns.TypeArgumentList.Arguments.Select(Walk))), - ArrayTypeSyntax ats => - new ArrayTypeKey(Walk(ats.ElementType), - ImmutableList.CreateRange(from rs in ats.RankSpecifiers - select rs.Rank)), - TupleTypeSyntax tts => - new TupleTypeKey(ImmutableList.CreateRange(from te in tts.Elements - select Walk(te.Type))), - _ => throw new NotSupportedException("Unhandled type: " + ts) - }; - } +static TypeKey CreateTypeKey(TypeSyntax root, Func abbreviator) +{ + return Walk(root ?? throw new ArgumentNullException(nameof(root))); - static T Read(IEnumerator e, Func errorFactory = null) - { - if (!e.MoveNext()) - throw errorFactory?.Invoke() ?? new InvalidOperationException(); - return e.Current; - } + TypeKey Walk(TypeSyntax ts) => ts switch + { + PredefinedTypeSyntax pts => new SimpleTypeKey(pts.ToString()), + NullableTypeSyntax nts => new NullableTypeKey(Walk(nts.ElementType)), + IdentifierNameSyntax ins => abbreviator?.Invoke(ins.Identifier.ValueText) + ?? new SimpleTypeKey(ins.ToString()), + GenericNameSyntax gns => + new GenericTypeKey(gns.Identifier.ToString(), + ImmutableList.CreateRange(gns.TypeArgumentList.Arguments.Select(Walk))), + ArrayTypeSyntax ats => + new ArrayTypeKey(Walk(ats.ElementType), + ImmutableList.CreateRange(from rs in ats.RankSpecifiers + select rs.Rank)), + TupleTypeSyntax tts => + new TupleTypeKey(ImmutableList.CreateRange(from te in tts.Elements + select Walk(te.Type))), + _ => throw new NotSupportedException("Unhandled type: " + ts) + }; +} - static int Main(string[] args) - { - try - { - Run(args); - return 0; - } - catch (Exception e) - { - Console.Error.WriteLine(e.ToString()); - return 0xbad; - } - } - } +static T Read(IEnumerator e, Func errorFactory) => + e.MoveNext() ? e.Current : throw errorFactory(); - // - // Logical type nodes designed to be structurally sortable based on: - // - // - Type parameter count - // - Name - // - Array rank, if an array - // - Each type parameter (recursively) - // +// +// Logical type nodes designed to be structurally sortable based on: +// +// - Type parameter count +// - Name +// - Array rank, if an array +// - Each type parameter (recursively) +// - abstract class TypeKey : IComparable - { - protected TypeKey(string name) => Name = name; +abstract class TypeKey : IComparable +{ + protected TypeKey(string name) => Name = name; - public string Name { get; } - public abstract ImmutableList Parameters { get; } + public string Name { get; } + public abstract ImmutableList Parameters { get; } - public virtual int CompareTo(TypeKey other) - => ReferenceEquals(this, other) ? 0 - : other == null ? 1 - : Parameters.Count.CompareTo(other.Parameters.Count) is int lc && lc != 0 ? lc - : string.Compare(Name, other.Name, StringComparison.Ordinal) is int nc && nc != 0 ? nc - : CompareParameters(other); + public virtual int CompareTo(TypeKey? other) + => ReferenceEquals(this, other) ? 0 + : other == null ? 1 + : Parameters.Count.CompareTo(other.Parameters.Count) is var lc and not 0 ? lc + : string.Compare(Name, other.Name, StringComparison.Ordinal) is var nc and not 0 ? nc + : CompareParameters(other); - protected virtual int CompareParameters(TypeKey other) => - Compare(Parameters, other.Parameters); + protected virtual int CompareParameters(TypeKey other) => + Compare(Parameters, other.Parameters); - protected static int Compare(IEnumerable a, IEnumerable b) => - a.Zip(b, (us, them) => (Us: us, Them: them)) - .Select(e => e.Us.CompareTo(e.Them)) - .FirstOrDefault(e => e != 0); - } + protected static int Compare(IEnumerable a, IEnumerable b) => + a.Zip(b, (us, them) => (Us: us, Them: them)) + .Select(e => e.Us.CompareTo(e.Them)) + .FirstOrDefault(e => e != 0); +} - sealed class SimpleTypeKey : TypeKey - { - public SimpleTypeKey(string name) : base(name) {} - public override string ToString() => Name; - public override ImmutableList Parameters => ImmutableList.Empty; - } +sealed class SimpleTypeKey : TypeKey +{ + public SimpleTypeKey(string name) : base(name) {} + public override string ToString() => Name; + public override ImmutableList Parameters => ImmutableList.Empty; +} - abstract class ParameterizedTypeKey : TypeKey - { - protected ParameterizedTypeKey(string name, TypeKey parameter) : - this(name, ImmutableList.Create(parameter)) {} +abstract class ParameterizedTypeKey : TypeKey +{ + protected ParameterizedTypeKey(string name, TypeKey parameter) : + this(name, ImmutableList.Create(parameter)) {} - protected ParameterizedTypeKey(string name, ImmutableList parameters) : - base(name) => Parameters = parameters; + protected ParameterizedTypeKey(string name, ImmutableList parameters) : + base(name) => Parameters = parameters; - public override ImmutableList Parameters { get; } - } + public override ImmutableList Parameters { get; } +} - sealed class GenericTypeKey : ParameterizedTypeKey - { - public GenericTypeKey(string name, ImmutableList parameters) : - base(name, parameters) {} +sealed class GenericTypeKey : ParameterizedTypeKey +{ + public GenericTypeKey(string name, ImmutableList parameters) : + base(name, parameters) {} - public override string ToString() => - Name + "<" + string.Join(", ", Parameters) + ">"; - } + public override string ToString() => + Name + "<" + string.Join(", ", Parameters) + ">"; +} - sealed class NullableTypeKey : ParameterizedTypeKey - { - public NullableTypeKey(TypeKey underlying) : base("?", underlying) {} - public override string ToString() => Parameters.Single() + "?"; - } +sealed class NullableTypeKey : ParameterizedTypeKey +{ + public NullableTypeKey(TypeKey underlying) : base("?", underlying) {} + public override string ToString() => Parameters.Single() + "?"; +} - sealed class TupleTypeKey : ParameterizedTypeKey - { - public TupleTypeKey(ImmutableList parameters) : - base("()", parameters) {} +sealed class TupleTypeKey : ParameterizedTypeKey +{ + public TupleTypeKey(ImmutableList parameters) : + base("()", parameters) {} - public override string ToString() => - "(" + string.Join(", ", Parameters) + ")"; - } + public override string ToString() => + "(" + string.Join(", ", Parameters) + ")"; +} - sealed class ArrayTypeKey : ParameterizedTypeKey - { - public ArrayTypeKey(TypeKey element, IEnumerable ranks) : - base("[]", element) => Ranks = ImmutableList.CreateRange(ranks); +sealed class ArrayTypeKey : ParameterizedTypeKey +{ + public ArrayTypeKey(TypeKey element, IEnumerable ranks) : + base("[]", element) => Ranks = ImmutableList.CreateRange(ranks); - public ImmutableList Ranks { get; } + public ImmutableList Ranks { get; } - public override string ToString() => - Parameters.Single() + string.Concat(from r in Ranks - select "[" + string.Concat(Enumerable.Repeat(",", r - 1)) + "]"); + public override string ToString() => + Parameters.Single() + string.Concat(from r in Ranks + select "[" + string.Concat(Enumerable.Repeat(",", r - 1)) + "]"); - protected override int CompareParameters(TypeKey other) + protected override int CompareParameters(TypeKey other) + { + if (other is ArrayTypeKey a) { - if (other is ArrayTypeKey a) - { - if (Ranks.Count.CompareTo(a.Ranks.Count) is int rlc && rlc != 0) - return rlc; - if (Ranks.Zip(a.Ranks, (us, them) => (Us: us, Them: them)) - .Aggregate(0, (c, r) => c == 0 ? r.Us.CompareTo(r.Them) : c) is int rc && rc != 0) - return rc; - } - - return base.CompareParameters(other); + if (Ranks.Count.CompareTo(a.Ranks.Count) is var rlc and not 0) + return rlc; + if (Ranks.Zip(a.Ranks, (us, them) => (Us: us, Them: them)) + .Aggregate(0, (c, r) => c == 0 ? r.Us.CompareTo(r.Them) : c) is var rc and not 0) + return rc; } + + return base.CompareParameters(other); } } diff --git a/build.cmd b/build.cmd index cedd31964..18923a015 100644 --- a/build.cmd +++ b/build.cmd @@ -13,7 +13,6 @@ if "%1"=="docs" shift & goto :docs dotnet restore && dotnet tool 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 ^ - && call MoreLinq\tt ^ && for %%i in (debug release) do dotnet build -c %%i --no-restore %* || exit /b 1 goto :EOF @@ -28,7 +27,7 @@ exit /b 2 :codegen echo | set /p=Generating extensions wrappers (%1)... -dotnet run -p bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj -c Release -- %2 %3 %4 %5 %6 %7 %8 %9 > "%temp%\%~nx1" ^ +dotnet run --project bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj -c Release -- %2 %3 %4 %5 %6 %7 %8 %9 > "%temp%\%~nx1" ^ && move "%temp%\%~nx1" "%~dp1" > nul ^ && echo Done. goto :EOF diff --git a/build.sh b/build.sh index 226dd4559..7d188c377 100755 --- a/build.sh +++ b/build.sh @@ -7,12 +7,11 @@ codegen() { dest="$1" printf "Generating extensions wrappers (%s)..." "$1" shift - dotnet run -p bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj -c Release -- "$@" > "$dest" + dotnet run --project bld/ExtensionsGenerator/MoreLinq.ExtensionsGenerator.csproj -c Release -- "$@" > "$dest" printf "Done.\n" } codegen MoreLinq/Extensions.g.cs -x "[/\\\\]ToDataTable\.cs$" -u System.Linq -u System.Collections MoreLinq codegen MoreLinq/Extensions.ToDataTable.g.cs -i "[/\\\\]ToDataTable\.cs$" -u System.Data -u System.Linq.Expressions MoreLinq -MoreLinq/tt.sh if [[ -z "$1" ]]; then configs="Debug Release" else diff --git a/global.json b/global.json index 2223a05e3..77c776f82 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "3.0.100" + "version": "7.0.100", + "rollForward": "latestFeature" } -} \ No newline at end of file +} diff --git a/msbuild.cmd b/msbuild.cmd index f24ead479..406eea283 100644 --- a/msbuild.cmd +++ b/msbuild.cmd @@ -1,10 +1,8 @@ @echo off 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\2019\%%e\MSBuild\Current\Bin\MSBuild.exe" ( - set "MSBUILD=%PROGRAMS%\Microsoft Visual Studio\2019\%%e\MSBuild\Current\Bin\MSBuild.exe" + if exist "%ProgramFiles%\Microsoft Visual Studio\2022\%%e\MSBuild\Current\Bin\MSBuild.exe" ( + set "MSBUILD=%ProgramFiles%\Microsoft Visual Studio\2022\%%e\MSBuild\Current\Bin\MSBuild.exe" ) ) if exist "%MSBUILD%" goto :build @@ -17,13 +15,13 @@ for /f "delims=. tokens=1,2,3,4" %%m in ('msbuild /version /nologo') do ( set MSBUILD_VERSION_MAJOR=%%m ) if not defined MSBUILD_VERSION_MAJOR goto :nomsbuild -if %MSBUILD_VERSION_MAJOR% lss 16 goto :nomsbuild +if %MSBUILD_VERSION_MAJOR% lss 17 goto :nomsbuild :build "%MSBUILD%" %* goto :EOF :nomsbuild -echo>&2 Microsoft Build Engine 16.0 or a later version is required to build +echo>&2 Microsoft Build Engine 17.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 diff --git a/test.cmd b/test.cmd index 2fafd3c21..7f87f56c0 100644 --- a/test.cmd +++ b/test.cmd @@ -7,20 +7,49 @@ goto :EOF :main setlocal call build ^ - && call :test netcoreapp2.1 Debug ^ - && call :test netcoreapp2.1 Release ^ - && call :test netcoreapp3.0 Debug ^ - && call :test netcoreapp3.0 Release ^ - && call :test net451 Debug ^ - && call :test net451 Release + && call :clean ^ + && call :test net7.0 Debug ^ + && call :test net7.0 Release ^ + && call :test net6.0 Debug ^ + && call :test net6.0 Release ^ + && call :test netcoreapp3.1 Debug ^ + && call :test netcoreapp3.1 Release ^ + && call :test net462 Debug ^ + && call :test net462 Release ^ + && call :report-cover +goto :EOF + +:clean +setlocal +cd MoreLinq.Test +if exist TestResults rd /s /q TestResults || exit /b 1 +if exist TestResult.xml del TestResult.xml || exit /b 1 goto :EOF :test setlocal +cd MoreLinq.Test echo Testing %1 (%2)... -if %1==net451 ( - MoreLinq.Test\bin\%2\net451\MoreLinq.Test.exe -) else ( - dotnet exec MoreLinq.Test\bin\%2\%1\MoreLinq.Test.dll +if %1==net462 ( + bin\%2\net462\MoreLinq.Test.exe + exit /b %ERRORLEVEL% +) +dotnet test --no-build -f %1 -c %2 --settings coverlet.runsettings || exit /b 1 +cd TestResults +set TEST_RESULTS_DIR= +for /f %%d in ('dir /b /od /ad') do if not defined TEST_RESULTS_DIR set TEST_RESULTS_DIR=%%~d +if not defined TEST_RESULTS_DIR ( + echo>&2 Test coverage XML not found! + exit /b 1 ) +copy "%TEST_RESULTS_DIR%\coverage.opencover.xml" coverage-%1-%2.opencover.xml > nul +goto :EOF + +:report-cover +setlocal +cd MoreLinq.Test\TestResults +dotnet reportgenerator -reports:coverage-*.opencover.xml ^ + -reporttypes:Html;TextSummary ^ + -targetdir:reports ^ + && type reports\Summary.txt goto :EOF diff --git a/test.sh b/test.sh index 4729e73d4..c228eabe2 100755 --- a/test.sh +++ b/test.sh @@ -1,16 +1,31 @@ #!/usr/bin/env bash set -e cd "$(dirname "$0")" -./build.sh -for v in 2.1 3.0; do - for c in Debug Release; do - dotnet exec MoreLinq.Test/bin/$c/netcoreapp$v/MoreLinq.Test.dll +./build.sh $c +if [[ -d "MoreLinq.Test/TestResults" ]]; then + rm -rf MoreLinq.Test/TestResults +fi +if [[ -z "$1" ]]; then + configs="Debug Release" +else + configs="$1" +fi +for f in netcoreapp3.1 net6.0 net7.0; do + for c in $configs; do + dotnet test --no-build -c $c -f $f --settings MoreLinq.Test/coverlet.runsettings MoreLinq.Test + TEST_RESULTS_DIR="$(ls -dc MoreLinq.Test/TestResults/* | head -1)" + cp "$TEST_RESULTS_DIR/coverage.opencover.xml" "MoreLinq.Test/TestResults/coverage-$f-$c.opencover.xml" done done +dotnet reportgenerator -reports:MoreLinq.Test/TestResults/coverage-*.opencover.xml \ + -reporttypes:Html\;TextSummary \ + -targetdir:MoreLinq.Test/TestResults/reports +cat MoreLinq.Test/TestResults/reports/Summary.txt if [[ -z `which mono 2>/dev/null` ]]; then echo>&2 NOTE! Mono does not appear to be installed so unit tests echo>&2 against the Mono runtime will be skipped. else - mono MoreLinq.Test/bin/Debug/net451/MoreLinq.Test.exe - mono MoreLinq.Test/bin/Release/net451/MoreLinq.Test.exe + for c in $configs; do + mono MoreLinq.Test/bin/$c/net462/MoreLinq.Test.exe + done fi diff --git a/tools/dotnet-install.ps1 b/tools/dotnet-install.ps1 index 8c59e162a..99798362c 100644 --- a/tools/dotnet-install.ps1 +++ b/tools/dotnet-install.ps1 @@ -15,9 +15,10 @@ - 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 + examples: 2.0, 1.0 - Branch name - examples: release/2.0.0; Master + examples: release/2.0.0, Master + Note: The version parameter overrides the channel parameter. .PARAMETER Version Default: latest Represents a build version on specific channel. Possible values: @@ -25,26 +26,24 @@ - 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 + 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 + Possible values are: , amd64, x64, x86, arm64, arm .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 + - windowsdesktop - the Microsoft.WindowsDesktop.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 @@ -70,19 +69,25 @@ .PARAMETER ProxyUseDefaultCredentials Default: false Use default credentials, when using proxy address. +.PARAMETER ProxyBypassList + If set with ProxyAddress, will provide the list of comma separated urls that will bypass the proxy .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. +.PARAMETER JSonFile + Determines the SDK version from a user specified global.json file + Note: global.json must have a value for 'SDK:Version' #> [cmdletbinding()] param( [string]$Channel="LTS", [string]$Version="Latest", + [string]$JSonFile, [string]$InstallDir="", [string]$Architecture="", - [ValidateSet("dotnet", "aspnetcore", IgnoreCase = $false)] + [ValidateSet("dotnet", "aspnetcore", "windowsdesktop", 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, @@ -93,6 +98,7 @@ param( [string]$FeedCredential, [string]$ProxyAddress, [switch]$ProxyUseDefaultCredentials, + [string[]]$ProxyBypassList=@(), [switch]$SkipNonVersionedFiles, [switch]$NoCdn ) @@ -116,11 +122,27 @@ $VersionRegEx="/\d+\.\d+[^/]+/" $OverrideNonVersionedFiles = !$SkipNonVersionedFiles function Say($str) { - Write-Host "dotnet-install: $str" + try + { + Write-Host "dotnet-install: $str" + } + catch + { + # Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output + Write-Output "dotnet-install: $str" + } } function Say-Verbose($str) { - Write-Verbose "dotnet-install: $str" + try + { + Write-Verbose "dotnet-install: $str" + } + catch + { + # Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output + Write-Output "dotnet-install: $str" + } } function Say-Invocation($Invocation) { @@ -151,7 +173,16 @@ function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [in function Get-Machine-Architecture() { Say-Invocation $MyInvocation - # possible values: amd64, x64, x86, arm64, arm + # On PS x86, PROCESSOR_ARCHITECTURE reports x86 even on x64 systems. + # To get the correct architecture, we need to use PROCESSOR_ARCHITEW6432. + # PS x64 doesn't define this, so we fall back to PROCESSOR_ARCHITECTURE. + # Possible values: amd64, x64, x86, arm64, arm + + if( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) + { + return $ENV:PROCESSOR_ARCHITEW6432 + } + return $ENV:PROCESSOR_ARCHITECTURE } @@ -164,18 +195,25 @@ function Get-CLIArchitecture-From-Architecture([string]$Architecture) { { $_ -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" } + default { throw "Architecture '$Architecture' not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" } } } +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version function Get-Version-Info-From-Version-Text([string]$VersionText) { Say-Invocation $MyInvocation - $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); + $Data = -split $VersionText - $VersionInfo = @{} - $VersionInfo.CommitHash = $Data[0].Trim() - $VersionInfo.Version = $Data[1].Trim() + $VersionInfo = @{ + CommitHash = $(if ($Data.Count -gt 1) { $Data[0] }) + Version = $Data[-1] # last line is always the version number. + } return $VersionInfo } @@ -218,7 +256,11 @@ function GetHTTPResponse([Uri] $Uri) if($ProxyAddress) { $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler - $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials} + $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{ + Address=$ProxyAddress; + UseDefaultCredentials=$ProxyUseDefaultCredentials; + BypassList = $ProxyBypassList; + } $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler } else { @@ -226,8 +268,8 @@ function GetHTTPResponse([Uri] $Uri) $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 + # 20 minutes allows it to work over much slower connections. + $HttpClient.Timeout = New-TimeSpan -Minutes 20 $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. @@ -249,7 +291,6 @@ function GetHTTPResponse([Uri] $Uri) }) } - function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) { Say-Invocation $MyInvocation @@ -260,6 +301,10 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co elseif ($Runtime -eq "aspnetcore") { $VersionFileUrl = "$UncachedFeed/aspnetcore/Runtime/$Channel/latest.version" } + # Currently, the WindowsDesktop runtime is manufactured with the .Net core runtime + elseif ($Runtime -eq "windowsdesktop") { + $VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version" + } elseif (-not $Runtime) { if ($Coherent) { $VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version" @@ -271,8 +316,12 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co else { throw "Invalid value for `$Runtime" } - - $Response = GetHTTPResponse -Uri $VersionFileUrl + try { + $Response = GetHTTPResponse -Uri $VersionFileUrl + } + catch { + throw "Could not resolve version information." + } $StringContent = $Response.Content.ReadAsStringAsync().Result switch ($Response.Content.Headers.ContentType) { @@ -287,42 +336,87 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Co return $VersionInfo } - -function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) { +function Parse-Jsonfile-For-Version([string]$JSonFile) { Say-Invocation $MyInvocation - switch ($Version.ToLower()) { - { $_ -eq "latest" } { - $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False - return $LatestVersionInfo.Version + If (-Not (Test-Path $JSonFile)) { + throw "Unable to find '$JSonFile'" + } + try { + $JSonContent = Get-Content($JSonFile) -Raw | ConvertFrom-Json | Select-Object -expand "sdk" -ErrorAction SilentlyContinue + } + catch { + throw "Json file unreadable: '$JSonFile'" + } + if ($JSonContent) { + try { + $JSonContent.PSObject.Properties | ForEach-Object { + $PropertyName = $_.Name + if ($PropertyName -eq "version") { + $Version = $_.Value + Say-Verbose "Version = $Version" + } + } + } + catch { + throw "Unable to parse the SDK node in '$JSonFile'" } - { $_ -eq "coherent" } { - $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True - return $LatestVersionInfo.Version + } + else { + throw "Unable to find the SDK node in '$JSonFile'" + } + If ($Version -eq $null) { + throw "Unable to find the SDK:version node in '$JSonFile'" + } + return $Version +} + +function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version, [string]$JSonFile) { + Say-Invocation $MyInvocation + + if (-not $JSonFile) { + 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 } } - default { return $Version } + } + else { + return Parse-Jsonfile-For-Version $JSonFile } } function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { Say-Invocation $MyInvocation + # If anything fails in this lookup it will default to $SpecificVersion + $SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion + if ($Runtime -eq "dotnet") { - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" } elseif ($Runtime -eq "aspnetcore") { - $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificVersion-win-$CLIArchitecture.zip" + $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" + } + elseif ($Runtime -eq "windowsdesktop") { + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" } elseif (-not $Runtime) { - $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip" + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip" } else { throw "Invalid value for `$Runtime" } - Say-Verbose "Constructed primary payload URL: $PayloadURL" + Say-Verbose "Constructed primary named payload URL: $PayloadURL" - return $PayloadURL + return $PayloadURL, $SpecificProductVersion } function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) { @@ -338,11 +432,56 @@ function Get-LegacyDownload-Link([string]$AzureFeed, [string]$SpecificVersion, [ return $null } - Say-Verbose "Constructed legacy payload URL: $PayloadURL" + Say-Verbose "Constructed legacy named payload URL: $PayloadURL" return $PayloadURL } +function Get-Product-Version([string]$AzureFeed, [string]$SpecificVersion) { + Say-Invocation $MyInvocation + + if ($Runtime -eq "dotnet") { + $ProductVersionTxtURL = "$AzureFeed/Runtime/$SpecificVersion/productVersion.txt" + } + elseif ($Runtime -eq "aspnetcore") { + $ProductVersionTxtURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/productVersion.txt" + } + elseif ($Runtime -eq "windowsdesktop") { + $ProductVersionTxtURL = "$AzureFeed/Runtime/$SpecificVersion/productVersion.txt" + } + elseif (-not $Runtime) { + $ProductVersionTxtURL = "$AzureFeed/Sdk/$SpecificVersion/productVersion.txt" + } + else { + throw "Invalid value '$Runtime' specified for `$Runtime" + } + + Say-Verbose "Checking for existence of $ProductVersionTxtURL" + + try { + $productVersionResponse = GetHTTPResponse($productVersionTxtUrl) + + if ($productVersionResponse.StatusCode -eq 200) { + $productVersion = $productVersionResponse.Content.ReadAsStringAsync().Result.Trim() + if ($productVersion -ne $SpecificVersion) + { + Say "Using alternate version $productVersion found in $ProductVersionTxtURL" + } + + return $productVersion + } + else { + Say-Verbose "Got StatusCode $($productVersionResponse.StatusCode) trying to get productVersion.txt at $productVersionTxtUrl, so using default value of $SpecificVersion" + $productVersion = $SpecificVersion + } + } catch { + Say-Verbose "Could not read productVersion.txt at $productVersionTxtUrl, so using default value of $SpecificVersion (Exception: '$($_.Exception.Message)' )" + $productVersion = $SpecificVersion + } + + return $productVersion +} + function Get-User-Share-Path() { Say-Invocation $MyInvocation @@ -362,28 +501,11 @@ function Resolve-Installation-Path([string]$InstallDir) { 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" + Say-Verbose "Is-Dotnet-Package-Installed: DotnetPackagePath=$DotnetPackagePath" return Test-Path $DotnetPackagePath -PathType Container } @@ -468,17 +590,23 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { } } -function DownloadFile([Uri]$Uri, [string]$OutPath) { - if ($Uri -notlike "http*") { - Say-Verbose "Copying file from $Uri to $OutPath" - Copy-Item $Uri.AbsolutePath $OutPath +function DownloadFile($Source, [string]$OutPath) { + if ($Source -notlike "http*") { + # Using System.IO.Path.GetFullPath to get the current directory + # does not work in this context - $pwd gives the current directory + if (![System.IO.Path]::IsPathRooted($Source)) { + $Source = $(Join-Path -Path $pwd -ChildPath $Source) + } + $Source = Get-Absolute-Path $Source + Say "Copying file from $Source to $OutPath" + Copy-Item $Source $OutPath return } $Stream = $null try { - $Response = GetHTTPResponse -Uri $Uri + $Response = GetHTTPResponse -Uri $Source $Stream = $Response.Content.ReadAsStreamAsync().Result $File = [System.IO.File]::Create($OutPath) $Stream.CopyTo($File) @@ -494,32 +622,60 @@ function DownloadFile([Uri]$Uri, [string]$OutPath) { 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 + $SuffixedBinPath = "$BinPath;" + if (-Not $env:path.Contains($SuffixedBinPath)) { + Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." + $env:path = $SuffixedBinPath + $env:path + } else { + Say-Verbose "Current process PATH already contains `"$BinPath`"" + } } else { Say "Binaries of dotnet can be found in $BinPath" } } +Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +Say "- The SDK needs to be installed without user interaction and without admin rights." +Say "- The SDK installation doesn't need to persist across multiple CI runs." +Say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n" + $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 +$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version -JSonFile $JSonFile +$DownloadLink, $EffectiveVersion = Get-Download-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture $LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture +$InstallRoot = Resolve-Installation-Path $InstallDir +Say-Verbose "InstallRoot: $InstallRoot" +$ScriptName = $MyInvocation.MyCommand.Name + if ($DryRun) { Say "Payload URLs:" - Say "Primary - $DownloadLink" + Say "Primary named payload URL: $DownloadLink" if ($LegacyDownloadLink) { - Say "Legacy - $LegacyDownloadLink" + Say "Legacy named payload URL: $LegacyDownloadLink" + } + $RepeatableCommand = ".\$ScriptName -Version `"$SpecificVersion`" -InstallDir `"$InstallRoot`" -Architecture `"$CLIArchitecture`"" + if ($Runtime -eq "dotnet") { + $RepeatableCommand+=" -Runtime `"dotnet`"" } - Say "Repeatable invocation: .\$($MyInvocation.Line)" + elseif ($Runtime -eq "aspnetcore") { + $RepeatableCommand+=" -Runtime `"aspnetcore`"" + } + foreach ($key in $MyInvocation.BoundParameters.Keys) { + if (-not (@("Architecture","Channel","DryRun","InstallDir","Runtime","SharedRuntime","Version") -contains $key)) { + $RepeatableCommand+=" -$key `"$($MyInvocation.BoundParameters[$key])`"" + } + } + Say "Repeatable invocation: $RepeatableCommand" + if ($SpecificVersion -ne $EffectiveVersion) + { + Say "NOTE: Due to finding a version manifest with this runtime, it would actually install with version '$EffectiveVersion'" + } + exit 0 } -$InstallRoot = Resolve-Installation-Path $InstallDir -Say-Verbose "InstallRoot: $InstallRoot" - if ($Runtime -eq "dotnet") { $assetName = ".NET Core Runtime" $dotnetPackageRelativePath = "shared\Microsoft.NETCore.App" @@ -528,6 +684,10 @@ elseif ($Runtime -eq "aspnetcore") { $assetName = "ASP.NET Core Runtime" $dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App" } +elseif ($Runtime -eq "windowsdesktop") { + $assetName = ".NET Core Windows Desktop Runtime" + $dotnetPackageRelativePath = "shared\Microsoft.WindowsDesktop.App" +} elseif (-not $Runtime) { $assetName = ".NET Core SDK" $dotnetPackageRelativePath = "sdk" @@ -536,6 +696,12 @@ else { throw "Invalid value for `$Runtime" } +if ($SpecificVersion -ne $EffectiveVersion) +{ + Say "Performing installation checks for effective version: $EffectiveVersion" + $SpecificVersion = $EffectiveVersion +} + # Check if the SDK version is already installed. $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $SpecificVersion if ($isAssetInstalled) { @@ -559,7 +725,7 @@ Say-Verbose "Zip path: $ZipPath" $DownloadFailed = $false Say "Downloading link: $DownloadLink" try { - DownloadFile -Uri $DownloadLink -OutPath $ZipPath + DownloadFile -Source $DownloadLink -OutPath $ZipPath } catch { Say "Cannot download: $DownloadLink" @@ -569,7 +735,7 @@ catch { Say-Verbose "Legacy zip path: $ZipPath" Say "Downloading legacy link: $DownloadLink" try { - DownloadFile -Uri $DownloadLink -OutPath $ZipPath + DownloadFile -Source $DownloadLink -OutPath $ZipPath } catch { Say "Cannot download: $DownloadLink" @@ -588,8 +754,22 @@ if ($DownloadFailed) { 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 +# Check if the SDK version is installed; if not, fail the installation. +$isAssetInstalled = $false + +# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. +if ($SpecificVersion -Match "rtm" -or $SpecificVersion -Match "servicing") { + $ReleaseVersion = $SpecificVersion.Split("-")[0] + Say-Verbose "Checking installation: version = $ReleaseVersion" + $isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $ReleaseVersion +} + +# Check if the SDK version is installed. +if (!$isAssetInstalled) { + Say-Verbose "Checking installation: version = $SpecificVersion" + $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." } @@ -598,5 +778,199 @@ Remove-Item $ZipPath Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath +Say "Note that the script does not resolve dependencies during installation." +Say "To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install/windows#dependencies" Say "Installation finished" -exit 0 \ No newline at end of file +exit 0 +# SIG # Begin signature block +# MIIjlgYJKoZIhvcNAQcCoIIjhzCCI4MCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA+isugNMwZSGLd +# kfBd0C2Ud//U2Nbj31s1jg3Yf9gh4KCCDYUwggYDMIID66ADAgECAhMzAAABiK9S +# 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0 +# t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs +# 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd +# vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv +# V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W +# MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q +# qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X +# 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P +# mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM +# i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT +# GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz +# LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM +# SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa +# 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV +# Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+ +# r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWcwghVjAgEBMIGVMH4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA +# AYgwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIK4I +# CDH7/r/eeMqTtDETJ67ogfneVRo0/P6ogV2vy4tXMEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEAOnmVmILEjI6ZiuuSOvvTvijidkBez61Vz97A +# jV3AOsfmUvLpVaTVa1Mt2iPDuq1QLqRPaT7BD8PAUwr91pYllVgEd8NqivCIaCZg +# QyIRiTmHQxbozWsLcjxMvX2VxSmNKDw7IOHzUbXtmiEGhygyZpdh/uiCj7ziSxp3 +# lQBR8mUE1NL9dxaxKWLhGeORqAepw6nId9oO+mHRh4JRK7uqZOFAES7/21M9vPZi +# XYilJLgIoyMkvqYSdoouzn6+m74kgzkNkyK9GYz2mmO2BCMnai9Njze2d0+kY+37 +# kt10BmJDw3FHaZ+/fH/TMTgo0ZcAOicP9ccdIh/CzzpU52o+Q6GCEvEwghLtBgor +# BgEEAYI3AwMBMYIS3TCCEtkGCSqGSIb3DQEHAqCCEsowghLGAgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBSbhMJwNER+BICn3iLUnPrP8dptyUphcFC +# A/NsIgnPLwIGX4hEzP6WGBMyMDIwMTEwOTE0NDY1Mi4yMzNaMASAAgH0oIHUpIHR +# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL +# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh +# bGVzIFRTUyBFU046MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBU +# aW1lLVN0YW1wIFNlcnZpY2Wggg5EMIIE9TCCA92gAwIBAgITMwAAAScvbqPvkagZ +# qAAAAAABJzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg +# MjAxMDAeFw0xOTEyMTkwMTE0NTlaFw0yMTAzMTcwMTE0NTlaMIHOMQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg +# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 +# MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl +# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD4Ad5xEZ5On0uN +# L71ng9xwoDPRKeMUyEIj5yVxPRPh5GVbU7D3pqDsoXzQMhfeRP61L1zlU1HCRS+1 +# 29eo0yj1zjbAlmPAwosUgyIonesWt9E4hFlXCGUcIg5XMdvQ+Ouzk2r+awNRuk8A +# BGOa0I4VBy6zqCYHyX2pGauiB43frJSNP6pcrO0CBmpBZNjgepof5Z/50vBuJDUS +# ug6OIMQ7ZwUhSzX4bEmZUUjAycBb62dhQpGqHsXe6ypVDTgAEnGONdSBKkHiNT8H +# 0Zt2lm0vCLwHyTwtgIdi67T/LCp+X2mlPHqXsY3u72X3GYn/3G8YFCkrSc6m3b0w +# TXPd5/2fAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQU5fSWVYBfOTEkW2JTiV24WNNt +# lfIwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBL +# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv +# TWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr +# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAK +# BggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEACsqNfNFVxwalZ42cEMuzZc12 +# 6Nvluanx8UewDVeUQZEZHRmppMFHAzS/g6RzmxTyR2tKE3mChNGW5dTL730vEbRh +# nYRmBgiX/gT3f4AQrOPnZGXY7zszcrlbgzxpakOX+x0u4rkP3Ashh3B2CdJ11XsB +# di5PiZa1spB6U5S8D15gqTUfoIniLT4v1DBdkWExsKI1vsiFcDcjGJ4xRlMRF+fw +# 7SY0WZoOzwRzKxDTdg4DusAXpaeKbch9iithLFk/vIxQrqCr/niW8tEA+eSzeX/E +# q1D0ZyvOn4e2lTnwoJUKH6OQAWSBogyK4OCbFeJOqdKAUiBTgHKkQIYh/tbKQjCC +# BnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV +# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w +# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m +# dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1 +# NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw +# ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/ +# aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxh +# MFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhH +# hjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tk +# iVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox +# 8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJN +# AgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIox +# kPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0P +# BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9 +# lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu +# Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3Js +# MFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3Nv +# ZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAG +# A1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRw +# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAG +# CCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEA +# dABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXED +# PZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgr +# UYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c +# 8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFw +# nzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFt +# w5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk +# 7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9d +# dJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zG +# y9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3 +# yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7c +# RDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wkn +# HNWzfjUeCLraNtvTX4/edIhJEqGCAtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYD +# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe +# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3Nv +# ZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBF +# U046MEE1Ni1FMzI5LTRENEQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVALOVuE5sgxzETO4s+poBqI6r1x8zoIGD +# MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV +# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG +# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF +# BQACBQDjU7byMCIYDzIwMjAxMTA5MTYzOTE0WhgPMjAyMDExMTAxNjM5MTRaMHcw +# PQYKKwYBBAGEWQoEATEvMC0wCgIFAONTtvICAQAwCgIBAAICIt0CAf8wBwIBAAIC +# EcQwCgIFAONVCHICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAK +# MAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQAQhyIIAC/A +# P+VJdbhL9IQgm8WTa1DmPPE+BQSuRbBy2MmzC1KostixdEkr2OaNSjcYuZBNIJgv +# vE8CWhVDD+sbBpVcOdoSfoBwHXKfvqSTiWvovoexkF0X5aon7yr3PkJ/kEqoLyUM +# xRvdWKJdHOL1sT0/aWHn048c6aGin/zc8DGCAw0wggMJAgEBMIGTMHwxCzAJBgNV +# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w +# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m +# dCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJy9uo++RqBmoAAAAAAEnMA0GCWCG +# SAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZI +# hvcNAQkEMSIEIJZkrbvF4R8oqYYpN6ZPGOj+QEZTQriEi/Yw9gW6zMqRMIH6Bgsq +# hkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgG5LoSxKGHWoW/wVMlbMztlQ4upAdzEmq +# H//vLu0jPiIwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT +# MwAAAScvbqPvkagZqAAAAAABJzAiBCDwhEViCRvqKwQV3MxociF2iGYrDP4p1BK+ +# s4tStO4vSDANBgkqhkiG9w0BAQsFAASCAQAkgmDo8lVmar0ZIqTG1it3skG8PZC9 +# iqEEC1vxcz8OSfsjl2QSkQ5T2+3xWpxWA4uy2+Byv0bi8EsfQEnnn4vtdthS6/kb +# vB/LLQiqoMhJ0rasf3/y/4KnQZEtztpg1+cCaNwFUgI6o+E8YEFt1frhLwFs/0WH +# 5pyBFx9ECEs0M22SLIpW13gexv9fgk6ZboIfSreAI28DLveeJpkgwggxHRpuVOVD +# 4D7QQJAvJ0VU6p+yJlbvQXR9iltwb1REhlsJ5mADJ/FkzPVX/swMSUIoyE2inlxK +# LEiPkkZYwiFYCifFYUTnQjWU1Ls0EV+ysosL+jhzCxO8S6oRdp5TAi4F +# SIG # End signature block diff --git a/tools/dotnet-install.sh b/tools/dotnet-install.sh new file mode 100755 index 000000000..6525339be --- /dev/null +++ b/tools/dotnet-install.sh @@ -0,0 +1,1108 @@ +#!/usr/bin/env bash +# 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. +# + +# Stop script on NZEC +set -e +# Stop script if unbound variable found (use ${var:-} if intentional) +set -u +# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success +# This is causing it to fail +set -o pipefail + +# Use in the the functions: eval $invocation +invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' + +# standard output may be used as a return value in the functions +# we need a way to write text on the screen in the functions so that +# it won't interfere with the return value. +# Exposing stream 3 as a pipe to standard output of the script itself +exec 3>&1 + +# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. +# See if stdout is a terminal +if [ -t 1 ] && command -v tput > /dev/null; then + # see if it supports colors + ncolors=$(tput colors) + if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then + bold="$(tput bold || echo)" + normal="$(tput sgr0 || echo)" + black="$(tput setaf 0 || echo)" + red="$(tput setaf 1 || echo)" + green="$(tput setaf 2 || echo)" + yellow="$(tput setaf 3 || echo)" + blue="$(tput setaf 4 || echo)" + magenta="$(tput setaf 5 || echo)" + cyan="$(tput setaf 6 || echo)" + white="$(tput setaf 7 || echo)" + fi +fi + +say_warning() { + printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" +} + +say_err() { + printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 +} + +say() { + # using stream 3 (defined in the beginning) to not interfere with stdout of functions + # which may be used as return value + printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 +} + +say_verbose() { + if [ "$verbose" = true ]; then + say "$1" + fi +} + +# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets, +# then and only then should the Linux distribution appear in this list. +# Adding a Linux distribution to this list does not imply distribution-specific support. +get_legacy_os_name_from_platform() { + eval $invocation + + platform="$1" + case "$platform" in + "centos.7") + echo "centos" + return 0 + ;; + "debian.8") + echo "debian" + return 0 + ;; + "debian.9") + echo "debian.9" + return 0 + ;; + "fedora.23") + echo "fedora.23" + return 0 + ;; + "fedora.24") + echo "fedora.24" + return 0 + ;; + "fedora.27") + echo "fedora.27" + return 0 + ;; + "fedora.28") + echo "fedora.28" + return 0 + ;; + "opensuse.13.2") + echo "opensuse.13.2" + return 0 + ;; + "opensuse.42.1") + echo "opensuse.42.1" + return 0 + ;; + "opensuse.42.3") + echo "opensuse.42.3" + return 0 + ;; + "rhel.7"*) + echo "rhel" + return 0 + ;; + "ubuntu.14.04") + echo "ubuntu" + return 0 + ;; + "ubuntu.16.04") + echo "ubuntu.16.04" + return 0 + ;; + "ubuntu.16.10") + echo "ubuntu.16.10" + return 0 + ;; + "ubuntu.18.04") + echo "ubuntu.18.04" + return 0 + ;; + "alpine.3.4.3") + echo "alpine" + return 0 + ;; + esac + return 1 +} + +get_linux_platform_name() { + eval $invocation + + if [ -n "$runtime_id" ]; then + echo "${runtime_id%-*}" + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + echo "$ID${VERSION_ID:+.${VERSION_ID}}" + return 0 + elif [ -e /etc/redhat-release ]; then + local redhatRelease=$(&1 || true) | grep -q musl +} + +get_current_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ "$uname" = "FreeBSD" ]; then + echo "freebsd" + return 0 + elif [ "$uname" = "Linux" ]; then + local linux_platform_name + linux_platform_name="$(get_linux_platform_name)" || { echo "linux" && return 0 ; } + + if [ "$linux_platform_name" = "rhel.6" ]; then + echo $linux_platform_name + return 0 + elif is_musl_based_distro; then + echo "linux-musl" + return 0 + else + echo "linux" + return 0 + fi + fi + + say_err "OS name could not be detected: UName = $uname" + return 1 +} + +get_legacy_os_name() { + eval $invocation + + local uname=$(uname) + if [ "$uname" = "Darwin" ]; then + echo "osx" + return 0 + elif [ -n "$runtime_id" ]; then + echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}") + return 0 + else + if [ -e /etc/os-release ]; then + . /etc/os-release + os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "") + if [ -n "$os" ]; then + echo "$os" + return 0 + fi + fi + fi + + say_verbose "Distribution specific OS name and version could not be detected: UName = $uname" + return 1 +} + +machine_has() { + eval $invocation + + hash "$1" > /dev/null 2>&1 + return $? +} + + +check_min_reqs() { + local hasMinimum=false + if machine_has "curl"; then + hasMinimum=true + elif machine_has "wget"; then + hasMinimum=true + fi + + if [ "$hasMinimum" = "false" ]; then + say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed." + return 1 + fi + return 0 +} + +# args: +# input - $1 +to_lowercase() { + #eval $invocation + + echo "$1" | tr '[:upper:]' '[:lower:]' + return 0 +} + +# args: +# input - $1 +remove_trailing_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input%/}" + return 0 +} + +# args: +# input - $1 +remove_beginning_slash() { + #eval $invocation + + local input="${1:-}" + echo "${input#/}" + return 0 +} + +# args: +# root_path - $1 +# child_path - $2 - this parameter can be empty +combine_paths() { + eval $invocation + + # TODO: Consider making it work with any number of paths. For now: + if [ ! -z "${3:-}" ]; then + say_err "combine_paths: Function takes two parameters." + return 1 + fi + + local root_path="$(remove_trailing_slash "$1")" + local child_path="$(remove_beginning_slash "${2:-}")" + say_verbose "combine_paths: root_path=$root_path" + say_verbose "combine_paths: child_path=$child_path" + echo "$root_path/$child_path" + return 0 +} + +get_machine_architecture() { + eval $invocation + + if command -v uname > /dev/null; then + CPUName=$(uname -m) + case $CPUName in + armv7l) + echo "arm" + return 0 + ;; + aarch64) + echo "arm64" + return 0 + ;; + esac + fi + + # Always default to 'x64' + echo "x64" + return 0 +} + +# args: +# architecture - $1 +get_normalized_architecture_from_architecture() { + eval $invocation + + local architecture="$(to_lowercase "$1")" + case "$architecture" in + \) + echo "$(get_normalized_architecture_from_architecture "$(get_machine_architecture)")" + return 0 + ;; + amd64|x64) + echo "x64" + return 0 + ;; + arm) + echo "arm" + return 0 + ;; + arm64) + echo "arm64" + return 0 + ;; + esac + + say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" + return 1 +} + +# The version text returned from the feeds is a 1-line or 2-line string: +# For the SDK and the dotnet runtime (2 lines): +# Line 1: # commit_hash +# Line 2: # 4-part version +# For the aspnetcore runtime (1 line): +# Line 1: # 4-part version + +# args: +# version_text - stdin +get_version_from_version_info() { + eval $invocation + + cat | tail -n 1 | sed 's/\r$//' + return 0 +} + +# args: +# install_root - $1 +# relative_path_to_package - $2 +# specific_version - $3 +is_dotnet_package_installed() { + eval $invocation + + local install_root="$1" + local relative_path_to_package="$2" + local specific_version="${3//[$'\t\r\n']}" + + local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")" + say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" + + if [ -d "$dotnet_package_path" ]; then + return 0 + else + return 1 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# coherent - $4 +get_latest_version_info() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local coherent="$4" + + local version_file_url=null + if [[ "$runtime" == "dotnet" ]]; then + version_file_url="$uncached_feed/Runtime/$channel/latest.version" + elif [[ "$runtime" == "aspnetcore" ]]; then + version_file_url="$uncached_feed/aspnetcore/Runtime/$channel/latest.version" + elif [ -z "$runtime" ]; then + if [ "$coherent" = true ]; then + version_file_url="$uncached_feed/Sdk/$channel/latest.coherent.version" + else + version_file_url="$uncached_feed/Sdk/$channel/latest.version" + fi + else + say_err "Invalid value for \$runtime" + return 1 + fi + say_verbose "get_latest_version_info: latest url: $version_file_url" + + download "$version_file_url" + return $? +} + +# args: +# json_file - $1 +parse_jsonfile_for_version() { + eval $invocation + + local json_file="$1" + if [ ! -f "$json_file" ]; then + say_err "Unable to find \`$json_file\`" + return 1 + fi + + sdk_section=$(cat $json_file | awk '/"sdk"/,/}/') + if [ -z "$sdk_section" ]; then + say_err "Unable to parse the SDK node in \`$json_file\`" + return 1 + fi + + sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}') + sdk_list=${sdk_list//[\" ]/} + sdk_list=${sdk_list//,/$'\n'} + + local version_info="" + while read -r line; do + IFS=: + while read -r key value; do + if [[ "$key" == "version" ]]; then + version_info=$value + fi + done <<< "$line" + done <<< "$sdk_list" + if [ -z "$version_info" ]; then + say_err "Unable to find the SDK:version node in \`$json_file\`" + return 1 + fi + + unset IFS; + echo "$version_info" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# version - $4 +# json_file - $5 +get_specific_version_from_version() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local version="$(to_lowercase "$4")" + local json_file="$5" + + if [ -z "$json_file" ]; then + case "$version" in + latest) + local version_info + version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_version_info + return 0 + ;; + coherent) + local version_info + version_info="$(get_latest_version_info "$azure_feed" "$channel" "$normalized_architecture" true)" || return 1 + say_verbose "get_specific_version_from_version: version_info=$version_info" + echo "$version_info" | get_version_from_version_info + return 0 + ;; + *) + echo "$version" + return 0 + ;; + esac + else + local version_info + version_info="$(parse_jsonfile_for_version "$json_file")" || return 1 + echo "$version_info" + return 0 + fi +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + local specific_product_version="$(get_specific_product_version "$1" "$4")" + + local osname + osname="$(get_current_os_name)" || return 1 + + local download_link=null + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz" + else + return 1 + fi + + echo "$download_link" + return 0 +} + +# args: +# azure_feed - $1 +# specific_version - $2 +get_specific_product_version() { + # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents + # to resolve the version of what's in the folder, superseding the specified version. + eval $invocation + + local azure_feed="$1" + local specific_version="${2//[$'\t\r\n']}" + local specific_product_version=$specific_version + + local download_link=null + if [[ "$runtime" == "dotnet" ]]; then + download_link="$azure_feed/Runtime/$specific_version/productVersion.txt${feed_credential}" + elif [[ "$runtime" == "aspnetcore" ]]; then + download_link="$azure_feed/aspnetcore/Runtime/$specific_version/productVersion.txt${feed_credential}" + elif [ -z "$runtime" ]; then + download_link="$azure_feed/Sdk/$specific_version/productVersion.txt${feed_credential}" + else + return 1 + fi + + if machine_has "curl" + then + specific_product_version=$(curl -s --fail "$download_link") + if [ $? -ne 0 ] + then + specific_product_version=$specific_version + fi + elif machine_has "wget" + then + specific_product_version=$(wget -qO- "$download_link") + if [ $? -ne 0 ] + then + specific_product_version=$specific_version + fi + fi + specific_product_version="${specific_product_version//[$'\t\r\n']}" + + echo "$specific_product_version" + return 0 +} + +# args: +# azure_feed - $1 +# channel - $2 +# normalized_architecture - $3 +# specific_version - $4 +construct_legacy_download_link() { + eval $invocation + + local azure_feed="$1" + local channel="$2" + local normalized_architecture="$3" + local specific_version="${4//[$'\t\r\n']}" + + local distro_specific_osname + distro_specific_osname="$(get_legacy_os_name)" || return 1 + + local legacy_download_link=null + if [[ "$runtime" == "dotnet" ]]; then + legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + elif [ -z "$runtime" ]; then + legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz" + else + return 1 + fi + + echo "$legacy_download_link" + return 0 +} + +get_user_install_path() { + eval $invocation + + if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then + echo "$DOTNET_INSTALL_DIR" + else + echo "$HOME/.dotnet" + fi + return 0 +} + +# args: +# install_dir - $1 +resolve_installation_path() { + eval $invocation + + local install_dir=$1 + if [ "$install_dir" = "" ]; then + local user_install_path="$(get_user_install_path)" + say_verbose "resolve_installation_path: user_install_path=$user_install_path" + echo "$user_install_path" + return 0 + fi + + echo "$install_dir" + return 0 +} + +# args: +# relative_or_absolute_path - $1 +get_absolute_path() { + eval $invocation + + local relative_or_absolute_path=$1 + echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")" + return 0 +} + +# args: +# input_files - stdin +# root_path - $1 +# out_path - $2 +# override - $3 +copy_files_or_dirs_from_list() { + eval $invocation + + local root_path="$(remove_trailing_slash "$1")" + local out_path="$(remove_trailing_slash "$2")" + local override="$3" + local osname="$(get_current_os_name)" + local override_switch=$( + if [ "$override" = false ]; then + if [ "$osname" = "linux-musl" ]; then + printf -- "-u"; + else + printf -- "-n"; + fi + fi) + + cat | uniq | while read -r file_path; do + local path="$(remove_beginning_slash "${file_path#$root_path}")" + local target="$out_path/$path" + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then + mkdir -p "$out_path/$(dirname "$path")" + if [ -d "$target" ]; then + rm -rf "$target" + fi + cp -R $override_switch "$root_path/$path" "$target" + fi + done +} + +# args: +# zip_path - $1 +# out_path - $2 +extract_dotnet_package() { + eval $invocation + + local zip_path="$1" + local out_path="$2" + + local temp_out_path="$(mktemp -d "$temporary_file_template")" + + local failed=false + tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true + + local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + + rm -rf "$temp_out_path" + + if [ "$failed" = true ]; then + say_err "Extraction failed" + return 1 + fi +} + +# args: +# remote_path - $1 +# [out_path] - $2 - stdout if not provided +download() { + eval $invocation + + local remote_path="$1" + local out_path="${2:-}" + + if [[ "$remote_path" != "http"* ]]; then + cp "$remote_path" "$out_path" + return $? + fi + + local failed=false + if machine_has "curl"; then + downloadcurl "$remote_path" "$out_path" || failed=true + elif machine_has "wget"; then + downloadwget "$remote_path" "$out_path" || failed=true + else + failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Download failed: $remote_path" + return 1 + fi + return 0 +} + +downloadcurl() { + eval $invocation + local remote_path="$1" + local out_path="${2:-}" + + # Append feed_credential as late as possible before calling curl to avoid logging feed_credential + remote_path="${remote_path}${feed_credential}" + + local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs " + local failed=false + if [ -z "$out_path" ]; then + curl $curl_options "$remote_path" || failed=true + else + curl $curl_options -o "$out_path" "$remote_path" || failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Curl download failed" + return 1 + fi + return 0 +} + +downloadwget() { + eval $invocation + local remote_path="$1" + local out_path="${2:-}" + + # Append feed_credential as late as possible before calling wget to avoid logging feed_credential + remote_path="${remote_path}${feed_credential}" + local wget_options="--tries 20 --waitretry 2 --connect-timeout 15 " + local failed=false + if [ -z "$out_path" ]; then + wget -q $wget_options -O - "$remote_path" || failed=true + else + wget $wget_options -O "$out_path" "$remote_path" || failed=true + fi + if [ "$failed" = true ]; then + say_verbose "Wget download failed" + return 1 + fi + return 0 +} + +calculate_vars() { + eval $invocation + valid_legacy_download_link=true + + normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")" + say_verbose "normalized_architecture=$normalized_architecture" + + specific_version="$(get_specific_version_from_version "$azure_feed" "$channel" "$normalized_architecture" "$version" "$json_file")" + specific_product_version="$(get_specific_product_version "$azure_feed" "$specific_version")" + say_verbose "specific_version=$specific_version" + if [ -z "$specific_version" ]; then + say_err "Could not resolve version information." + return 1 + fi + + download_link="$(construct_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" + say_verbose "Constructed primary named payload URL: $download_link" + + legacy_download_link="$(construct_legacy_download_link "$azure_feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false + + if [ "$valid_legacy_download_link" = true ]; then + say_verbose "Constructed legacy named payload URL: $legacy_download_link" + else + say_verbose "Cound not construct a legacy_download_link; omitting..." + fi + + install_root="$(resolve_installation_path "$install_dir")" + say_verbose "InstallRoot: $install_root" +} + +install_dotnet() { + eval $invocation + local download_failed=false + local asset_name='' + local asset_relative_path='' + + if [[ "$runtime" == "dotnet" ]]; then + asset_relative_path="shared/Microsoft.NETCore.App" + asset_name=".NET Core Runtime" + elif [[ "$runtime" == "aspnetcore" ]]; then + asset_relative_path="shared/Microsoft.AspNetCore.App" + asset_name="ASP.NET Core Runtime" + elif [ -z "$runtime" ]; then + asset_relative_path="sdk" + asset_name=".NET Core SDK" + else + say_err "Invalid value for \$runtime" + return 1 + fi + + # Check if the SDK version is already installed. + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_version"; then + say "$asset_name version $specific_version is already installed." + return 0 + fi + + mkdir -p "$install_root" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Zip path: $zip_path" + + say "Downloading link: $download_link" + + # Failures are normal in the non-legacy case for ultimately legacy downloads. + # Do not output to stderr, since output to stderr is considered an error. + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + # if the download fails, download the legacy_download_link + if [ "$download_failed" = true ]; then + say "Cannot download: $download_link" + + if [ "$valid_legacy_download_link" = true ]; then + download_failed=false + download_link="$legacy_download_link" + zip_path="$(mktemp "$temporary_file_template")" + say_verbose "Legacy zip path: $zip_path" + say "Downloading legacy link: $download_link" + download "$download_link" "$zip_path" 2>&1 || download_failed=true + + if [ "$download_failed" = true ]; then + say "Cannot download: $download_link" + fi + fi + fi + + if [ "$download_failed" = true ]; then + say_err "Could not find/download: \`$asset_name\` with version = $specific_version" + say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support" + return 1 + fi + + say "Extracting zip from $download_link" + extract_dotnet_package "$zip_path" "$install_root" + + # Check if the SDK version is installed; if not, fail the installation. + # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed. + if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then + IFS='-' + read -ra verArr <<< "$specific_version" + release_version="${verArr[0]}" + unset IFS; + say_verbose "Checking installation: version = $release_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then + return 0 + fi + fi + + # Check if the standard SDK version is installed. + say_verbose "Checking installation: version = $specific_product_version" + if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$specific_product_version"; then + return 0 + fi + + say_err "\`$asset_name\` with version = $specific_product_version failed to install with an unknown error." + return 1 +} + +args=("$@") + +local_version_file_relative_path="/.version" +bin_folder_relative_path="" +temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" + +channel="LTS" +version="Latest" +json_file="" +install_dir="" +architecture="" +dry_run=false +no_path=false +no_cdn=false +azure_feed="https://dotnetcli.azureedge.net/dotnet" +uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" +feed_credential="" +verbose=false +runtime="" +runtime_id="" +override_non_versioned_files=true +non_dynamic_parameters="" + +while [ $# -ne 0 ] +do + name="$1" + case "$name" in + -c|--channel|-[Cc]hannel) + shift + channel="$1" + ;; + -v|--version|-[Vv]ersion) + shift + version="$1" + ;; + -i|--install-dir|-[Ii]nstall[Dd]ir) + shift + install_dir="$1" + ;; + --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) + shift + architecture="$1" + ;; + --shared-runtime|-[Ss]hared[Rr]untime) + say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'." + if [ -z "$runtime" ]; then + runtime="dotnet" + fi + ;; + --runtime|-[Rr]untime) + shift + runtime="$1" + if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then + say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'." + if [[ "$runtime" == "windowsdesktop" ]]; then + say_err "WindowsDesktop archives are manufactured for Windows platforms only." + fi + exit 1 + fi + ;; + --dry-run|-[Dd]ry[Rr]un) + dry_run=true + ;; + --no-path|-[Nn]o[Pp]ath) + no_path=true + non_dynamic_parameters+=" $name" + ;; + --verbose|-[Vv]erbose) + verbose=true + non_dynamic_parameters+=" $name" + ;; + --no-cdn|-[Nn]o[Cc]dn) + no_cdn=true + non_dynamic_parameters+=" $name" + ;; + --azure-feed|-[Aa]zure[Ff]eed) + shift + azure_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --uncached-feed|-[Uu]ncached[Ff]eed) + shift + uncached_feed="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --feed-credential|-[Ff]eed[Cc]redential) + shift + feed_credential="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --runtime-id|-[Rr]untime[Ii]d) + shift + runtime_id="$1" + non_dynamic_parameters+=" $name "\""$1"\""" + ;; + --jsonfile|-[Jj][Ss]on[Ff]ile) + shift + json_file="$1" + ;; + --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles) + override_non_versioned_files=false + non_dynamic_parameters+=" $name" + ;; + -?|--?|-h|--help|-[Hh]elp) + script_name="$(basename "$0")" + echo ".NET Tools Installer" + echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" + echo " $script_name -h|-?|--help" + echo "" + echo "$script_name is a simple command line interface for obtaining dotnet cli." + echo "" + echo "Options:" + echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`." + echo " -Channel" + echo " Possible values:" + echo " - Current - most current release" + echo " - LTS - most current supported release" + echo " - 2-part version in a format A.B - represents a specific release" + echo " examples: 2.0; 1.0" + echo " - Branch name" + echo " examples: release/2.0.0; Master" + echo " Note: The version parameter overrides the channel parameter." + echo " -v,--version Use specific VERSION, Defaults to \`$version\`." + echo " -Version" + echo " Possible values:" + echo " - latest - most latest build on specific channel" + echo " - coherent - most latest coherent build on specific channel" + echo " coherent applies only to SDK downloads" + echo " - 3-part version in a format A.B.C - represents specific version of build" + echo " examples: 2.0.0-preview2-006120; 1.1.0" + echo " -i,--install-dir Install under specified location (see Install Location below)" + echo " -InstallDir" + echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`." + echo " --arch,-Architecture,-Arch" + echo " Possible values: x64, arm, and arm64" + echo " --runtime Installs a shared runtime only, without the SDK." + echo " -Runtime" + echo " Possible values:" + echo " - dotnet - the Microsoft.NETCore.App shared runtime" + echo " - aspnetcore - the Microsoft.AspNetCore.App shared runtime" + echo " --dry-run,-DryRun Do not perform installation. Display download link." + echo " --no-path, -NoPath Do not set PATH for the current process." + echo " --verbose,-Verbose Display diagnostics information." + echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed, This parameter typically is not changed by the user." + echo " --uncached-feed,-UncachedFeed Uncached feed location. This parameter typically is not changed by the user." + echo " --feed-credential,-FeedCredential Azure feed shared access token. This parameter typically is not specified." + echo " --skip-non-versioned-files Skips non-versioned files if they already exist, such as the dotnet executable." + echo " -SkipNonVersionedFiles" + echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly." + echo " --jsonfile Determines the SDK version from a user specified global.json file." + echo " Note: global.json must have a value for 'SDK:Version'" + echo " --runtime-id Installs the .NET Tools for the given platform (use linux-x64 for portable linux)." + echo " -RuntimeId" + echo " -?,--?,-h,--help,-Help Shows this help message" + echo "" + echo "Obsolete parameters:" + echo " --shared-runtime The recommended alternative is '--runtime dotnet'." + echo " This parameter is obsolete and may be removed in a future version of this script." + echo " Installs just the shared runtime bits, not the entire SDK." + echo "" + echo "Install Location:" + echo " Location is chosen in following order:" + echo " - --install-dir option" + echo " - Environmental variable DOTNET_INSTALL_DIR" + echo " - $HOME/.dotnet" + exit 0 + ;; + *) + say_err "Unknown argument \`$name\`" + exit 1 + ;; + esac + + shift +done + +if [ "$no_cdn" = true ]; then + azure_feed="$uncached_feed" +fi + +say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" +say "- The SDK needs to be installed without user interaction and without admin rights." +say "- The SDK installation doesn't need to persist across multiple CI runs." +say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" + +check_min_reqs +calculate_vars +script_name=$(basename "$0") + +if [ "$dry_run" = true ]; then + say "Payload URLs:" + say "Primary named payload URL: $download_link" + if [ "$valid_legacy_download_link" = true ]; then + say "Legacy named payload URL: $legacy_download_link" + fi + repeatable_command="./$script_name --version "\""$specific_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\""" + if [[ "$runtime" == "dotnet" ]]; then + repeatable_command+=" --runtime "\""dotnet"\""" + elif [[ "$runtime" == "aspnetcore" ]]; then + repeatable_command+=" --runtime "\""aspnetcore"\""" + fi + repeatable_command+="$non_dynamic_parameters" + say "Repeatable invocation: $repeatable_command" + exit 0 +fi + +install_dotnet + +bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")" +if [ "$no_path" = false ]; then + say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script." + export PATH="$bin_path":"$PATH" +else + say "Binaries of dotnet can be found in $bin_path" +fi + +say "Note that the script does not resolve dependencies during installation." +say "To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." +say "Installation finished successfully."