diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..83a0df0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: Build and Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal \ No newline at end of file diff --git a/CSharpExtender.sln b/CSharpExtender.sln index 6aa96fb..c610db4 100644 --- a/CSharpExtender.sln +++ b/CSharpExtender.sln @@ -9,6 +9,20 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.CSharpExtender", "Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkTestBench", "BenchmarkTestBench\BenchmarkTestBench.csproj", "{1CF1DAEF-0755-4F06-89CD-6ABCF50A838C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + LICENSE.txt = LICENSE.txt + README.md = README.md + RELEASE_NOTES.md = RELEASE_NOTES.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A344A16F-0891-4C85-AEB9-3974D4606C22}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{7B81E5AC-5679-484F-8757-6C3DE84DACBF}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci.yml = .github\workflows\ci.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +45,10 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A344A16F-0891-4C85-AEB9-3974D4606C22} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {7B81E5AC-5679-484F-8757-6C3DE84DACBF} = {A344A16F-0891-4C85-AEB9-3974D4606C22} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1B310B14-9E81-497D-8284-40A19D9A89A4} EndGlobalSection diff --git a/CSharpExtender/ExtensionMethods/LinqExtensionMethods.cs b/CSharpExtender/ExtensionMethods/LinqExtensionMethods.cs index caaa10f..fbd27c3 100644 --- a/CSharpExtender/ExtensionMethods/LinqExtensionMethods.cs +++ b/CSharpExtender/ExtensionMethods/LinqExtensionMethods.cs @@ -1,5 +1,6 @@ -using System.Collections.Generic; +using CSharpExtender.Services; using System; +using System.Collections.Generic; using System.Linq; namespace CSharpExtender.ExtensionMethods; @@ -41,6 +42,19 @@ public static void ForEach(this IEnumerable elements, Action action) } } + /// + /// Returns a random element from the list. + /// + /// The type of elements in the list. + /// The list to pick a random element from. + /// A random element from the list, or default(T) if the list is empty. + public static T RandomElement(this List options) + { + return options.Count == 0 + ? default + : options[RngCreator.GetNumberBetween(0, options.Count - 1)]; + } + /// /// Checks if any element in the collection has a duplicate property value. /// Ignores default values. diff --git a/CSharpExtender/Services/RngCreator.cs b/CSharpExtender/Services/RngCreator.cs new file mode 100644 index 0000000..9c3c680 --- /dev/null +++ b/CSharpExtender/Services/RngCreator.cs @@ -0,0 +1,15 @@ +using System; +using System.Security.Cryptography; + +namespace CSharpExtender.Services; + +/// +/// Class to create cryptographically random numbers +/// +public static class RngCreator +{ + public static int GetNumberBetween(int minimumValue, int maximumValue) + { + return RandomNumberGenerator.GetInt32(minimumValue, maximumValue + 1); + } +} \ No newline at end of file diff --git a/README.md b/README.md index c1f4c8a..2c41f6a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Extension methods and classes I often find useful for C# development. +![Build Status](https://github.com/ScottLilly/CSharpExtender/actions/workflows/ci.yml/badge.svg) +[![NuGet](https://img.shields.io/nuget/v/ScottLilly.CSharpExtender)](https://www.nuget.org/packages/ScottLilly.CSharpExtender/) +[![NuGet Downloads](https://img.shields.io/nuget/dt/ScottLilly.CSharpExtender)](https://www.nuget.org/packages/ScottLilly.CSharpExtender/) + ## DataAnnotations Attribute classes to validate properties in models. @@ -74,7 +78,8 @@ This class provides LINQ-related extension methods in C#. - **`HasDuplicatePropertyValue`**: Checks if any objects have the same value in the specified property. - **`HasDuplicatePropertyValue`**: Checks if any objects have the same value in the specified string property. - **`None`**: Checks if none of the elements in the collection satisfy the provided condition. If no condition is provided, it checks if the collection is empty. - +- **`RandomElement`**: Returns a random element from the list. +- ### NumericExtensionMethods This class provides extension methods for numerical operations in C#. @@ -178,10 +183,16 @@ Base class that inherits from ObservableModel (to handle property change notific ## Service classes -Classes to handle common tasks. +Static classes to handle common tasks. ### CompositeRegexMatcher Accepts a list of regex patterns and checks if a string matches any of them. --**`MatchesAny`**: Checks if the string matches any of the regex patterns. \ No newline at end of file +-**`MatchesAny`**: Checks if the string matches any of the regex patterns. + +### RngCreator + +This class is designed to create cryptographically random numbers in C#. + +- **`GetNumberBetween`**: Generates a cryptographically random number between the specified minimum and maximum values. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md new file mode 100644 index 0000000..e03ff40 --- /dev/null +++ b/RELEASE_NOTES.md @@ -0,0 +1,8 @@ +# RELEASE NOTES + +## Version 3.0.0 (2025-??-??) +### Breaking Changes +* Updated to .NET 8.0 + +### Features +* Improved performance for string extension methods. \ No newline at end of file diff --git a/Test.CSharpExtender/ExtensionMethods/Test_LinqExtensionMethods.cs b/Test.CSharpExtender/ExtensionMethods/Test_LinqExtensionMethods.cs index 8ca7dee..f4da9cb 100644 --- a/Test.CSharpExtender/ExtensionMethods/Test_LinqExtensionMethods.cs +++ b/Test.CSharpExtender/ExtensionMethods/Test_LinqExtensionMethods.cs @@ -36,6 +36,22 @@ public void ForEach_IEnumerableAction_AppliesAction() Assert.Equal(15, sum); } + [Fact] + public void RandomElement_ReturnsElementInList() + { + var list = new List { 1, 2, 3, 4, 5 }; + var randomElement = list.RandomElement(); + Assert.Contains(randomElement, list); + } + + [Fact] + public void RandomElement_EmptyList_ReturnsDefault() + { + var list = new List(); + var randomElement = list.RandomElement(); + Assert.Equal(default, randomElement); + } + [Fact] public void DetectDuplicatePropertyValues() { diff --git a/Test.CSharpExtender/Services/Test_RngCreator.cs b/Test.CSharpExtender/Services/Test_RngCreator.cs new file mode 100644 index 0000000..fd9166f --- /dev/null +++ b/Test.CSharpExtender/Services/Test_RngCreator.cs @@ -0,0 +1,38 @@ +using CSharpExtender.Services; + +namespace Test.CSharpExtender.Services; + +public class Test_RngCreator +{ + [Fact] + public void TestGetNumberBetween() + { + int min = 2; + int max = 12; + bool has2 = false; + bool has12 = false; + + // Run 1000 times, to ensure we don't get a value outside of the range + for (int i = 0; i < 1000; i++) + { + int result = RngCreator.GetNumberBetween(min, max); + + // Verify that the result is between 2 and 12 + Assert.True(result >= min && result <= max); + + // Check if 2 and 12 were generated at least once + if (result == 2) + { + has2 = true; + } + else if (result == 12) + { + has12 = true; + } + } + + // Ensure that at least one 2 and one 12 were generated + Assert.True(has2); + Assert.True(has12); + } +} \ No newline at end of file