diff --git a/.editorconfig b/.editorconfig index 6edce03..cb61c33 100644 --- a/.editorconfig +++ b/.editorconfig @@ -56,20 +56,20 @@ csharp_preserve_single_line_statements=true #Style - Code block preferences #prefer no curly braces if allowed -csharp_prefer_braces=false:suggestion +csharp_prefer_braces= false:suggestion #Style - expression bodied member options #prefer expression-bodied members for accessors -csharp_style_expression_bodied_accessors=true:suggestion +csharp_style_expression_bodied_accessors= true:suggestion #prefer block bodies for constructors -csharp_style_expression_bodied_constructors=false:suggestion +csharp_style_expression_bodied_constructors= false:suggestion #prefer expression-bodied members for indexers -csharp_style_expression_bodied_indexers=true:suggestion +csharp_style_expression_bodied_indexers= true:suggestion #prefer block bodies for methods -csharp_style_expression_bodied_methods=when_on_single_line +csharp_style_expression_bodied_methods= when_on_single_line:silent #prefer expression-bodied members for properties -csharp_style_expression_bodied_properties=true:suggestion +csharp_style_expression_bodied_properties= true:suggestion #Style - expression level options @@ -79,7 +79,7 @@ dotnet_style_predefined_type_for_member_access=true:suggestion #Style - Expression-level preferences #prefer default(T) over default -csharp_prefer_simple_default_expression=false:suggestion +csharp_prefer_simple_default_expression= false:suggestion #Style - implicit and explicit types @@ -130,3 +130,129 @@ dotnet_remove_unnecessary_suppression_exclusions=category: ReSharper # ReSharper properties resharper_space_within_single_line_array_initializer_braces=true + +[*.cs] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.async_method_should_be_pascal_case_async.severity = warning +dotnet_naming_rule.async_method_should_be_pascal_case_async.symbols = async_method +dotnet_naming_rule.async_method_should_be_pascal_case_async.style = pascal_case_async + +dotnet_naming_rule.method_should_be_pascal_case.severity = warning +dotnet_naming_rule.method_should_be_pascal_case.symbols = method +dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.async_method.applicable_kinds = method +dotnet_naming_symbols.async_method.applicable_accessibilities = * +dotnet_naming_symbols.async_method.required_modifiers = async + +dotnet_naming_symbols.method.applicable_kinds = method +dotnet_naming_symbols.method.applicable_accessibilities = public +dotnet_naming_symbols.method.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case_async.required_prefix = +dotnet_naming_style.pascal_case_async.required_suffix = Async +dotnet_naming_style.pascal_case_async.word_separator = +dotnet_naming_style.pascal_case_async.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = false:suggestion +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_indent_labels = one_less_than_current +csharp_space_around_binary_operators = before_and_after + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml new file mode 100644 index 0000000..bfaa243 --- /dev/null +++ b/.github/workflows/build-publish.yml @@ -0,0 +1,92 @@ +name: Build and Publish +on: [push, workflow_dispatch] + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: true + NUGET_DIR: ${{ github.workspace }}/nuget + +jobs: + unit_tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .Net + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Restore Nuget Packages + run: dotnet restore --locked-mode + + - name: Clean Debug + run: dotnet clean --configuration Debug + + - name: Build Debug + run: dotnet build --configuration Debug --no-restore --packages ./.nuget/packages + + - name: Unit Tests + run: dotnet test --configuration Debug --no-build --filter TestCategory=Unit + + build_publish: + if: ${{ startsWith(github.ref_name, 'prerelease') || startsWith(github.ref_name, 'release') }} + runs-on: ubuntu-latest + needs: [unit_tests] + steps: + - uses: actions/checkout@v4 + + - name: Setup .Net + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x + cache: true + cache-dependency-path: '**/packages.lock.json' + + - name: Setup Environment Variables + run: | + github_sha_hash=${{ github.sha }} + + git_hash="${github_sha_hash:0:7}" + + TZ=America/Denver + + printf -v jdate '%(%y%j%H%M)T' + + branch_name=${{github.ref_name}} + + values=(${branch_name//_/ }) + + branch=${values[0]} + version=${values[1]} + + if [ $branch == 'release' ]; then + branch_version="${version}+${git_hash}" + elif [ $branch == 'dev' ]; then + branch_version="9.0.0-pre.${jdate}+${git_hash}" + else + branch_version="${version}-pre.${jdate}+${git_hash}" + fi + + echo $branch_version + + echo "BRANCH_VERSION=${branch_version}" >> "$GITHUB_ENV" + + - name: Restore Nuget Packages + run: dotnet restore --locked-mode + + - name: Clean Release + run: dotnet clean --configuration Release + + - name: Build Release + run: dotnet build --configuration Release --no-restore -p:Version="$BRANCH_VERSION" -p:PublishRepositoryUrl=true + + - name: Package Nuget + run: dotnet pack --configuration Release --no-build --include-symbols -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -p:PackageVersion="$BRANCH_VERSION" --output "$NUGET_DIR" + + - name: Publish Nuget + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: dotnet nuget push "$NUGET_DIR\*.nupkg" -k "$NUGET_API_KEY" diff --git a/Jenkinsfile b/Jenkinsfile.old similarity index 100% rename from Jenkinsfile rename to Jenkinsfile.old diff --git a/STR.Common.sln b/STR.Common.sln index 29d7604..838c6de 100644 --- a/STR.Common.sln +++ b/STR.Common.sln @@ -1,21 +1,28 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.156 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35828.75 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Str.Common", "Str.Common\Str.Common.csproj", "{5E07704B-4938-4D5F-A695-F87E016AC7BF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A51EE79F-F52E-4DDD-9FBB-F0A5BE4EC2DE}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - Jenkinsfile = Jenkinsfile LICENSE = LICENSE + Jenkinsfile.old = Jenkinsfile.old ReadMe.md = ReadMe.md STR.Common.sln.DotSettings = STR.Common.sln.DotSettings EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Str.Common.Tests", "Str.Common.Tests\Str.Common.Tests.csproj", "{4FB30997-A73F-485F-BC45-62B6D3B8944C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{9E962354-5E96-438D-B5CE-E6BFD20D2152}" + ProjectSection(SolutionItems) = preProject + .github\workflows\build-publish.yml = .github\workflows\build-publish.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +41,10 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {A51EE79F-F52E-4DDD-9FBB-F0A5BE4EC2DE} + {9E962354-5E96-438D-B5CE-E6BFD20D2152} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0157AB65-53CD-4464-B113-6D50BE77B2FA} EndGlobalSection diff --git a/STR.Common.sln.DotSettings b/STR.Common.sln.DotSettings index ef24329..748361e 100644 --- a/STR.Common.sln.DotSettings +++ b/STR.Common.sln.DotSettings @@ -52,8 +52,12 @@ UseExplicitType <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> BOTH_SIDES True True True - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/Str.Common.Tests/LockingObservableCollectionTests.cs b/Str.Common.Tests/LockingObservableCollectionTests.cs index 2ea318c..72594d9 100644 --- a/Str.Common.Tests/LockingObservableCollectionTests.cs +++ b/Str.Common.Tests/LockingObservableCollectionTests.cs @@ -11,156 +11,156 @@ namespace Str.Common.Tests; [TestClass] public class LockingObservableCollectionTests { - #region OnCollectionChanged Tests + #region OnCollectionChanged Tests - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedAddEventTest() { - TestClass tester = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedAddEventTest() { + TestClass tester = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Add) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Add) ++changedCount; + }; - testCollection.Add(tester); + testCollection.Add(tester); - Assert.AreEqual(1, changedCount); - } + Assert.AreEqual(1, changedCount); + } - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedRemoveEventTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedRemoveEventTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Remove) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Remove) ++changedCount; + }; - testCollection.Add(tester1); - testCollection.Add(tester2); + testCollection.Add(tester1); + testCollection.Add(tester2); - testCollection.Remove(tester1); + testCollection.Remove(tester1); - Assert.AreEqual(1, changedCount); - } + Assert.AreEqual(1, changedCount); + } - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedResetEventTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedResetEventTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Reset) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Reset) ++changedCount; + }; - testCollection.Add(tester1); - testCollection.Add(tester2); + testCollection.Add(tester1); + testCollection.Add(tester2); - testCollection.Clear(); + testCollection.Clear(); - Assert.AreEqual(1, changedCount); - } + Assert.AreEqual(1, changedCount); + } - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedReplaceEventTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedReplaceEventTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Replace) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Replace) ++changedCount; + }; - testCollection.Add(tester1); + testCollection.Add(tester1); - testCollection[0] = tester2; + testCollection[0] = tester2; - Assert.AreEqual(1, changedCount); - } + Assert.AreEqual(1, changedCount); + } - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedMoveEventTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedMoveEventTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Move) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Move) ++changedCount; + }; - testCollection.Add(tester1); - testCollection.Add(tester2); + testCollection.Add(tester1); + testCollection.Add(tester2); - testCollection.Move(0, 1); + testCollection.Move(0, 1); - Assert.AreEqual(1, changedCount); - } + Assert.AreEqual(1, changedCount); + } - [TestMethod, TestCategory("Unit")] - public void OnCollectionChangedMoveEventSameIndexTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, TestCategory("Unit")] + public void OnCollectionChangedMoveEventSameIndexTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = []; + LockingObservableCollection testCollection = []; - int changedCount = 0; + int changedCount = 0; - testCollection.CollectionChanged += (_, args) => { - if (args.Action == NotifyCollectionChangedAction.Move) ++changedCount; - }; + testCollection.CollectionChanged += (_, args) => { + if (args.Action == NotifyCollectionChangedAction.Move) ++changedCount; + }; - testCollection.Add(tester1); - testCollection.Add(tester2); + testCollection.Add(tester1); + testCollection.Add(tester2); - testCollection.Move(1, 1); + testCollection.Move(1, 1); - Assert.AreEqual(0, changedCount); - } + Assert.AreEqual(0, changedCount); + } - [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException)), TestCategory("Unit")] - public void OnCollectionChangedMoveSourceOutOfRangeTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException)), TestCategory("Unit")] + public void OnCollectionChangedMoveSourceOutOfRangeTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = [ tester1, tester2 ]; + LockingObservableCollection testCollection = [tester1, tester2]; - testCollection.Move(2, 1); - } + testCollection.Move(2, 1); + } - [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException)), TestCategory("Unit")] - public void OnCollectionChangedMoveDestinationOutOfRangeTest() { - TestClass tester1 = new(); - TestClass tester2 = new(); + [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException)), TestCategory("Unit")] + public void OnCollectionChangedMoveDestinationOutOfRangeTest() { + TestClass tester1 = new(); + TestClass tester2 = new(); - LockingObservableCollection testCollection = [ tester1, tester2 ]; + LockingObservableCollection testCollection = [tester1, tester2]; - testCollection.Move(1, 2); - } + testCollection.Move(1, 2); + } - #endregion OnCollectionChanged Tests + #endregion OnCollectionChanged Tests - #region Private Class + #region Private Class - private class TestClass; + private class TestClass; - #endregion Private Class + #endregion Private Class } diff --git a/Str.Common.Tests/LockingReadOnlyCollectionTests.cs b/Str.Common.Tests/LockingReadOnlyCollectionTests.cs index 5275cbb..f770ee2 100644 --- a/Str.Common.Tests/LockingReadOnlyCollectionTests.cs +++ b/Str.Common.Tests/LockingReadOnlyCollectionTests.cs @@ -4,14 +4,14 @@ using Str.Common.Extensions; -namespace Str.Common.Tests; +namespace Str.Common.Tests; [TestClass] public class LockingReadOnlyCollectionTests { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionCount() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -20,7 +20,7 @@ public void LockingReadOnlyCollectionCount() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionContains() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -29,7 +29,7 @@ public void LockingReadOnlyCollectionContains() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionCopyTo() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -44,7 +44,7 @@ public void LockingReadOnlyCollectionCopyTo() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionIsReadOnly() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -53,7 +53,7 @@ public void LockingReadOnlyCollectionIsReadOnly() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionIsEnumerable() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -64,7 +64,7 @@ public void LockingReadOnlyCollectionIsEnumerable() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionIndexOf() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); @@ -75,7 +75,7 @@ public void LockingReadOnlyCollectionIndexOf() { [TestMethod, TestCategory("Unit")] public void LockingReadOnlyCollectionIndexer() { - List source = new() { 1, 2, 3 }; + List source = [1, 2, 3]; LockingReadOnlyCollection tester = source.ToLockingReadOnlyCollection(); diff --git a/Str.Common.Tests/Str.Common.Tests.csproj b/Str.Common.Tests/Str.Common.Tests.csproj index 6c8df6d..1ada754 100644 --- a/Str.Common.Tests/Str.Common.Tests.csproj +++ b/Str.Common.Tests/Str.Common.Tests.csproj @@ -1,12 +1,13 @@ - net8.0 + net9.0 false true enable + true @@ -15,10 +16,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Str.Common.Tests/TaskExtensionTests.cs b/Str.Common.Tests/TaskExtensionTests.cs index b376a7d..97378b5 100644 --- a/Str.Common.Tests/TaskExtensionTests.cs +++ b/Str.Common.Tests/TaskExtensionTests.cs @@ -3,62 +3,65 @@ using Str.Common.Extensions; -namespace Str.Common.Tests; +namespace Str.Common.Tests; [TestClass] public class TaskExtensionTests { - #region FireAndForget Tests + #region FireAndForget Tests - [TestMethod, TestCategory("Unit")] - public void FireAndForgetTaskNoActionSuccess() { - MethodAsync().FireAndForget(); - } + [TestMethod, TestCategory("Unit")] + public void FireAndForgetTaskNoActionSuccess() { + MethodAsync().FireAndForget(); + } - [TestMethod, TestCategory("Unit")] - public void FireAndForgetTaskNoActionException() - { - MethodAsync(true).FireAndForget(); // Exception is dropped on the floor - } + [TestMethod, TestCategory("Unit")] + public void FireAndForgetTaskNoActionException() { + MethodAsync(true).FireAndForget(); // Exception is dropped on the floor + } - [TestMethod, TestCategory("Unit")] - public async Task FireAndForgetTaskActionSuccess() { - int callbackCount = 0; + [TestMethod, TestCategory("Unit")] + public async Task FireAndForgetTaskActionSuccessAsync() { + int callbackCount = 0; - void callback(Exception ex) { ++callbackCount; } + MethodAsync().FireAndForget(Callback); - MethodAsync().FireAndForget(callback); + await Task.Delay(1000).Fire(); - await Task.Delay(1000).Fire(); + Assert.AreEqual(0, callbackCount); - Assert.AreEqual(0, callbackCount); - } + return; - [TestMethod, TestCategory("Unit")] - public async Task FireAndForgetTaskActionException() { - int callbackCount = 0; + void Callback(Exception ex) { ++callbackCount; } + } - void callback(Exception ex) { ++callbackCount; } + [TestMethod, TestCategory("Unit")] + public async Task FireAndForgetTaskActionExceptionAsync() { + int callbackCount = 0; - MethodAsync(true).FireAndForget(callback); + MethodAsync(true).FireAndForget(Callback); - await Task.Delay(1000).Fire(); + await Task.Delay(1000).Fire(); - Assert.AreEqual(1, callbackCount); - } + Assert.AreEqual(1, callbackCount); - #endregion FireAndForget Tests + return; - #region Private Methods + void Callback(Exception ex) { ++callbackCount; } + } - private static async Task MethodAsync(bool throwException = false) { - await Task.Delay(500).ConfigureAwait(false); + #endregion FireAndForget Tests - if (throwException) throw new Exception(); + #region Private Methods - await Task.CompletedTask.ConfigureAwait(false); - } + private static async Task MethodAsync(bool throwException = false) { + await Task.Delay(500).ConfigureAwait(false); - #endregion Private Methods + if (throwException) throw new Exception(); -} \ No newline at end of file + await Task.CompletedTask.ConfigureAwait(false); + } + + #endregion Private Methods + +} diff --git a/Str.Common.Tests/TraverseTests.cs b/Str.Common.Tests/TraverseTests.cs index 1c89782..18a391f 100644 --- a/Str.Common.Tests/TraverseTests.cs +++ b/Str.Common.Tests/TraverseTests.cs @@ -7,7 +7,8 @@ using Str.Common.Extensions; -namespace Str.Common.Tests; +namespace Str.Common.Tests; + [TestClass] public class TraverseTests { @@ -22,7 +23,7 @@ public class TraverseTests { [ClassInitialize] public static void ClassInit(TestContext _) { - root = new LockingObservableCollection(); + root = []; TestClass branch1 = new() { Value = 1 }; TestClass branch2 = new() { Value = 2 }; @@ -68,7 +69,7 @@ public void TraverseTestWithPredicate() { [TestMethod, TestCategory("Unit")] [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] public void TraverseTestEmptyTreeNoPredicate() { - LockingObservableCollection test = new(); + LockingObservableCollection test = []; IEnumerable flat = test.Traverse(); @@ -80,7 +81,7 @@ public void TraverseTestEmptyTreeNoPredicate() { [TestMethod, TestCategory("Unit")] [SuppressMessage("ReSharper", "CollectionNeverUpdated.Local")] public void TraverseTestEmptyTreeWithPredicate() { - LockingObservableCollection test = new(); + LockingObservableCollection test = []; IEnumerable flat = test.Traverse(tc => tc.Value == 1); @@ -93,12 +94,13 @@ public void TraverseTestEmptyTreeWithPredicate() { } + public class TestClass : ITraversable { - public int Value { get; set; } + public int Value { get; init; } - public LockingObservableCollection Children { get; } = new(); + public LockingObservableCollection Children { get; } = []; IEnumerable ITraversable.Children => Children; -} \ No newline at end of file +} diff --git a/Str.Common.Tests/packages.lock.json b/Str.Common.Tests/packages.lock.json new file mode 100644 index 0000000..8986c51 --- /dev/null +++ b/Str.Common.Tests/packages.lock.json @@ -0,0 +1,157 @@ +{ + "version": 1, + "dependencies": { + "net9.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "ConfigureAwaitEnforcer": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "jkoGjQWaD5ioGPCv/Oi/tsCEcGWfLgOHeN9IPrZbzS8epgbuBQREFlg0Oe7J6yRZuhxMyfJrSmdXtnhNgdmkLw==" + }, + "coverlet.collector": { + "type": "Direct", + "requested": "[6.0.4, )", + "resolved": "6.0.4", + "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.13.0, )", + "resolved": "17.13.0", + "contentHash": "W19wCPizaIC9Zh47w8wWI/yxuqR7/dtABwOrc8r2jX/8mUNxM2vw4fXDh+DJTeogxV+KzKwg5jNNGQVwf3LXyA==", + "dependencies": { + "Microsoft.CodeCoverage": "17.13.0", + "Microsoft.TestPlatform.TestHost": "17.13.0" + } + }, + "MSTest.TestAdapter": { + "type": "Direct", + "requested": "[3.8.2, )", + "resolved": "3.8.2", + "contentHash": "Xzch3LrRJKzIMP6D956W0DEy8NInkNSXS9novzEC72hMz8VlhDamRNDsR+b5QMvct+1TTIWRvw6cBHtiMB6Ajw==", + "dependencies": { + "Microsoft.Testing.Extensions.VSTestBridge": "1.6.2", + "Microsoft.Testing.Platform.MSBuild": "1.6.2" + } + }, + "MSTest.TestFramework": { + "type": "Direct", + "requested": "[3.8.2, )", + "resolved": "3.8.2", + "contentHash": "GE6TAA3yC6rYFZcUY7NprA4muVKtTCgoPwFPLu+Q0XgNjcIBa7C1O+hGT23mWwyiAyzVOH6G33pHsJS8mI2hqA==", + "dependencies": { + "MSTest.Analyzers": "3.8.2" + } + }, + "JetBrains.Annotations": { + "type": "Transitive", + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + }, + "Microsoft.ApplicationInsights": { + "type": "Transitive", + "resolved": "2.22.0", + "contentHash": "3AOM9bZtku7RQwHyMEY3tQMrHIgjcfRDa6YQpd/QG2LDGvMydSlL9Di+8LLMt7J2RDdfJ7/2jdYv6yHcMJAnNw==", + "dependencies": { + "System.Diagnostics.DiagnosticSource": "5.0.0" + } + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9LIUy0y+DvUmEPtbRDw6Bay3rzwqFV8P4efTrK4CZhQle3M/QwLPjISghfcolmEGAPWxuJi6m98ZEfk4VR4Lfg==" + }, + "Microsoft.Testing.Extensions.Telemetry": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "40oMlQzyey4jOihY0IpUufSoMYeijYgvrtIxuYmuVx1k5xl271XlP0gwD2DwAKnvmmP0cocou531d6/CB3cCIA==", + "dependencies": { + "Microsoft.ApplicationInsights": "2.22.0", + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Extensions.TrxReport.Abstractions": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "EE4PoYoRtrTKE0R22bXuBguVgdEeepImy0S8xHaZOcGz5AuahB2i+0CV4UTefLqO1dtbA4APfumpP1la+Yn3SA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Extensions.VSTestBridge": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "ZvYa+VDuk39EIqyOZ/IMFSRd/N54zFBnDFmDagFBJt21vZZnSG6l/3CkJX3DvmYmuf5Byj9w7Xf46mkWuur4LQ==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Microsoft.Testing.Extensions.Telemetry": "1.6.2", + "Microsoft.Testing.Extensions.TrxReport.Abstractions": "1.6.2", + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.Testing.Platform": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "7CFJKN3An5Ra6YOrTCAi7VldSRTxGGokqC0NSNrpKTKO6NJJby10EWwnqV/v2tawcRzfSbLpKNpvBv7s7ZoD3Q==" + }, + "Microsoft.Testing.Platform.MSBuild": { + "type": "Transitive", + "resolved": "1.6.2", + "contentHash": "tF5UgrXh0b0F8N11uWfaZT91v5QvuTZDwWP19GDMHPalWFKfmlix92xExo7cotJDoAK+bzljLK0S0XJuigYLbA==", + "dependencies": { + "Microsoft.Testing.Platform": "1.6.2" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "bt0E0Dx+iqW97o4A59RCmUmz/5NarJ7LRL+jXbSHod72ibL5XdNm1Ke+UO5tFhBG4VwHLcSjqq9BUSblGNWamw==", + "dependencies": { + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.13.0", + "contentHash": "9GGw08Dc3AXspjekdyTdZ/wYWFlxbgcF0s7BKxzVX+hzAwpifDOdxM+ceVaaJSQOwqt3jtuNlHn3XTpKUS9x9Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.13.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "MSTest.Analyzers": { + "type": "Transitive", + "resolved": "3.8.2", + "contentHash": "ODWteXvnMEgCoZl1vAi2lOFIFFJSZkyQoQB9AFwBEUrzgJpy5J4ml3jLye4n85TA7gd+Qg2eWtqkvyEunB7B0g==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "str.common": { + "type": "Project", + "dependencies": { + "ConfigureAwaitEnforcer": "[2.0.0, )", + "JetBrains.Annotations": "[2024.3.0, )" + } + } + } + } +} \ No newline at end of file diff --git a/Str.Common/Core/LockingCollection.cs b/Str.Common/Core/LockingCollection.cs index 609cab4..5d2907e 100644 --- a/Str.Common/Core/LockingCollection.cs +++ b/Str.Common/Core/LockingCollection.cs @@ -1,139 +1,143 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; -namespace Str.Common.Core; -[SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "This is a library.")] +namespace Str.Common.Core; + +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "VirtualMemberNeverOverridden.Global", Justification = "This is a library.")] -[SuppressMessage("ReSharper", "MemberCanBeProtected.Global", Justification = "This is a library.")] +[SuppressMessage("ReSharper", "MemberCanBeProtected.Global", Justification = "This is a library.")] public class LockingCollection : IList, IReadOnlyList { - #region Constructors + #region Constructors - public LockingCollection() { - Items = new LockingList(); - } + public LockingCollection() { + Items = []; + } - public LockingCollection(int capacity) { - Items = new LockingList(capacity); - } + public LockingCollection(int capacity) { + Items = new LockingList(capacity); + } - public LockingCollection(IEnumerable enumerable) { - Items = new LockingList(enumerable); - } + public LockingCollection(IEnumerable enumerable) { + Items = new LockingList(enumerable); + } - #endregion Constructors + #endregion Constructors - #region IList Implementation + #region IList Implementation - public T this[int index] { - get => Items[index]; - set { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public T this[int index] { + get => Items[index]; + set { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - if ((uint)index >= (uint)Items.Count) throw new IndexOutOfRangeException(); + if ((uint)index >= (uint)Items.Count) throw new IndexOutOfRangeException(); - SetItem(index, value); + SetItem(index, value); + } } - } - public int Count => Items.Count; + public int Count => Items.Count; - public void Add(T item) { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public void Add(T item) { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - int index = Items.Count; + int index = Items.Count; - InsertItem(index, item); - } + InsertItem(index, item); + } - public void Clear() { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public void Clear() { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - ClearItems(); - } + ClearItems(); + } - public void CopyTo(T[] array, int index) { - Items.CopyTo(array, index); - } + public void CopyTo(T[] array, int index) { + Items.CopyTo(array, index); + } - public bool Contains(T item) { - return Items.Contains(item); - } + public bool Contains(T item) { + return Items.Contains(item); + } - public IEnumerator GetEnumerator() { - return Items.GetEnumerator(); - } + [MustDisposeResource] + public IEnumerator GetEnumerator() { + return Items.GetEnumerator(); + } - public int IndexOf(T item) { - return Items.IndexOf(item); - } + public int IndexOf(T item) { + return Items.IndexOf(item); + } - public void Insert(int index, T item) { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public void Insert(int index, T item) { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - if ((uint) index >= (uint) Items.Count) throw new IndexOutOfRangeException(); + if ((uint)index >= (uint)Items.Count) throw new IndexOutOfRangeException(); - InsertItem(index, item); - } + InsertItem(index, item); + } - public bool Remove(T item) { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public bool Remove(T item) { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - int index = Items.IndexOf(item); + int index = Items.IndexOf(item); - if (index < 0) return false; + if (index < 0) return false; - RemoveItem(index); + RemoveItem(index); - return true; - } + return true; + } - public void RemoveAt(int index) { - if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); + public void RemoveAt(int index) { + if (Items.IsReadOnly) throw new NotSupportedException("Collection is read only."); - if ((uint) index >= (uint) Items.Count) throw new IndexOutOfRangeException(); + if ((uint)index >= (uint)Items.Count) throw new IndexOutOfRangeException(); - RemoveItem(index); - } + RemoveItem(index); + } - #endregion IList Implementation + #endregion IList Implementation - #region ICollection Implementation + #region ICollection Implementation - bool ICollection.IsReadOnly => Items.IsReadOnly; + bool ICollection.IsReadOnly => Items.IsReadOnly; - #endregion ICollection Implementation + #endregion ICollection Implementation - #region IEnumerable Implementation + #region IEnumerable Implementation - IEnumerator IEnumerable.GetEnumerator() { - return (Items as IEnumerable).GetEnumerator(); - } + [MustDisposeResource] + IEnumerator IEnumerable.GetEnumerator() { + return (Items as IEnumerable).GetEnumerator(); + } - #endregion IEnumerable Implementation + #endregion IEnumerable Implementation - #region Protected Methods + #region Protected Methods - protected IList Items { get; } + protected IList Items { get; } - protected virtual void SetItem(int index, T item) { - Items[index] = item; - } + protected virtual void SetItem(int index, T item) { + Items[index] = item; + } - protected virtual void RemoveItem(int index) { - Items.RemoveAt(index); - } + protected virtual void RemoveItem(int index) { + Items.RemoveAt(index); + } - protected virtual void InsertItem(int index, T item) { - Items.Insert(index, item); - } + protected virtual void InsertItem(int index, T item) { + Items.Insert(index, item); + } - protected virtual void ClearItems() { - Items.Clear(); - } + protected virtual void ClearItems() { + Items.Clear(); + } - #endregion Protected Methods + #endregion Protected Methods } \ No newline at end of file diff --git a/Str.Common/Core/LockingList.cs b/Str.Common/Core/LockingList.cs index f2eddd3..7332c79 100644 --- a/Str.Common/Core/LockingList.cs +++ b/Str.Common/Core/LockingList.cs @@ -1,6 +1,8 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; +using JetBrains.Annotations; + namespace Str.Common.Core; @@ -8,234 +10,235 @@ namespace Str.Common.Core; // // https://codereview.stackexchange.com/questions/7276/reader-writer-collection // -[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "This is a library.")] -[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is a library.")] +[SuppressMessage("ReSharper", "UnusedType.Global", Justification = "This is a library.")] +[SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "MemberCanBeProtected.Global", Justification = "This is a library.")] public sealed class LockingList : IList { - #region Private Fields + #region Private Fields - private readonly List inner; + private readonly List inner; - private readonly ReaderWriterLockSlim innerLock = new(LockRecursionPolicy.SupportsRecursion); + private readonly ReaderWriterLockSlim innerLock = new(LockRecursionPolicy.SupportsRecursion); - #endregion Private Fields + #endregion Private Fields - #region Constructors + #region Constructors - public LockingList() { - inner = new List(); - } + public LockingList() { + inner = []; + } - public LockingList(int capacity) { - inner = new List(capacity); - } + public LockingList(int capacity) { + inner = new List(capacity); + } - public LockingList(IEnumerable enumerable) { - inner = new List(enumerable); - } + public LockingList(IEnumerable enumerable) { + inner = [..enumerable]; + } - #endregion Constructors + #endregion Constructors - #region IList Implementation + #region IList Implementation - IEnumerator IEnumerable.GetEnumerator() { - innerLock.EnterReadLock(); + IEnumerator IEnumerable.GetEnumerator() { + innerLock.EnterReadLock(); - try { - return new LockingEnumerator(inner.GetEnumerator(), innerLock); - } - finally { - innerLock.ExitReadLock(); + try { + return new LockingEnumerator(inner.GetEnumerator(), innerLock); + } + finally { + innerLock.ExitReadLock(); + } } - } - public IEnumerator GetEnumerator() { - return (this as IEnumerable).GetEnumerator(); - } - - public void Add(T item) { - innerLock.EnterWriteLock(); - - try { - inner.Add(item); - } - finally { - innerLock.ExitWriteLock(); + [MustDisposeResource] + public IEnumerator GetEnumerator() { + return (this as IEnumerable).GetEnumerator(); } - } - public void Clear() { - innerLock.EnterWriteLock(); + public void Add(T item) { + innerLock.EnterWriteLock(); - try { - inner.Clear(); + try { + inner.Add(item); + } + finally { + innerLock.ExitWriteLock(); + } } - finally { - innerLock.ExitWriteLock(); - } - } - public bool Contains(T item) { - innerLock.EnterReadLock(); + public void Clear() { + innerLock.EnterWriteLock(); - try { - return inner.Contains(item); - } - finally { - innerLock.ExitReadLock(); + try { + inner.Clear(); + } + finally { + innerLock.ExitWriteLock(); + } } - } - public void CopyTo(T[] array, int arrayIndex) { - innerLock.EnterReadLock(); + public bool Contains(T item) { + innerLock.EnterReadLock(); - try { - inner.CopyTo(array, arrayIndex); + try { + return inner.Contains(item); + } + finally { + innerLock.ExitReadLock(); + } } - finally { - innerLock.ExitReadLock(); - } - } - public bool Remove(T item) { - innerLock.EnterWriteLock(); + public void CopyTo(T[] array, int arrayIndex) { + innerLock.EnterReadLock(); - try { - return inner.Remove(item); - } - finally { - innerLock.ExitWriteLock(); + try { + inner.CopyTo(array, arrayIndex); + } + finally { + innerLock.ExitReadLock(); + } } - } - public int Count { - get { - innerLock.EnterReadLock(); + public bool Remove(T item) { + innerLock.EnterWriteLock(); - try { - return inner.Count; - } - finally { - innerLock.ExitReadLock(); - } + try { + return inner.Remove(item); + } + finally { + innerLock.ExitWriteLock(); + } } - } - public bool IsReadOnly { - get { - innerLock.EnterReadLock(); + public int Count { + get { + innerLock.EnterReadLock(); - try { - return (inner as ICollection).IsReadOnly; - } - finally { - innerLock.ExitReadLock(); - } + try { + return inner.Count; + } + finally { + innerLock.ExitReadLock(); + } + } } - } - public int IndexOf(T item) { - innerLock.EnterReadLock(); + public bool IsReadOnly { + get { + innerLock.EnterReadLock(); - try { - return inner.IndexOf(item); - } - finally { - innerLock.ExitReadLock(); + try { + return (inner as ICollection).IsReadOnly; + } + finally { + innerLock.ExitReadLock(); + } + } } - } - public void Insert(int index, T item) { - innerLock.EnterWriteLock(); + public int IndexOf(T item) { + innerLock.EnterReadLock(); - try { - inner.Insert(index, item); + try { + return inner.IndexOf(item); + } + finally { + innerLock.ExitReadLock(); + } } - finally { - innerLock.ExitWriteLock(); - } - } - public void RemoveAt(int index) { - innerLock.EnterWriteLock(); + public void Insert(int index, T item) { + innerLock.EnterWriteLock(); - try { - inner.RemoveAt(index); - } - finally { - innerLock.ExitWriteLock(); + try { + inner.Insert(index, item); + } + finally { + innerLock.ExitWriteLock(); + } } - } - public T this[int index] { - get { - innerLock.EnterReadLock(); + public void RemoveAt(int index) { + innerLock.EnterWriteLock(); - try { - return inner[index]; - } - finally { - innerLock.ExitReadLock(); - } + try { + inner.RemoveAt(index); + } + finally { + innerLock.ExitWriteLock(); + } } - set { - innerLock.EnterWriteLock(); - try { - inner[index] = value; - } - finally { - innerLock.ExitWriteLock(); - } - } - } + public T this[int index] { + get { + innerLock.EnterReadLock(); - #endregion IList Implementation + try { + return inner[index]; + } + finally { + innerLock.ExitReadLock(); + } + } + set { + innerLock.EnterWriteLock(); - #region Public Properties + try { + inner[index] = value; + } + finally { + innerLock.ExitWriteLock(); + } + } + } - public int Capacity => inner.Capacity; + #endregion IList Implementation - #endregion Public Properties + #region Public Properties - #region Public Methods + public int Capacity => inner.Capacity; - // Implement remaining List methods here. + #endregion Public Properties - public void AddRange(IEnumerable collection) { - innerLock.EnterWriteLock(); + #region Public Methods + // + // Implement remaining List methods here. + // + public void AddRange(IEnumerable collection) { + innerLock.EnterWriteLock(); - try { - inner.AddRange(collection); - } - finally { - innerLock.ExitWriteLock(); + try { + inner.AddRange(collection); + } + finally { + innerLock.ExitWriteLock(); + } } - } - public bool Exists(Predicate match) { - innerLock.EnterReadLock(); + public bool Exists(Predicate match) { + innerLock.EnterReadLock(); - try { - return inner.Exists(match); + try { + return inner.Exists(match); + } + finally { + innerLock.ExitReadLock(); + } } - finally { - innerLock.ExitReadLock(); - } - } - public int RemoveAll(Predicate match) { - innerLock.EnterWriteLock(); + public int RemoveAll(Predicate match) { + innerLock.EnterWriteLock(); - try { - return inner.RemoveAll(match); - } - finally { - innerLock.ExitWriteLock(); + try { + return inner.RemoveAll(match); + } + finally { + innerLock.ExitWriteLock(); + } } - } - #endregion Public Methods + #endregion Public Methods } diff --git a/Str.Common/Core/LockingObservableCollection.cs b/Str.Common/Core/LockingObservableCollection.cs index 4be3a10..f2de2f4 100644 --- a/Str.Common/Core/LockingObservableCollection.cs +++ b/Str.Common/Core/LockingObservableCollection.cs @@ -3,7 +3,8 @@ using System.Diagnostics.CodeAnalysis; -namespace Str.Common.Core; +namespace Str.Common.Core; + [SuppressMessage("ReSharper", "UnusedType.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is a library.")] @@ -187,4 +188,4 @@ internal static class EventArgsCache { internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new(NotifyCollectionChangedAction.Reset); -} \ No newline at end of file +} diff --git a/Str.Common/Core/LockingReadOnlyCollection.cs b/Str.Common/Core/LockingReadOnlyCollection.cs index 05434f9..0970e8c 100644 --- a/Str.Common/Core/LockingReadOnlyCollection.cs +++ b/Str.Common/Core/LockingReadOnlyCollection.cs @@ -1,93 +1,98 @@ using System.Collections; +using JetBrains.Annotations; + + +namespace Str.Common.Core; -namespace Str.Common.Core; public sealed class LockingReadOnlyCollection : IList, IReadOnlyList { - #region Private Fields + #region Private Fields - private readonly LockingList list; + private readonly LockingList list; - private readonly NotSupportedException notSupportedException; + private readonly NotSupportedException notSupportedException; - #endregion Private Fields + #endregion Private Fields - #region Constructor + #region Constructor - public LockingReadOnlyCollection(IEnumerable list) { - this.list = new LockingList(list); + public LockingReadOnlyCollection(IEnumerable list) { + this.list = new LockingList(list); - notSupportedException = new NotSupportedException("This is a Read Only Collection."); - } + notSupportedException = new NotSupportedException("This is a Read Only Collection."); + } - #endregion Constructor + #endregion Constructor - #region ICollection Implementation + #region ICollection Implementation - public int Count => list.Count; + public int Count => list.Count; - public bool Contains(T value) { - return list.Contains(value); - } + public bool Contains(T value) { + return list.Contains(value); + } - public void CopyTo(T[] array, int index) { - list.CopyTo(array, index); - } + public void CopyTo(T[] array, int index) { + list.CopyTo(array, index); + } - public bool IsReadOnly => true; + public bool IsReadOnly => true; - void ICollection.Add(T value) { - throw notSupportedException; - } + void ICollection.Add(T value) { + throw notSupportedException; + } - void ICollection.Clear() { - throw notSupportedException; - } + void ICollection.Clear() { + throw notSupportedException; + } - bool ICollection.Remove(T value) { - throw notSupportedException; - } + bool ICollection.Remove(T value) { + throw notSupportedException; + } - #endregion ICollection Implementation + #endregion ICollection Implementation - #region IEnumerable Implementation + #region IEnumerable Implementation - public IEnumerator GetEnumerator() { - return ((IList)list).GetEnumerator(); - } + [MustDisposeResource] + public IEnumerator GetEnumerator() { + return ((IList)list).GetEnumerator(); + } - IEnumerator IEnumerable.GetEnumerator() { - return list.GetEnumerator(); - } + [MustDisposeResource] + IEnumerator IEnumerable.GetEnumerator() { + return list.GetEnumerator(); + } - #endregion IEnumerable Implementation + #endregion IEnumerable Implementation - #region IList Implementation + #region IList Implementation - public int IndexOf(T value) { - return list.IndexOf(value); - } + public int IndexOf(T value) { + return list.IndexOf(value); + } - T IList.this[int index] { - get => list[index]; - set => throw notSupportedException; - } + T IList.this[int index] { + get => list[index]; + set => throw notSupportedException; + } - void IList.Insert(int index, T value) { - throw notSupportedException; - } + void IList.Insert(int index, T value) { + throw notSupportedException; + } - void IList.RemoveAt(int index) { - throw notSupportedException; - } + void IList.RemoveAt(int index) { + throw notSupportedException; + } - #endregion IList Implementation + #endregion IList Implementation - #region IReadOnlyList Implementation + #region IReadOnlyList Implementation - public T this[int index] => list[index]; + public T this[int index] => list[index]; - #endregion IReadOnlyList Implementation + #endregion IReadOnlyList Implementation } \ No newline at end of file diff --git a/Str.Common/Extensions/ObservableCollectionExtensions.cs b/Str.Common/Extensions/ObservableCollectionExtensions.cs index 389f2c5..f5dba96 100644 --- a/Str.Common/Extensions/ObservableCollectionExtensions.cs +++ b/Str.Common/Extensions/ObservableCollectionExtensions.cs @@ -33,7 +33,7 @@ public static void OrderedMerge(this ObservableCollection list, T item) wh public static void OrderedMerge(this ObservableCollection list, IEnumerable items) where T : IComparable { List itemList = items.ToList(); - if (!itemList.Any()) return; + if (itemList.Count == 0) return; if (!list.Any()) { list.AddRange(itemList); diff --git a/Str.Common/Extensions/StreamExtensions.cs b/Str.Common/Extensions/StreamExtensions.cs index 4406132..129c791 100644 --- a/Str.Common/Extensions/StreamExtensions.cs +++ b/Str.Common/Extensions/StreamExtensions.cs @@ -5,8 +5,7 @@ using Str.Common.Messages; -namespace Str.Common.Extensions; - +namespace Str.Common.Extensions; // // From an answer on Stack Overflow // @@ -17,78 +16,78 @@ namespace Str.Common.Extensions; [SuppressMessage("ReSharper", "UnusedType.Global", Justification = "This is a library.")] public static class StreamExtensions { - private const int DefaultBufferSize = 32768; - - public static Task CopyToAsync(this Stream input, Stream output, int bufferSize = DefaultBufferSize, FileDownloadProgressMessage? message = null, Action?>? callback = null) where T : class { - return input.CopyToAsync(output, bufferSize, message as FileDownloadProgressMessage, callback as Action); - } - - public static async Task CopyToAsync(this Stream input, Stream output, int bufferSize = DefaultBufferSize, FileDownloadProgressMessage? message = null, Action? callback = null) { - ValidateCopyToAsyncArguments(input, output, bufferSize); - - byte[][] buf = { new byte[bufferSize], new byte[bufferSize] }; - - int[] bufl = { 0, 0 }; - - int bufno = 0; - int total = 0; - - Task read = input.ReadAsync(buf[bufno], 0, buf[bufno].Length); - - Task? write = null; + private const int DefaultBufferSize = 32768; - while(true) { - // - // wait for the read operation to complete - // - bufl[bufno] = await read.Fire(); - // - // if zero bytes read, the copy is complete - // - if (bufl[bufno] == 0) break; - - total += bufl[bufno]; - - if (message != null) message.BytesCurrent = total; - - callback?.Invoke(message); - // - // wait for the in-flight write operation, if one exists, to complete - // the only time one won't exist is after the very first read operation completes - // - if (write != null) await write.Fire(); - // - // start the new write operation - // - write = output.WriteAsync(buf[bufno], 0, bufl[bufno]); - // - // toggle the current, in-use buffer - // and start the read operation on the new buffer. - // - // Changed to use XOR to toggle between 0 and 1. - // A little speedier than using a ternary expression. - // - bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ; - - read = input.ReadAsync(buf[bufno], 0, buf[bufno].Length); + public static async Task CopyToAsync(this Stream input, Stream output, int bufferSize = DefaultBufferSize, FileDownloadProgressMessage? message = null, Action?>? callback = null) where T : class { + await input.CopyToAsync(output, bufferSize, message as FileDownloadProgressMessage, callback as Action).Fire(); } - // - // wait for the final in-flight write operation, if one exists, to complete - // the only time one won't exist is if the input stream is empty. - // - if (write != null) await write.Fire(); - if (message != null) message.IsComplete = true; - - callback?.Invoke(message); - } + public static async Task CopyToAsync(this Stream input, Stream output, int bufferSize = DefaultBufferSize, FileDownloadProgressMessage? message = null, Action? callback = null) { + ValidateCopyToAsyncArguments(input, output, bufferSize); + + byte[][] buf = [new byte[bufferSize], new byte[bufferSize]]; + + int[] bufl = [0, 0]; + + int bufno = 0; + int total = 0; + + Task read = input.ReadAsync(buf[bufno], 0, buf[bufno].Length); + + Task? write = null; + + while (true) { + // + // wait for the read operation to complete + // + bufl[bufno] = await read.Fire(); + // + // if zero bytes read, the copy is complete + // + if (bufl[bufno] == 0) break; + + total += bufl[bufno]; + + if (message != null) message.BytesCurrent = total; + + callback?.Invoke(message); + // + // wait for the in-flight write operation, if one exists, to complete + // the only time one won't exist is after the very first read operation completes + // + if (write != null) await write.Fire(); + // + // start the new write operation + // + write = output.WriteAsync(buf[bufno], 0, bufl[bufno]); + // + // toggle the current, in-use buffer + // and start the read operation on the new buffer. + // + // Changed to use XOR to toggle between 0 and 1. + // A little speedier than using a ternary expression. + // + bufno ^= 1; // bufno = ( bufno == 0 ? 1 : 0 ) ; + + read = input.ReadAsync(buf[bufno], 0, buf[bufno].Length); + } + // + // wait for the final in-flight write operation, if one exists, to complete + // the only time one won't exist is if the input stream is empty. + // + if (write != null) await write.Fire(); + + if (message != null) message.IsComplete = true; + + callback?.Invoke(message); + } - [AssertionMethod] - private static void ValidateCopyToAsyncArguments(Stream input, Stream output, int bufferSize) { - if (!input.CanRead) throw new InvalidOperationException("Input stream must be open for reading."); - if (!output.CanWrite) throw new InvalidOperationException("Output stream must be open for writing."); + [AssertionMethod] + private static void ValidateCopyToAsyncArguments(Stream input, Stream output, int bufferSize) { + if (!input.CanRead) throw new InvalidOperationException("Input stream must be open for reading."); + if (!output.CanWrite) throw new InvalidOperationException("Output stream must be open for writing."); - if (bufferSize < 1) throw new ArgumentException("Argument may not be 0 or negative.", nameof(bufferSize)); - } + if (bufferSize < 1) throw new ArgumentException("Argument may not be 0 or negative.", nameof(bufferSize)); + } } \ No newline at end of file diff --git a/Str.Common/Messages/ApplicationClosingMessage.cs b/Str.Common/Messages/ApplicationClosingMessage.cs index f0f23ed..aaa337d 100644 --- a/Str.Common/Messages/ApplicationClosingMessage.cs +++ b/Str.Common/Messages/ApplicationClosingMessage.cs @@ -1,6 +1,5 @@ - +namespace Str.Common.Messages; -namespace Str.Common.Messages; public class ApplicationClosingMessage : MessageBase { diff --git a/Str.Common/Messages/ApplicationErrorMessage.cs b/Str.Common/Messages/ApplicationErrorMessage.cs index f75dbb0..6ec1269 100644 --- a/Str.Common/Messages/ApplicationErrorMessage.cs +++ b/Str.Common/Messages/ApplicationErrorMessage.cs @@ -1,7 +1,8 @@ using System.Diagnostics.CodeAnalysis; -namespace Str.Common.Messages; +namespace Str.Common.Messages; + [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "This is a library.")] diff --git a/Str.Common/Messages/FileDownloadProgressMessage.cs b/Str.Common/Messages/FileDownloadProgressMessage.cs index fbab024..78683a8 100644 --- a/Str.Common/Messages/FileDownloadProgressMessage.cs +++ b/Str.Common/Messages/FileDownloadProgressMessage.cs @@ -1,7 +1,8 @@ using System.Diagnostics.CodeAnalysis; -namespace Str.Common.Messages; +namespace Str.Common.Messages; + [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "This is a library.")] @@ -18,6 +19,7 @@ public class FileDownloadProgressMessage : MessageBase { } + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "UnusedMember.Global", Justification = "This is a library.")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "This is a library.")] diff --git a/Str.Common/Messages/MessageBase.cs b/Str.Common/Messages/MessageBase.cs index 3b09ed2..24c2fad 100644 --- a/Str.Common/Messages/MessageBase.cs +++ b/Str.Common/Messages/MessageBase.cs @@ -1,9 +1,8 @@ - +namespace Str.Common.Messages; -namespace Str.Common.Messages; public class MessageBase { protected MessageBase() { } -} \ No newline at end of file +} diff --git a/Str.Common/Str.Common.csproj b/Str.Common/Str.Common.csproj index 97553d8..4fc8261 100644 --- a/Str.Common/Str.Common.csproj +++ b/Str.Common/Str.Common.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 false 1.0.0.0 stricq @@ -28,6 +28,7 @@ portable true + true @@ -43,7 +44,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Str.Common/packages.lock.json b/Str.Common/packages.lock.json new file mode 100644 index 0000000..fe3988b --- /dev/null +++ b/Str.Common/packages.lock.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "dependencies": { + "net9.0": { + "AsyncFixer": { + "type": "Direct", + "requested": "[1.6.0, )", + "resolved": "1.6.0", + "contentHash": "/Xfs9H3UMfEv64cwT+C/JrTRp4w08BmPuFbj0ageadCHpx6rxYJxAU2C6sEqRFG22xmGk5cX9ewzoiiehWVHOw==" + }, + "ConfigureAwaitEnforcer": { + "type": "Direct", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "jkoGjQWaD5ioGPCv/Oi/tsCEcGWfLgOHeN9IPrZbzS8epgbuBQREFlg0Oe7J6yRZuhxMyfJrSmdXtnhNgdmkLw==" + }, + "JetBrains.Annotations": { + "type": "Direct", + "requested": "[2024.3.0, )", + "resolved": "2024.3.0", + "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + } + } + } +} \ No newline at end of file