From ae75e712a5cb0ab5d532a7b81133dfcb62f2230d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 22 Jul 2024 20:48:01 +0200 Subject: [PATCH 1/4] Add Fork extension method. --- .../Extensions/EnumerableExtensions.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs index df10065d..b0f8dffa 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs @@ -404,4 +404,30 @@ public static Task InvokeFirstOrDefaultAsync( enumerable.FirstOrDefault() is { } item ? funcAsync(item) : Task.FromResult(default(TResult)); + + /// + /// Splits the provided into two. + /// + /// The original collection to be tested. + /// + /// Tests each item of . If returns , the + /// item is added to the left collection, otherwise added to the right collection. + /// + /// The type of the items in . + /// A tuple of two collections. Each item in is in one of them. + public static (IList Left, IList Right) Fork( + this IEnumerable enumerable, + Func leftPredicate) + { + var left = new List(); + var right = new List(); + + foreach (var item in enumerable) + { + var target = leftPredicate(item) ? left : right; + target.Add(item); + } + + return (left, right); + } } From 1b64bdd56ae0f8f2e7f612a6487b900fc0a90745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 22 Jul 2024 21:13:10 +0200 Subject: [PATCH 2/4] Mark EnumerableExtensions.cs with `#nullable enable`. --- .../Extensions/EnumerableExtensions.cs | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs index b0f8dffa..0062fa31 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs @@ -1,3 +1,5 @@ +#nullable enable + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -39,7 +41,7 @@ public static TAccumulate AggregateSeed( /// The action to perform before the first item's . /// The type of the items in . /// if the had at least one item. - public static bool ForEach(this IEnumerable source, Action action, Action beforeFirst = null) + public static bool ForEach(this IEnumerable source, Action action, Action? beforeFirst = null) { bool any = false; @@ -171,9 +173,9 @@ public static IList AsList(this IEnumerable collection) => public static IEnumerable SelectWhere( this IEnumerable collection, Func select, - Func where = null) + Func? where = null) { - foreach (var item in collection ?? []) + foreach (var item in collection) { var converted = select(item); if (where?.Invoke(converted) ?? converted is not null) yield return converted; @@ -192,6 +194,7 @@ public static Dictionary ToDictionaryOverwrite( this IEnumerable collection, Func keySelector, Func valueSelector) + where TKey : notnull { var dictionary = new Dictionary(); foreach (var item in collection) dictionary[keySelector(item)] = valueSelector(item); @@ -208,7 +211,8 @@ public static Dictionary ToDictionaryOverwrite( Justification = "This is the point of the method.")] public static Dictionary ToDictionaryOverwrite( this IEnumerable collection, - Func keySelector) => + Func keySelector) + where TKey : notnull => ToDictionaryOverwrite(collection, keySelector, item => item); /// @@ -221,7 +225,7 @@ public static Dictionary ToDictionaryOverwrite( /// after grouping. /// /// - public static IEnumerable Unique( + public static IEnumerable Unique( this IEnumerable collection, Func keySelector) => collection.GroupBy(keySelector).Select(group => group.FirstOrDefault()); @@ -230,7 +234,7 @@ public static IEnumerable Unique( /// Returns the without any duplicate items picking the first of each when sorting by /// . /// - public static IEnumerable Unique( + public static IEnumerable Unique( this IEnumerable collection, Func keySelector, Func orderBySelector) => @@ -242,7 +246,7 @@ public static IEnumerable Unique( /// Returns the without any duplicate items picking the last of each when sorting by /// . /// - public static IEnumerable UniqueDescending( + public static IEnumerable UniqueDescending( this IEnumerable collection, Func keySelector, Func orderBySelector) => @@ -254,11 +258,13 @@ public static IEnumerable UniqueDescending( /// Returns a string that joins the string collection. It excludes null or empty items if there are any. /// /// The concatenated texts if there are any nonempty, otherwise . - public static string JoinNotNullOrEmpty(this IEnumerable strings, string separator = ",") + public static string? JoinNotNullOrEmpty(this IEnumerable? strings, string separator = ",") { - var filteredStrings = strings?.Where(text => !string.IsNullOrWhiteSpace(text)).ToList(); + var filteredStrings = (strings ?? []) + .Where(text => !string.IsNullOrWhiteSpace(text)) + .ToList(); - return filteredStrings?.Count > 0 + return filteredStrings.Count > 0 ? string.Join(separator, filteredStrings) : null; } @@ -271,7 +277,7 @@ public static string JoinNotNullOrEmpty(this IEnumerable strings, string /// /// A new that concatenates all values with the provided. /// - public static string Join(this IEnumerable values, string separator = " ") => + public static string Join(this IEnumerable? values, string separator = " ") => string.Join(separator, values ?? []); /// @@ -293,14 +299,14 @@ public static IEnumerable WhereNot(this IEnumerable collection, Func if it's not , otherwise . /// - public static IEnumerable EmptyIfNull(this IEnumerable collection) => + public static IEnumerable EmptyIfNull(this IEnumerable? collection) => collection ?? []; /// /// Returns if it's not , otherwise . /// - public static IEnumerable EmptyIfNull(this T[] array) => + public static IEnumerable EmptyIfNull(this T[]? array) => array ?? []; /// @@ -333,7 +339,7 @@ public static IEnumerable Select( /// Similar to , but it checks if the types are correct first, and filters out /// the ones that couldn't be cast. The optional can filter the cast items. /// - public static IEnumerable CastWhere(this IEnumerable enumerable, Func predicate = null) + public static IEnumerable CastWhere(this IEnumerable enumerable, Func? predicate = null) { if (enumerable is IEnumerable alreadyCast) { @@ -342,7 +348,7 @@ public static IEnumerable CastWhere(this IEnumerable enumerable, Func Iterate(IEnumerable enumerable, Func predicate) + static IEnumerable Iterate(IEnumerable enumerable, Func? predicate) { foreach (var item in enumerable) { @@ -398,12 +404,12 @@ public static Task InvokeFirstOrCompletedAsync(this IEnumerable enumerable /// If the is not empty, invokes the on the first item /// and returns its result, otherwise returns for . /// - public static Task InvokeFirstOrDefaultAsync( + public static Task InvokeFirstOrDefaultAsync( this IEnumerable enumerable, - Func> funcAsync) => + Func> funcAsync) => enumerable.FirstOrDefault() is { } item ? funcAsync(item) - : Task.FromResult(default(TResult)); + : Task.FromResult(default(TResult?)); /// /// Splits the provided into two. From 66cffff223d91db7f9cdc5e99e702aa678e5e2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Mon, 22 Jul 2024 21:14:44 +0200 Subject: [PATCH 3/4] Fork's input can be null. --- .../Extensions/EnumerableExtensions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs index 0062fa31..251dad82 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs @@ -422,12 +422,14 @@ public static Task InvokeFirstOrCompletedAsync(this IEnumerable enumerable /// The type of the items in . /// A tuple of two collections. Each item in is in one of them. public static (IList Left, IList Right) Fork( - this IEnumerable enumerable, + this IEnumerable? enumerable, Func leftPredicate) { var left = new List(); var right = new List(); + if (enumerable is null) return (left, right); + foreach (var item in enumerable) { var target = leftPredicate(item) ? left : right; From 49a301993ad7b70f6072e9145a04d4e00311c2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A1ra=20El-Saig?= Date: Tue, 23 Jul 2024 11:14:58 +0200 Subject: [PATCH 4/4] Split SelectWhere into two methods for better nullable representation. --- .../Extensions/EnumerableExtensions.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs index 251dad82..a304a710 100644 --- a/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs +++ b/Lombiq.HelpfulLibraries.Common/Extensions/EnumerableExtensions.cs @@ -167,18 +167,31 @@ public static IList AsList(this IEnumerable collection) => /// /// Transforms the specified with the function and returns - /// the items that are not null. Or if the function is given then those that return with it. + /// the items that return when passed to the function. /// public static IEnumerable SelectWhere( this IEnumerable collection, Func select, - Func? where = null) + Func where) { foreach (var item in collection) { var converted = select(item); - if (where?.Invoke(converted) ?? converted is not null) yield return converted; + if (where.Invoke(converted)) yield return converted; + } + } + + /// + /// Transforms the specified with the function and returns + /// the items that are not null. + /// + public static IEnumerable SelectWhere(this IEnumerable collection, Func select) + where TOut : notnull + { + foreach (var item in collection) + { + var converted = select(item); + if (converted is not null) yield return converted; } }