From a2ebc49c803824ba838f21d3c4ed9896e00213a4 Mon Sep 17 00:00:00 2001 From: Lucas Teles Date: Wed, 26 Apr 2023 11:09:09 -0300 Subject: [PATCH] minor: add array linq extensions --- src/AssemblyExtensions.cs | 2 +- src/EnumerablePlus/LinqArrayExtensions.cs | 51 ++++++++++ src/EnumerablePlus/LinqEnumerablePlus.cs | 9 +- src/EnumerablePlus/Operators/Repeat.cs | 4 +- src/EnumerablePlus/Operators/Until.cs | 1 - src/EnumerablePlus/StringEnumerablePlus.cs | 1 + src/EnumerablePlus/TaskEnumerablePlus.cs | 2 + src/EnumerablePlus/TupleEnumerablePlus.cs | 4 +- ...numeration.cs => EnumerationExtensions.cs} | 2 +- src/ExpressionExtensions.cs | 17 +++- src/JsonConverters/Base/Converters.cs | 5 +- .../EnumerablePlus/LinqEnumerablePlusTests.cs | 50 +++++++++- .../ExpressionExtensionsTests.cs | 20 ++++ .../LinqArrayExtensionsTests.cs | 94 +++++++++++++++++++ tests/CSharpPlus.Tests/Usings.cs | 1 - tests/CSharpPlus.Tests/Utils/PropertyTest.cs | 6 ++ 16 files changed, 250 insertions(+), 19 deletions(-) create mode 100644 src/EnumerablePlus/LinqArrayExtensions.cs rename src/{Enumeration.cs => EnumerationExtensions.cs} (98%) create mode 100644 tests/CSharpPlus.Tests/ExpressionExtensionsTests.cs create mode 100644 tests/CSharpPlus.Tests/LinqArrayExtensionsTests.cs create mode 100644 tests/CSharpPlus.Tests/Utils/PropertyTest.cs diff --git a/src/AssemblyExtensions.cs b/src/AssemblyExtensions.cs index 70df1a3..8c406a1 100644 --- a/src/AssemblyExtensions.cs +++ b/src/AssemblyExtensions.cs @@ -40,6 +40,6 @@ public static IEnumerable InstantiateAllImplementations(this Assembly asse ? Activator.CreateInstance(t) : t.GetConstructor(Type.EmptyTypes) ?.Invoke(Array.Empty())) - .NotNullOnly() + .WhereNotNull() .Cast(); } diff --git a/src/EnumerablePlus/LinqArrayExtensions.cs b/src/EnumerablePlus/LinqArrayExtensions.cs new file mode 100644 index 0000000..93ef202 --- /dev/null +++ b/src/EnumerablePlus/LinqArrayExtensions.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; + +namespace System.Linq; + +/// +/// Enumerable Plus Extensions +/// +public static partial class EnumerablePlus +{ + /// + /// Filters an array of values based on a predicate. + /// + public static TSource[] + WhereArray(this TSource[] @this, Func predicate) => + Array.FindAll(@this, new Predicate(predicate)); + + /// + /// Sorts the elements of an array in ascending order according to a key. + /// + public static TSource[] + OrderByArray(this TSource[] @this, Func selector) + where TKey : IComparable + { + var copy = new TSource[@this.Length]; + Array.Copy(@this, copy, @this.Length); + Array.Sort(copy, KeyComparison(selector)); + return copy; + } + + /// + /// Sorts the elements of an array in ascending order. + /// + public static TSource[] OrderArray(this TSource[] @this) + { + var copy = new TSource[@this.Length]; + Array.Copy(@this, copy, @this.Length); + Array.Sort(copy); + return copy; + } + + /// + /// Projects each element of an array into a new form. + /// + public static TResult[] + SelectArray(this TSource[] @this, Func selector) => + Array.ConvertAll(@this, new Converter(selector)); + + static Comparison KeyComparison(Func key) + where TKey : IComparable => + (x, y) => Comparer.Default.Compare(key(x), key(y)); +} diff --git a/src/EnumerablePlus/LinqEnumerablePlus.cs b/src/EnumerablePlus/LinqEnumerablePlus.cs index 1718d6c..f86fb09 100644 --- a/src/EnumerablePlus/LinqEnumerablePlus.cs +++ b/src/EnumerablePlus/LinqEnumerablePlus.cs @@ -1,6 +1,6 @@ -using System; using System.Collections.Generic; -using System.Linq; + +namespace System.Linq; /// /// Enumerable Plus Extensions @@ -69,7 +69,7 @@ public static (T? Min, T? Max) MinMax(this IEnumerable @this) /// /// /// - public static IEnumerable NotNullOnly(this IEnumerable enumerable) where T : class => + public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : class => enumerable.Where(e => e is not null).Cast(); /// @@ -78,11 +78,10 @@ public static (T? Min, T? Max) MinMax(this IEnumerable @this) /// /// /// - public static IEnumerable NotNullOnly(this IEnumerable enumerable) + public static IEnumerable WhereNotNull(this IEnumerable enumerable) where T : struct => enumerable.Where(e => e is not null).Select(e => e!.Value); - /// /// Enumerate the enumerable to a collection of value tuples (int Index, T Value) /// diff --git a/src/EnumerablePlus/Operators/Repeat.cs b/src/EnumerablePlus/Operators/Repeat.cs index 66be1fb..8ce38b1 100644 --- a/src/EnumerablePlus/Operators/Repeat.cs +++ b/src/EnumerablePlus/Operators/Repeat.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; +/// +/// Enumerable Plus Extensions +/// public static partial class EnumerablePlus { /// diff --git a/src/EnumerablePlus/Operators/Until.cs b/src/EnumerablePlus/Operators/Until.cs index 4566ef9..b662725 100644 --- a/src/EnumerablePlus/Operators/Until.cs +++ b/src/EnumerablePlus/Operators/Until.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; public static partial class EnumerablePlus { diff --git a/src/EnumerablePlus/StringEnumerablePlus.cs b/src/EnumerablePlus/StringEnumerablePlus.cs index 42fd07e..9f8771e 100644 --- a/src/EnumerablePlus/StringEnumerablePlus.cs +++ b/src/EnumerablePlus/StringEnumerablePlus.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; +namespace System.Linq; /// /// Enumerable Plus Extensions /// diff --git a/src/EnumerablePlus/TaskEnumerablePlus.cs b/src/EnumerablePlus/TaskEnumerablePlus.cs index 027da6b..ea1d0b1 100644 --- a/src/EnumerablePlus/TaskEnumerablePlus.cs +++ b/src/EnumerablePlus/TaskEnumerablePlus.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Threading.Tasks; +namespace System.Linq; + /// /// Enumerable Plus Extensions /// diff --git a/src/EnumerablePlus/TupleEnumerablePlus.cs b/src/EnumerablePlus/TupleEnumerablePlus.cs index 925bff8..34a6969 100644 --- a/src/EnumerablePlus/TupleEnumerablePlus.cs +++ b/src/EnumerablePlus/TupleEnumerablePlus.cs @@ -1,6 +1,6 @@ -using System; using System.Collections.Generic; -using System.Linq; + +namespace System.Linq; /// /// Enumerable Plus Extensions diff --git a/src/Enumeration.cs b/src/EnumerationExtensions.cs similarity index 98% rename from src/Enumeration.cs rename to src/EnumerationExtensions.cs index f9a2401..4e3c6bd 100644 --- a/src/Enumeration.cs +++ b/src/EnumerationExtensions.cs @@ -6,7 +6,7 @@ /// /// Enum extensions /// -public static class Enumeration +public static class EnumerationExtensions { static TAttr? GetAttribute(this TEnum value) where TEnum : Enum diff --git a/src/ExpressionExtensions.cs b/src/ExpressionExtensions.cs index c1dd364..07d6a85 100644 --- a/src/ExpressionExtensions.cs +++ b/src/ExpressionExtensions.cs @@ -9,17 +9,28 @@ namespace CSharpPlus; public static class ExpressionExtensions { /// - /// Get the member name of an expression + /// Get the member name of an expression if it is valid otherwise return null /// /// /// /// /// - public static string GetMemberName(this Expression expression) => + public static string? GetMemberNameOrNull(this Expression expression) => expression.Body switch { MemberExpression m => m.Member.Name, UnaryExpression { Operand: MemberExpression m } => m.Member.Name, - _ => throw new NotImplementedException(expression.GetType().ToString()) + _ => null, }; + + /// + /// Get the member name of an expression + /// + /// + /// + /// + /// + public static string GetMemberName(this Expression expression) => + expression.GetMemberNameOrNull() ?? + throw new NotImplementedException(expression.GetType().ToString()); } diff --git a/src/JsonConverters/Base/Converters.cs b/src/JsonConverters/Base/Converters.cs index bb75146..18aeea9 100644 --- a/src/JsonConverters/Base/Converters.cs +++ b/src/JsonConverters/Base/Converters.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; namespace CSharpPlus.JsonConverters.Base; @@ -14,7 +13,7 @@ class JsonEnumMemberValueConverter : JsonEnumCustomStringConverter protected override string GetCustomString(TEnum value) => value.GetEnumMemberValue(); protected override TEnum? GetValueFromString(string value, StringComparison comparison) => - Enumeration.GetEnumFromEnumMemberValue(value, comparison); + EnumerationExtensions.GetEnumFromEnumMemberValue(value, comparison); } class JsonEnumDescriptionConverter : JsonEnumCustomStringConverter @@ -28,7 +27,7 @@ class JsonEnumDescriptionConverter : JsonEnumCustomStringConverter protected override string GetCustomString(TEnum value) => value.GetDescription(); protected override TEnum? GetValueFromString(string value, StringComparison comparison) => - Enumeration.GetEnumFromDescription(value, comparison); + EnumerationExtensions.GetEnumFromDescription(value, comparison); } class JsonEnumNumericAsStringConverter : JsonEnumCustomStringConverter diff --git a/tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs b/tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs index 40b55fa..b95a39a 100644 --- a/tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs +++ b/tests/CSharpPlus.Tests/EnumerablePlus/LinqEnumerablePlusTests.cs @@ -1,12 +1,24 @@ // ReSharper disable PossibleMultipleEnumeration -using System.ComponentModel.DataAnnotations; using FsCheck; namespace CSharpPlus.Tests.EnumerablePlus; public class LinqEnumerablePlusTests : BaseTest { + [Test] + public void StringJoinSampleTest() + { + var collection = new[] + { + 4, 8, 15, 16, 23, 42, + }; + + const string expected = "4;8;15;16;23;42"; + + collection.JoinString(";").Should().Be(expected); + } + [PropertyTest] public void StringJoinChar(string[] value, char chr) => value.JoinString(chr).Should().Be(string.Join(chr, value)); @@ -129,4 +141,40 @@ public void RepeatForever(NonEmptyArray array, PositiveInt times) sut.Should().AllSatisfy(v => v.First.Should().Be(v.Second)); } + + record TestRefType(string Id); + + [Test] + public void FilterNullRefTypes() + { + TestRefType?[] items = + { + new("A"), null, new("B"), null, new("C"), null, null, new("D"), + }; + + TestRefType[] expected = + { + new("A"), new("B"), new("C"), new("D"), + }; + + items.WhereNotNull().Should().BeEquivalentTo(expected); + } + + record struct TestValueType(string Id); + + [Test] + public void FilterNullValueTypes() + { + TestValueType?[] items = + { + new("A"), null, new("B"), null, new("C"), null, null, new("D"), + }; + + TestValueType[] expected = + { + new("A"), new("B"), new("C"), new("D"), + }; + + items.WhereNotNull().Should().BeEquivalentTo(expected); + } } diff --git a/tests/CSharpPlus.Tests/ExpressionExtensionsTests.cs b/tests/CSharpPlus.Tests/ExpressionExtensionsTests.cs new file mode 100644 index 0000000..35b059c --- /dev/null +++ b/tests/CSharpPlus.Tests/ExpressionExtensionsTests.cs @@ -0,0 +1,20 @@ +using System.Linq.Expressions; + +namespace CSharpPlus.Tests; + +public class ExpressionExtensionsTests +{ + record ClassWithMembers(int Foo, string Bar); + + [Test] + public void ShouldGetClassMemberName() + { + Expression> bar = x => x.Bar; + Expression> foo = x => x.Foo; + Expression> length = x => x.Bar.Length; + + bar.GetMemberName().Should().Be(nameof(ClassWithMembers.Bar)); + foo.GetMemberName().Should().Be(nameof(ClassWithMembers.Foo)); + length.GetMemberName().Should().Be(nameof(string.Length)); + } +} diff --git a/tests/CSharpPlus.Tests/LinqArrayExtensionsTests.cs b/tests/CSharpPlus.Tests/LinqArrayExtensionsTests.cs new file mode 100644 index 0000000..95f72be --- /dev/null +++ b/tests/CSharpPlus.Tests/LinqArrayExtensionsTests.cs @@ -0,0 +1,94 @@ +namespace CSharpPlus.Tests; + +public class LinqArrayExtensionsTests +{ + [Test] + public void ShouldFilterSampleArray() + { + int[] items = + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + }; + + int[] expected = + { + 2, 4, 6, 8, 10, 11, 12, 13, + }; + + items.WhereArray(x => x % 2 == 0 || x.ToString().Length is 2) + .Should().BeEquivalentTo(expected); + } + + [PropertyTest] + public void ShouldFilterArray(int[] items, Func pred) + { + var expected = items.Where(pred).ToArray(); + items.WhereArray(pred).Should().BeEquivalentTo(expected); + } + + [Test] + public void ShouldMapSampleTest() + { + int[] items = + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }; + + string[] expected = + { + "odd[1]", + "even[2]", + "odd[3]", + "even[4]", + "odd[5]", + "even[6]", + "odd[7]", + "even[8]", + "odd[9]", + "even[10]", + }; + + items.SelectArray(x => $"{(x % 2 == 0 ? "even" : "odd")}[{x}]") + .Should().BeEquivalentTo(expected); + } + + [PropertyTest] + public void ShouldMapArray(int[] items) + { + string Selector(int x) => $"{(x % 2 == 0 ? "even" : "odd")}[{x}]"; + var expected = items.Select(Selector).ToArray(); + items.SelectArray(Selector).Should().BeEquivalentTo(expected); + } + + + [Test] + public void ShouldOrderSampleArray() + { + int[] items = + { + 10, 5, 3, 8, 2, 4, 9, 6, 1, 7, + }; + + int[] expected = + { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + }; + + items.OrderArray() + .Should().BeEquivalentTo(expected); + } + + [PropertyTest] + public void ShouldOrderArray(int[] items) + { + var expected = items.OrderBy(x => x).ToArray(); + items.OrderArray().Should().BeEquivalentTo(expected); + } + + [PropertyTest] + public void ShouldOrderByArray(int[] items, Func pred) + { + var expected = items.OrderBy(pred).ToArray(); + items.OrderByArray(pred).Should().BeEquivalentTo(expected); + } +} diff --git a/tests/CSharpPlus.Tests/Usings.cs b/tests/CSharpPlus.Tests/Usings.cs index a9b116f..aea6113 100644 --- a/tests/CSharpPlus.Tests/Usings.cs +++ b/tests/CSharpPlus.Tests/Usings.cs @@ -3,4 +3,3 @@ global using CSharpPlus.Tests.Utils; global using FluentAssertions; global using NUnit.Framework; -global using PropertyTestAttribute = FsCheck.NUnit.PropertyAttribute; diff --git a/tests/CSharpPlus.Tests/Utils/PropertyTest.cs b/tests/CSharpPlus.Tests/Utils/PropertyTest.cs new file mode 100644 index 0000000..28e4611 --- /dev/null +++ b/tests/CSharpPlus.Tests/Utils/PropertyTest.cs @@ -0,0 +1,6 @@ +namespace CSharpPlus.Tests.Utils; + +public sealed class PropertyTestAttribute : FsCheck.NUnit.PropertyAttribute +{ + public PropertyTestAttribute() => this.QuietOnSuccess = true; +}