diff --git a/docs/CodeDoc/Atc/Atc.Helpers.md b/docs/CodeDoc/Atc/Atc.Helpers.md index f5914457..0fed788a 100644 --- a/docs/CodeDoc/Atc/Atc.Helpers.md +++ b/docs/CodeDoc/Atc/Atc.Helpers.md @@ -974,6 +974,47 @@ Enumeration Helper: EnumHelper. >     `ignoreCase`  -  if set to true [ignore case].
> >Returns: If parsed successfully and defined as a valid enum value, the enum value is returned. Otherwise the default value is returned. +#### GetIndividualValues +>```csharp +>IList GetIndividualValues(bool includeDefault = True) +>``` +>Summary: Retrieves individual flag values from a enum. +> +>Parameters:
+>     `includeDefault`  -  Includes the default '0' value if true.
+> +>Returns: A list of individual values. +#### GetIndividualValuesByCombinedValueFromFlagEnum +>```csharp +>IList GetIndividualValuesByCombinedValueFromFlagEnum(T combinedValue, bool includeDefault = True) +>``` +>Summary: Extracts and returns individual flags from a combined flag value. +> +>Parameters:
+>     `combinedValue`  -  The aggregate value of flags.
+>     `includeDefault`  -  Includes the default '0' value if true.
+> +>Returns: A list of matching individual flags. +#### GetIndividualValuesFromEnum +>```csharp +>IList GetIndividualValuesFromEnum(bool includeDefault = True) +>``` +>Summary: Retrieves values from a regular (non-flag) enum. +> +>Parameters:
+>     `includeDefault`  -  Includes the default '0' value if true.
+> +>Returns: A list of enum values. +#### GetIndividualValuesFromFlagEnum +>```csharp +>IList GetIndividualValuesFromFlagEnum(bool includeDefault = True) +>``` +>Summary: Retrieves individual flag values from a flag-based enum. +> +>Parameters:
+>     `includeDefault`  -  Includes the default '0' value if true.
+> +>Returns: A list of individual flag values. #### GetName >```csharp >string GetName(Enum enumeration) @@ -1971,6 +2012,22 @@ TaskHelper. >     `taskToRun`  -  The task to run.
>     `timeout`  -  The timeout.
>     `cancellationToken`  -  The cancellation token.
+#### FireAndForget +>```csharp +>void FireAndForget(Action action) +>``` +>Summary: Executes the provided action on a background thread, ignoring its completion status. This method is intended for fire-and-forget scenarios where the action is non-critical and does not need to be awaited or monitored. +> +>Parameters:
+>     `action`  -  The action to execute asynchronously.
+#### FireAndForget +>```csharp +>void FireAndForget(Task task) +>``` +>Summary: Executes the provided action on a background thread, ignoring its completion status. This method is intended for fire-and-forget scenarios where the action is non-critical and does not need to be awaited or monitored. +> +>Parameters:
+>     `action`  -  The action to execute asynchronously.
#### RunSync >```csharp >void RunSync(Func func) diff --git a/docs/CodeDoc/Atc/IndexExtended.md b/docs/CodeDoc/Atc/IndexExtended.md index 2d4e4651..7e9b94e7 100644 --- a/docs/CodeDoc/Atc/IndexExtended.md +++ b/docs/CodeDoc/Atc/IndexExtended.md @@ -4502,6 +4502,10 @@ - ConvertEnumToDictionaryWithStringKey(Type enumType, DropDownFirstItemType dropDownFirstItemType = None, bool useDescriptionAttribute = False, bool includeDefault = True, SortDirectionType sortDirectionType = None, bool byFlagIncludeBase = True, bool byFlagIncludeCombined = True) - GetDescription(Enum enumeration) - GetEnumValue(string value, bool ignoreCase = True) + - GetIndividualValues(bool includeDefault = True) + - GetIndividualValuesByCombinedValueFromFlagEnum(T combinedValue, bool includeDefault = True) + - GetIndividualValuesFromEnum(bool includeDefault = True) + - GetIndividualValuesFromFlagEnum(bool includeDefault = True) - GetName(Enum enumeration) - GetValueFromDescription(string description) - [FileHelper](Atc.Helpers.md#filehelper) @@ -4663,6 +4667,8 @@ - [TaskHelper](Atc.Helpers.md#taskhelper) - Static Methods - Execute(Func<CancellationToken, Task<TResult>> taskToRun, TimeSpan timeout, CancellationToken cancellationToken = null) + - FireAndForget(Action action) + - FireAndForget(Task task) - RunSync(Func<Task> func) - RunSync(Func<Task<TResult>> func) - WhenAll(IEnumerable<Task> tasks) @@ -5146,6 +5152,7 @@ - [SwitchCaseDefaultException](System.md#switchcasedefaultexception) - [TaskExtensions](System.md#taskextensions) - Static Methods + - Forget(this Task task) - StartAndWaitAllThrottled(this IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = null) - StartAndWaitAllThrottled(this IEnumerable<Task> tasksToRun, int maxTasksToRunInParallel, int timeoutInMilliseconds, CancellationToken cancellationToken = null) - [TcpException](System.md#tcpexception) diff --git a/docs/CodeDoc/Atc/System.md b/docs/CodeDoc/Atc/System.md index 547e9ac3..7b7b6e0d 100644 --- a/docs/CodeDoc/Atc/System.md +++ b/docs/CodeDoc/Atc/System.md @@ -2287,6 +2287,14 @@ Extensions for the `System.Threading.Tasks.Task` class. ### Static Methods +#### Forget +>```csharp +>void Forget(this Task task) +>``` +>Summary: Marks the provided task as 'forgotten', meaning its completion is intentionally unobserved. This method is used to explicitly denote that a task's result or exception is to be ignored. It should be used with caution, primarily in fire-and-forget scenarios where task exceptions are handled separately. +> +>Parameters:
+>     `task`  -  The task to be forgotten.
#### StartAndWaitAllThrottled >```csharp >void StartAndWaitAllThrottled(this IEnumerable tasksToRun, int maxTasksToRunInParallel, CancellationToken cancellationToken = null) diff --git a/src/Atc/Extensions/TaskExtensions.cs b/src/Atc/Extensions/TaskExtensions.cs index cc084627..6e3bca23 100644 --- a/src/Atc/Extensions/TaskExtensions.cs +++ b/src/Atc/Extensions/TaskExtensions.cs @@ -69,4 +69,39 @@ public static void StartAndWaitAllThrottled(this IEnumerable tasksToRun, i // before some Tasks have had their "post" action completed, which references the throttler, resulting in an exception due to accessing a disposed object. Task.WaitAll(postTaskTasks.ToArray(), cancellationToken); } + + /// + /// Marks the provided task as 'forgotten', meaning its completion is intentionally unobserved. + /// This method is used to explicitly denote that a task's result or exception is to be ignored. + /// It should be used with caution, primarily in fire-and-forget scenarios where task exceptions are handled separately. + /// + /// The task to be forgotten. + /// Thrown if the task is null. + public static void Forget( + this Task task) + { + if (task is null) + { + throw new ArgumentNullException(nameof(task)); + } + + if (!task.IsCompleted || task.IsFaulted) + { + _ = ForgetAwaited(task); + } + } + + [SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")] + private static async Task ForgetAwaited( + Task task) + { + try + { + await task.ConfigureAwait(false); + } + catch + { + // Nothing to do here + } + } } \ No newline at end of file diff --git a/src/Atc/Helpers/Enums/EnumHelper.cs b/src/Atc/Helpers/Enums/EnumHelper.cs index 2d3be796..9758cc18 100644 --- a/src/Atc/Helpers/Enums/EnumHelper.cs +++ b/src/Atc/Helpers/Enums/EnumHelper.cs @@ -1,5 +1,7 @@ // ReSharper disable UnreachableSwitchCaseDueToIntegerAnalysis // ReSharper disable once CheckNamespace +// ReSharper disable LoopCanBeConvertedToQuery +// ReSharper disable ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator namespace Atc.Helpers; /// @@ -398,7 +400,191 @@ public static Dictionary ConvertEnumToDictionaryWithStringKey( return orderList.ToDictionary(x => x.Key, x => x.Value, StringComparer.Ordinal); } - internal static bool ShouldEnumValueBeSkipped( + /// + /// Retrieves individual flag values from a enum. + /// + /// The enum type. + /// Includes the default '0' value if true. + /// A list of individual values. + /// Thrown if T is not an enum. + public static IList GetIndividualValues( + bool includeDefault = true) + where T : Enum + { + if (!typeof(T).IsEnum) + { + throw new InvalidOperationException("The generic type parameter must be an enum."); + } + + return typeof(T).GetCustomAttribute() is null + ? GetIndividualValuesFromEnum(includeDefault) + : GetIndividualValuesFromFlagEnum(includeDefault); + } + + /// + /// Retrieves individual flag values from a flag-based enum. + /// + /// The enum type with [Flags] attribute. + /// Includes the default '0' value if true. + /// A list of individual flag values. + /// Thrown if T is not an enum with the [Flags] attribute. + public static IList GetIndividualValuesFromFlagEnum( + bool includeDefault = true) + where T : Enum + { + if (!typeof(T).IsEnum || + typeof(T).GetCustomAttribute() is null) + { + throw new InvalidOperationException("The generic type parameter must be an enum with the [Flags] attribute."); + } + + var allValues = Enum + .GetValues(typeof(T)) + .Cast() + .ToList(); + + var individualValues = new List(); + foreach (var value in allValues) + { + var valueAsLong = Convert.ToInt64(value, GlobalizationConstants.EnglishCultureInfo); + if (!includeDefault && + valueAsLong == 0) + { + continue; + } + + var bitCount = CountBitsForEnumValue(valueAsLong); + if (includeDefault) + { + if (bitCount is 0 or 1) + { + individualValues.Add(value); + } + } + else + { + if (bitCount == 1) + { + individualValues.Add(value); + } + } + } + + return individualValues; + } + + /// + /// Extracts and returns individual flags from a combined flag value. + /// + /// The enum type with [Flags] attribute. + /// The aggregate value of flags. + /// Includes the default '0' value if true. + /// A list of matching individual flags. + /// Thrown if T is not an enum with the [Flags] attribute or the combined value is invalid. + public static IList GetIndividualValuesByCombinedValueFromFlagEnum( + T combinedValue, + bool includeDefault = true) + where T : Enum + { + if (!typeof(T).IsEnum || + typeof(T).GetCustomAttribute() is null) + { + throw new InvalidOperationException("The generic type parameter must be an enum with the [Flags] attribute."); + } + + var combinedValueAsLong = Convert.ToInt64(combinedValue, GlobalizationConstants.EnglishCultureInfo); + if (CountBitsForEnumValue(combinedValueAsLong) == 1) + { + throw new InvalidOperationException("The combined value is invalid."); + } + + var matchingValues = new List(); + foreach (T value in Enum.GetValues(typeof(T))) + { + var valueAsLong = Convert.ToInt64(value, GlobalizationConstants.EnglishCultureInfo); + if (!includeDefault && + valueAsLong == 0) + { + continue; + } + + var bitCount = CountBitsForEnumValue(valueAsLong); + if ((combinedValueAsLong & valueAsLong) != valueAsLong) + { + continue; + } + + if (includeDefault) + { + if (bitCount is 0 or 1) + { + matchingValues.Add(value); + } + } + else + { + if (bitCount == 1) + { + matchingValues.Add(value); + } + } + } + + return matchingValues; + } + + /// + /// Retrieves values from a regular (non-flag) enum. + /// + /// The enum type without the [Flags] attribute. + /// Includes the default '0' value if true. + /// A list of enum values. + /// Thrown if T is an enum with the [Flags] attribute. + public static IList GetIndividualValuesFromEnum( + bool includeDefault = true) + where T : Enum + { + if (!typeof(T).IsEnum || + typeof(T).GetCustomAttribute() is not null) + { + throw new InvalidOperationException("The generic type parameter must be an enum without the [Flags] attribute."); + } + + var allValues = Enum + .GetValues(typeof(T)) + .Cast() + .ToList(); + + var individualValues = new List(); + foreach (var value in allValues) + { + var valueAsLong = Convert.ToInt64(value, GlobalizationConstants.EnglishCultureInfo); + if (!includeDefault && + valueAsLong == 0) + { + continue; + } + + individualValues.Add(value); + } + + return individualValues; + } + + private static int CountBitsForEnumValue( + long value) + { + var count = 0; + while (value != 0) + { + count++; + value &= value - 1; // Clear the least significant bit set. + } + + return count; + } + + private static bool ShouldEnumValueBeSkipped( object objEnumValue, bool includeDefault, bool hasFlagAttribute, diff --git a/src/Atc/Helpers/TaskHelper.cs b/src/Atc/Helpers/TaskHelper.cs index 682de86d..88e2f125 100644 --- a/src/Atc/Helpers/TaskHelper.cs +++ b/src/Atc/Helpers/TaskHelper.cs @@ -169,4 +169,41 @@ public static TResult RunSync(Func> func) .GetAwaiter() .GetResult(); } + + /// + /// Executes the provided action on a background thread, ignoring its completion status. + /// This method is intended for fire-and-forget scenarios where the action is non-critical and does not need to be awaited or monitored. + /// + /// The action to execute asynchronously. + /// Thrown if the action is null. + [SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "OK.")] + public static void FireAndForget( + Action action) + { + if (action is null) + { + throw new ArgumentNullException(nameof(action)); + } + + Task.Run(action).Forget(); + } + + /// + /// Initiates the execution of a provided task and intentionally ignores its result or completion status. + /// This is a fire-and-forget utility method, used when the outcome of the task is not needed or is handled elsewhere. + /// It's primarily used for tasks where the result is not critical and does not need to be awaited or monitored. + /// + /// The task to execute and forget. + /// Thrown if the task is null. + [SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "OK.")] + public static void FireAndForget( + Task task) + { + if (task is null) + { + throw new ArgumentNullException(nameof(task)); + } + + task.Forget(); + } } \ No newline at end of file diff --git a/test/Atc.Tests/CodeComplianceTests.cs b/test/Atc.Tests/CodeComplianceTests.cs index 4ec3e5d9..820bc628 100644 --- a/test/Atc.Tests/CodeComplianceTests.cs +++ b/test/Atc.Tests/CodeComplianceTests.cs @@ -40,6 +40,7 @@ public class CodeComplianceTests // UnitTests are made, but CodeCompliance test cannot detect this typeof(DynamicJson), + typeof(EnumHelper), typeof(NumberHelper), typeof(InternetBrowserHelper), typeof(FileInfoExtensions), diff --git a/test/Atc.Tests/Helpers/Enums/EnumHelperTests.cs b/test/Atc.Tests/Helpers/Enums/EnumHelperTests.cs index 49670ba0..fffe8dc5 100644 --- a/test/Atc.Tests/Helpers/Enums/EnumHelperTests.cs +++ b/test/Atc.Tests/Helpers/Enums/EnumHelperTests.cs @@ -160,4 +160,67 @@ public void ConvertEnumToDictionaryWithStringKey( // Assert actual.Should().NotBeNull().And.HaveCount(expectedCount); } + + [Theory] + [InlineData(16, false)] + [InlineData(17, true)] + public void GetIndividualValues_CardinalDirectionType( + int expected, + bool includeDefault) + { + // Act + var actual = EnumHelper.GetIndividualValues(includeDefault); + + // Assert + Assert.Equal(expected, actual.Count); + } + + [Theory] + [InlineData(16, false)] + [InlineData(17, true)] + public void GetIndividualValuesFromFlagEnum_CardinalDirectionType( + int expected, + bool includeDefault) + { + // Act + var actual = EnumHelper.GetIndividualValuesFromFlagEnum(includeDefault); + + // Assert + Assert.Equal(expected, actual.Count); + } + + [Theory] + [InlineData(4, false, CardinalDirectionType.Simple)] + [InlineData(5, true, CardinalDirectionType.Simple)] + [InlineData(8, false, CardinalDirectionType.Medium)] + [InlineData(9, true, CardinalDirectionType.Medium)] + [InlineData(16, false, CardinalDirectionType.Advanced)] + [InlineData(17, true, CardinalDirectionType.Advanced)] + public void GetIndividualValuesByCombinedValueFromFlagEnum_CardinalDirectionType( + int expected, + bool includeDefault, + CardinalDirectionType cardinalDirectionType) + { + // Act + var actual = EnumHelper.GetIndividualValuesByCombinedValueFromFlagEnum( + cardinalDirectionType, + includeDefault); + + // Assert + Assert.Equal(expected, actual.Count); + } + + [Theory] + [InlineData(6, false)] + [InlineData(7, true)] + public void GetIndividualValuesFromEnum_DayOfWeek( + int expected, + bool includeDefault) + { + // Act + var actual = EnumHelper.GetIndividualValuesFromEnum(includeDefault); + + // Assert + Assert.Equal(expected, actual.Count); + } } \ No newline at end of file diff --git a/test/Atc.Tests/Helpers/TaskHelperTests.cs b/test/Atc.Tests/Helpers/TaskHelperTests.cs index 2a11bebc..ea171a3d 100644 --- a/test/Atc.Tests/Helpers/TaskHelperTests.cs +++ b/test/Atc.Tests/Helpers/TaskHelperTests.cs @@ -1,3 +1,5 @@ +// ReSharper disable RedundantAssignment +// ReSharper disable NotAccessedVariable namespace Atc.Tests.Helpers; [SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "OK.")] @@ -142,6 +144,32 @@ public void RunSyncAndReturnResult() Assert.Equal(42, actual); } + [Fact] + [SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "OK.")] + [SuppressMessage("Major Code Smell", "S1854:Unused assignments should be removed", Justification = "OK.")] + [SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "OK.")] + public void FireAndForget_Action() + { + // Act + TaskHelper.FireAndForget(() => + { + var x = 0; + x++; + }); + } + + [Fact] + [SuppressMessage("Design", "CA1030:Use events where appropriate", Justification = "OK.")] + [SuppressMessage("Blocker Code Smell", "S2699:Tests should include assertions", Justification = "OK.")] + public void FireAndForget_Task() + { + // Arrange + var doSomethingTask = DoSomethingAndReturnResultAsync(); + + // Act + TaskHelper.FireAndForget(doSomethingTask); + } + private static Task DoSomethingAsync() { return Task.Delay(100);