diff --git a/ListFunctions/Core/ListFunctions.Engine.dll b/ListFunctions/Core/ListFunctions.Engine.dll index 2e942a4..581f6ce 100644 Binary files a/ListFunctions/Core/ListFunctions.Engine.dll and b/ListFunctions/Core/ListFunctions.Engine.dll differ diff --git a/ListFunctions/Core/ListFunctions.Next.dll b/ListFunctions/Core/ListFunctions.Next.dll index 2fdde40..1460440 100644 Binary files a/ListFunctions/Core/ListFunctions.Next.dll and b/ListFunctions/Core/ListFunctions.Next.dll differ diff --git a/ListFunctions/Core/MG.Collections.Resources.dll b/ListFunctions/Core/MG.Collections.Resources.dll deleted file mode 100644 index 07e0ee5..0000000 Binary files a/ListFunctions/Core/MG.Collections.Resources.dll and /dev/null differ diff --git a/ListFunctions/Core/MG.Collections.dll b/ListFunctions/Core/MG.Collections.dll deleted file mode 100644 index 2aef0cb..0000000 Binary files a/ListFunctions/Core/MG.Collections.dll and /dev/null differ diff --git a/ListFunctions/Core/ZLinq.dll b/ListFunctions/Core/ZLinq.dll new file mode 100644 index 0000000..e157b13 Binary files /dev/null and b/ListFunctions/Core/ZLinq.dll differ diff --git a/ListFunctions/Desk/ListFunctions.Engine.dll b/ListFunctions/Desk/ListFunctions.Engine.dll index 6dd8cc3..492e8b8 100644 Binary files a/ListFunctions/Desk/ListFunctions.Engine.dll and b/ListFunctions/Desk/ListFunctions.Engine.dll differ diff --git a/ListFunctions/Desk/ListFunctions.NETFramework.dll b/ListFunctions/Desk/ListFunctions.NETFramework.dll index 1c984e5..fd62e3f 100644 Binary files a/ListFunctions/Desk/ListFunctions.NETFramework.dll and b/ListFunctions/Desk/ListFunctions.NETFramework.dll differ diff --git a/ListFunctions/Desk/MG.Collections.Resources.dll b/ListFunctions/Desk/MG.Collections.Resources.dll deleted file mode 100644 index 6f29d63..0000000 Binary files a/ListFunctions/Desk/MG.Collections.Resources.dll and /dev/null differ diff --git a/ListFunctions/Desk/MG.Collections.dll b/ListFunctions/Desk/MG.Collections.dll deleted file mode 100644 index 91f81e9..0000000 Binary files a/ListFunctions/Desk/MG.Collections.dll and /dev/null differ diff --git a/ListFunctions/ListFunctions.psd1 b/ListFunctions/ListFunctions.psd1 index b30f799..53412e3 100644 --- a/ListFunctions/ListFunctions.psd1 +++ b/ListFunctions/ListFunctions.psd1 @@ -3,7 +3,7 @@ # # Generated by: Mike Garvey # -# Generated on: 1/7/2024 +# Generated on: 9/13/2025 # @{ @@ -12,7 +12,7 @@ RootModule = 'ListFunctions.psm1' # Version number of this module. - ModuleVersion = '2.0.0' + ModuleVersion = '3.1.0' # Supported PSEditions CompatiblePSEditions = @('Desk', 'Core') @@ -27,7 +27,7 @@ CompanyName = 'Yevrag35, LLC.' # Copyright statement for this module - Copyright = 'Copyright (c) 2020-2024 Yevrag35, LLC.' + Copyright = 'Copyright (c) 2020-2025 Yevrag35, LLC.' # Description of the functionality provided by this module Description = 'A simple module that provides functions to manipulate, search, and create Arrays, Collections, Lists, and Sets.' @@ -75,7 +75,7 @@ CmdletsToExport = @( 'Assert-AllObject', 'Assert-AnyObject', 'Find-IndexOf', 'Find-LastIndexOf', 'New-Dictionary', 'New-HashSet', 'New-List', - 'New-SortedSet' + 'New-SortedSet', 'ConvertTo-Dictionary' ) # Variables to export from this module @@ -99,12 +99,9 @@ 'ListFunctions.psm1', 'Core\ListFunctions.Engine.dll', 'Core\ListFunctions.Next.dll', - 'Core\MG.Collections.dll', - 'Core\MG.Collections.Resources.dll', + 'Core\ZLinq.dll', 'Desk\ListFunctions.Engine.dll', - 'Desk\ListFunctions.NETFramework.dll', - 'Desk\MG.Collections.dll', - 'Desk\MG.Collections.Resources.dll' + 'Desk\ListFunctions.NETFramework.dll' ) # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. @@ -115,7 +112,7 @@ # Tags applied to this module. These help with module discovery in online galleries. Tags = @('All', 'Any', 'Array', 'Assert', 'bool', 'Collection', 'compare', 'Condition', 'count', 'Enumerable', 'equality', 'Find', 'HashSet', 'index', 'Last', 'Linq', - 'List', 'Modify', 'Predicate', 'Remove', 'set', 'sort', 'Test', 'Where') + 'List', 'Modify', 'Predicate', 'Remove', 'set', 'sort', 'Test', 'Where', 'Convert', 'ConvertTo') # A URL to the license for this module. LicenseUri = 'https://raw.githubusercontent.com/Yevrag35/PowerShell-ListFunctions/master/LICENSE' @@ -129,7 +126,7 @@ # Prerelease = 'beta' # ReleaseNotes of this module - ReleaseNotes = 'Overhaul of all cmdlets and moved to binary module for performance improvements.' + ReleaseNotes = 'Fixes Find-IndexOf cmdlet from returning the wrong result in certain situations.' } # End of PSData hashtable diff --git a/ListFunctions/ListFunctions.psm1 b/ListFunctions/ListFunctions.psm1 index 632734a..2b0b82f 100644 --- a/ListFunctions/ListFunctions.psm1 +++ b/ListFunctions/ListFunctions.psm1 @@ -4,15 +4,9 @@ default { throw "Incompatible PowerShell Version" } } -$script:assFolder = Split-Path -Path $script:dllPath -Parent -$script:loadThese = @( - 'MG.Collections.dll', - 'ListFunctions.Engine.dll' -) +# foreach ($script:assName in $script:loadThese) { -foreach ($script:assName in $script:loadThese) { - - Import-Module "$($script:assFolder)\$($script:assName)" -} + # Import-Module "$($script:assFolder)\$($script:assName)" +# } Import-Module $script:dllPath -ErrorAction Stop \ No newline at end of file diff --git a/src/engine/LFPublish/LFPublish.csproj b/src/engine/LFPublish/LFPublish.csproj index 388e9cb..f414d84 100644 --- a/src/engine/LFPublish/LFPublish.csproj +++ b/src/engine/LFPublish/LFPublish.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable true @@ -13,7 +13,7 @@ - + diff --git a/src/engine/ListFunctions-NETFramework/.vs/ListFunctions-NETFramework.csproj.dtbcache.json b/src/engine/ListFunctions-NETFramework/.vs/ListFunctions-NETFramework.csproj.dtbcache.json new file mode 100644 index 0000000..87fb26e --- /dev/null +++ b/src/engine/ListFunctions-NETFramework/.vs/ListFunctions-NETFramework.csproj.dtbcache.json @@ -0,0 +1 @@ +{"RootPath":"O:\\Local_Repos\\ListFunctions\\src\\engine\\ListFunctions-NETFramework","ProjectFileName":"ListFunctions-NETFramework.csproj","Configuration":"Debug|AnyCPU","FrameworkPath":"","Sources":[{"SourceFile":"Nullable\\AllowNullAttribute.cs"},{"SourceFile":"Nullable\\DisallowNullAttribute.cs"},{"SourceFile":"Nullable\\DoesNotReturnAttribute.cs"},{"SourceFile":"Nullable\\DoesNotReturnIfAttribute.cs"},{"SourceFile":"Nullable\\MaybeNullAttribute.cs"},{"SourceFile":"Nullable\\MaybeNullWhenAttribute.cs"},{"SourceFile":"Nullable\\MemberNotNullAttribute.cs"},{"SourceFile":"Nullable\\MemberNotNullWhenAttribute.cs"},{"SourceFile":"Nullable\\NotNullAttribute.cs"},{"SourceFile":"Nullable\\NotNullIfNotNullAttribute.cs"},{"SourceFile":"Nullable\\NotNullWhenAttribute.cs"},{"SourceFile":"Properties\\AssemblyInfo.cs"},{"SourceFile":"..\\ListFunctions-Next\\Build\\InternalFinder.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Assertions\\AssertAllObjectsCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Assertions\\AssertAnyObjectCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Assertions\\AssertObjectCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\ConvertToDictionaryCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\EqualityConstructingCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\NewDictionaryCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\NewHashSetCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\NewListCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Constructs\\NewSortedSetCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Finds\\FindIndexCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\Finds\\FindLastIndexCmdlet.cs"},{"SourceFile":"..\\ListFunctions-Next\\Cmdlets\\ListFunctionCmdletBase.cs"},{"SourceFile":"..\\ListFunctions-Next\\DuplicateKeyBehavior.cs"},{"SourceFile":"..\\ListFunctions-Next\\Exceptions\\LFInvalidCastException.cs"},{"SourceFile":"..\\ListFunctions-Next\\Validation\\ArgumentToTypeNameTransformAttribute.cs"},{"SourceFile":"..\\ListFunctions-Next\\Validation\\ListTransformAttribute.cs"},{"SourceFile":"..\\ListFunctions-Next\\Validation\\ValidateScriptVariableAttribute.cs"},{"SourceFile":"obj\\Debug\\.NETFramework,Version=v4.7.1.AssemblyAttributes.cs"}],"References":[{"Reference":"O:\\Local_Repos\\ListFunctions\\src\\engine\\ListFunctions.Engine\\bin\\Debug\\netstandard2.0\\ListFunctions.Engine.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":true,"ProjectPath":"O:\\Local_Repos\\ListFunctions\\src\\engine\\ListFunctions.Engine\\bin\\Debug\\netstandard2.0\\ListFunctions.Engine.dll"},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.1\\Microsoft.CSharp.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.1\\mscorlib.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\netfx.force.conflicts.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.1\\System.Core.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Data.Common.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Diagnostics.StackTrace.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Diagnostics.Tracing.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.7.1\\System.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Globalization.Extensions.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.IO.Compression.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"O:\\Local_Repos\\ListFunctions\\src\\engine\\packages\\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\\lib\\net4\\System.Management.Automation.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Net.Http.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Net.Sockets.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Runtime.Serialization.Primitives.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Security.Cryptography.Algorithms.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Security.SecureString.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Threading.Overlapped.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""},{"Reference":"C:\\Program Files\\Microsoft Visual Studio\\18\\Insiders\\MSBuild\\Microsoft\\Microsoft.NET.Build.Extensions\\net471\\lib\\System.Xml.XPath.XDocument.dll","ResolvedFrom":"","OriginalItemSpec":"","Name":"","EmbedInteropTypes":false,"CopyLocal":false,"IsProjectReference":false,"ProjectPath":""}],"Analyzers":[],"Outputs":[{"OutputItemFullPath":"O:\\Local_Repos\\ListFunctions\\src\\engine\\ListFunctions-NETFramework\\bin\\Debug\\ListFunctions.NETFramework.dll","OutputItemRelativePath":"ListFunctions.NETFramework.dll"},{"OutputItemFullPath":"","OutputItemRelativePath":""}],"CopyToOutputEntries":[]} \ No newline at end of file diff --git a/src/engine/ListFunctions-NETFramework/ListFunctions-NETFramework.csproj b/src/engine/ListFunctions-NETFramework/ListFunctions-NETFramework.csproj index be11dda..ff69319 100644 --- a/src/engine/ListFunctions-NETFramework/ListFunctions-NETFramework.csproj +++ b/src/engine/ListFunctions-NETFramework/ListFunctions-NETFramework.csproj @@ -6,15 +6,16 @@ Debug AnyCPU {6E62CFEE-5F6B-4097-98C0-93F3CF55A17D} - 8.0 + 9.0 Library enable Properties ListFunctions ListFunctions.NETFramework - 2.0.0 - 2.0.0 - 2.0.0 + 3.1.0 + true + 3.1.0 + 3.1.0 v4.7.1 512 true @@ -40,20 +41,11 @@ 4 - - ..\packages\MG.Collections.1.2.0\lib\net462\MG.Collections.dll - - ..\packages\Microsoft.PowerShell.5.ReferenceAssemblies.1.1.0\lib\net4\System.Management.Automation.dll - - - - - @@ -69,21 +61,30 @@ - + Always + + + + Never + Never + + + + + + + {515aa5f2-1c73-4556-a519-c2ff18b423f4} ListFunctions.Engine - - - diff --git a/src/engine/ListFunctions-NETFramework/Properties/AssemblyInfo.cs b/src/engine/ListFunctions-NETFramework/Properties/AssemblyInfo.cs index ee93f05..c1f66dd 100644 --- a/src/engine/ListFunctions-NETFramework/Properties/AssemblyInfo.cs +++ b/src/engine/ListFunctions-NETFramework/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ListFunctions-NETFramework")] -[assembly: AssemblyCopyright("Copyright © Yevrag35, LLC. 2020-2024")] +[assembly: AssemblyCopyright("Copyright © Yevrag35, LLC. 2020-2025")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.0")] -[assembly: AssemblyFileVersion("2.0.0")] +[assembly: AssemblyVersion("3.1.0")] +[assembly: AssemblyFileVersion("3.1.0")] diff --git a/src/engine/ListFunctions-NETFramework/app.config b/src/engine/ListFunctions-NETFramework/app.config new file mode 100644 index 0000000..7f57587 --- /dev/null +++ b/src/engine/ListFunctions-NETFramework/app.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/engine/ListFunctions-NETFramework/packages.config b/src/engine/ListFunctions-NETFramework/packages.config index e066725..572364e 100644 --- a/src/engine/ListFunctions-NETFramework/packages.config +++ b/src/engine/ListFunctions-NETFramework/packages.config @@ -1,6 +1,11 @@  - - + + + + + + + \ No newline at end of file diff --git a/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAllObjectsCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAllObjectsCmdlet.cs index ea3fe4f..aa09071 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAllObjectsCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAllObjectsCmdlet.cs @@ -6,51 +6,47 @@ using System.Collections.Generic; using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; + +#nullable enable namespace ListFunctions.Cmdlets.Assertions { [Cmdlet(VerbsLifecycle.Assert, "AllObject")] [Alias("Assert-AllObjects", "Assert-All", "All", "All-Object", "All-Objects")] [OutputType(typeof(bool))] - public sealed class AssertAllObjectsCmdlet : ListFunctionCmdletBase + public sealed class AssertAllObjectsCmdlet : AssertObjectCmdlet { - ScriptBlockFilter _equality = null!; - bool _stop; + [Parameter(Mandatory = true, Position = 0)] + [Alias("ScriptBlock", "FilterScript")] + [AllowNull, AllowEmptyString] + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.ARGS_FIRST)] + public override ScriptBlock? Condition + { + get => base.Condition; + set => base.Condition = value; + } [Parameter(Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyCollection] - [AllowNull] - public object[] InputObject { get; set; } = null!; - - [Parameter(Mandatory = true, Position = 0)] - [Alias("ScriptBlock")] - [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME)] - public ScriptBlock Condition { get; set; } = null!; + [AllowNull, AllowEmptyCollection, AllowEmptyString] + public object?[]? InputObject { get; set; } [Parameter] - public ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; + public override ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; - protected override void BeginProcessing() - { - _equality = new ScriptBlockFilter(this.Condition, EnumerateVariables(this.ScriptBlockErrorAction)); - } - protected override void ProcessRecord() + protected override bool Process(ScriptBlockFilter filter) { - if (!_stop) - { - _stop = !_equality.All(this.InputObject); - } + return !filter.All(this.InputObject); } - protected override void EndProcessing() + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0009:Member access should be qualified.", Justification = "Used in nameof()")] + protected override bool ProcessWhenNoCondition() { - this.WriteObject(_stop); + throw new ArgumentException("Asserting an all-true condition requires a condition to be specified.", nameof(Condition)); } - private static IEnumerable EnumerateVariables(ActionPreference errorPref) + protected override void End(bool scriptResult) { - yield return new PSVariable(ERROR_ACTION_PREFERENCE, errorPref); + this.WriteObject(!scriptResult); } } } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAnyObjectCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAnyObjectCmdlet.cs index d426c27..3e8faab 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAnyObjectCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertAnyObjectCmdlet.cs @@ -1,104 +1,61 @@ -using System.Collections.Generic; +using ListFunctions.Modern; +using ListFunctions.Modern.Variables; +using ListFunctions.Validation; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Management.Automation; -using ListFunctions.Modern; -using ListFunctions.Modern.Variables; -using ListFunctions.Validation; -using PSAllowNullAttribute = System.Management.Automation.AllowNullAttribute; +using AllowsNull = System.Diagnostics.CodeAnalysis.AllowNullAttribute; +using PSAllowNull = System.Management.Automation.AllowNullAttribute; + +#nullable enable + namespace ListFunctions.Cmdlets.Assertions { [Cmdlet(VerbsLifecycle.Assert, "AnyObject")] [Alias("Assert-Any", "Any-Object", "Any")] [OutputType(typeof(bool))] - public sealed class AssertAnyObjectCmdlet : ListFunctionCmdletBase + public sealed class AssertAnyObjectCmdlet : AssertObjectCmdlet { - ScriptBlock? _condition; - ActionPreference _errorPref; - bool _hasCondition; - bool _hasNonNull; - ScriptBlockFilter _equality = null!; - bool _stop; - [Parameter(Mandatory = true, ValueFromPipeline = true)] - [AllowEmptyCollection] - [PSAllowNull] - public object[] InputObject { get; set; } = null!; + [AllowEmptyCollection, PSAllowNull, AllowEmptyString] + public object?[]? InputObject { get; set; } [Parameter(Position = 0)] - [Alias("ScriptBlock")] - [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME)] - public ScriptBlock? Condition + [Alias("ScriptBlock", "FilterScript")] + [PSAllowNull, AllowEmptyString, MaybeNull, AllowsNull] + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.ARGS_FIRST)] + public override ScriptBlock Condition { - get => _condition; - set - { - _condition = value; - _hasCondition = !(_condition is null); - } + get => base.Condition; + set => base.Condition = value; } + [Parameter, Alias("ScriptErrorAction")] + public override ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; -#if NET5_0_OR_GREATER - [MemberNotNullWhen(true, nameof(Condition), nameof(_condition))] -#endif - internal bool HasCondition => _hasCondition; - - protected override void BeginProcessing() + protected override bool Process(ScriptBlockFilter filter) { - _errorPref = this.GetErrorPreference(); - if (this.HasCondition) - { - _equality = new ScriptBlockFilter(this.Condition!, EnumerateVariables(_errorPref)); - } + return filter.Any(this.InputObject); } - protected override void ProcessRecord() + protected override bool ProcessWhenNoCondition() { - if (!this.HasCondition) + if (!(this.InputObject is null)) { - if (!_hasNonNull) + foreach (object? item in this.InputObject) { - ProcessWhenNoCondition(this.InputObject, ref _hasNonNull); - } - - return; - } - else if (!_stop) - { - _stop = _equality.Any(this.InputObject); - } - } - private static void ProcessWhenNoCondition(object[]? inputObjects, ref bool hasNonNull) - { - if (!(inputObjects is null)) - { - foreach (object? o in inputObjects) - { - if (!(o is null)) + if (!(item is null)) { - hasNonNull = true; - break; + return true; } } } - } - protected override void EndProcessing() - { - if (!this.HasCondition) - { - this.WriteObject(_hasNonNull); - return; - } - else - { - this.WriteObject(_stop); - return; - } - } - private static IEnumerable EnumerateVariables(ActionPreference errorPref) + return false; + } + protected override void End(bool scriptResult) { - yield return new PSVariable(ERROR_ACTION_PREFERENCE, errorPref); + this.WriteObject(scriptResult); } } } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertObjectCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertObjectCmdlet.cs new file mode 100644 index 0000000..a413d21 --- /dev/null +++ b/src/engine/ListFunctions-Next/Cmdlets/Assertions/AssertObjectCmdlet.cs @@ -0,0 +1,103 @@ +using ListFunctions.Modern; +using ListFunctions.Modern.Variables; +using ListFunctions.Validation; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Management.Automation; + +using AllowsNull = System.Diagnostics.CodeAnalysis.AllowNullAttribute; +using PSAllowNull = System.Management.Automation.AllowNullAttribute; + +#nullable enable + +namespace ListFunctions.Cmdlets.Assertions +{ + public abstract class AssertObjectCmdlet : ListFunctionCmdletBase, IDisposable + { + private ScriptBlock? _condition; + private bool _disposed; + private bool _stopRequested; + + [MaybeNull, AllowsNull] + public virtual ScriptBlock Condition + { + get => _condition; + set + { + _condition = value; + this.HasCondition = !(value is null || string.IsNullOrWhiteSpace(value.ToString())); + } + } + public abstract ActionPreference ScriptBlockErrorAction { get; set; } + + [AllowsNull] + private protected ScriptBlockFilter? Filter { get; private set; } +#if NETCOREAPP + [MemberNotNullWhen(true, nameof(Condition), nameof(Filter))] +#endif + protected private bool HasCondition { get; set; } + + protected sealed override void BeginCore() + { + base.BeginCore(); + + if (this.HasCondition) + { + this.Filter = new ScriptBlockFilter(_condition!, new PSVariable(ERROR_ACTION_PREFERENCE, this.ScriptBlockErrorAction)); + } + + // # Maybe in the future. + //try + //{ + // this.Begin(); + //} + //catch + //{ + // this.Cleanup(); + // throw; + //} + } + + protected sealed override bool ProcessCore() + { + return this.HasCondition + ? !this.Process(this.Filter!) + : !this.ProcessWhenNoCondition(); + } + protected abstract bool Process(ScriptBlockFilter filter); + protected abstract bool ProcessWhenNoCondition(); + + protected sealed override void EndCore(bool wantsToStop) + { + this.End(wantsToStop); + } + protected abstract void End(bool scriptResult); + + protected override void Cleanup() + { + this.Dispose(); + } + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing && !(this.Filter is null)) + { + this.Filter.Dispose(); + this.Filter = null; + } + + _disposed = true; + } + } + } +} + diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/ConvertToDictionaryCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/ConvertToDictionaryCmdlet.cs new file mode 100644 index 0000000..cd01b0e --- /dev/null +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/ConvertToDictionaryCmdlet.cs @@ -0,0 +1,304 @@ +using ListFunctions.Exceptions; +using ListFunctions.Extensions; +using ListFunctions.Modern; +using ListFunctions.Modern.Variables; +using ListFunctions.Validation; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using ZLinq; + +#nullable enable + +namespace ListFunctions.Cmdlets.Constructs +{ + [Cmdlet(VerbsData.ConvertTo, "Dictionary", DefaultParameterSetName = "None")] + public sealed class ConvertToDictionaryCmdlet : ListFunctionCmdletBase + { + [Parameter(Mandatory = true, ValueFromPipeline = true)] + [AllowEmptyCollection, AllowNull, AllowEmptyString] + public object?[]? InputObject { get; set; } + + [Parameter] + public DuplicateKeyBehavior DuplicateKeyBehavior { get; set; } + + [Parameter] + public IEqualityComparer? KeyComparer { get; set; } + + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "KeyProperty")] + [Alias("KeyName", "Key")] +#if NET7_0_OR_GREATER + [ValidateNotNullOrWhiteSpace] +#endif + public string KeyPropertyName { get; set; } = string.Empty; + + [Parameter(Mandatory = true, Position = 0, ParameterSetName = "KeyScript")] + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.THIS_NAME, PSThisVariable.ARGS_FIRST)] + public ScriptBlock KeySelector { get; set; } = null!; + + [Parameter(Mandatory = false, Position = 1)] + [Alias("ValueName", "Value")] + [AllowEmptyString, AllowNull] + public object? ValuePropertyName { get; set; } + + [Parameter] + [AllowNull, AllowEmptyString] + public ScriptBlock? ValueSelector { get; set; } + + [Parameter] + [ArgumentToTypeTransform] + public Type? ValueType + { + get => _valueType; + set => _valueType = value; + } + + private IDictionary _dictionary = null!; + private Type _keyType = null!; + private Type? _valueType; + private nint _addToDictionaryPtr; + + protected override void BeginCore() + { + _addToDictionaryPtr = StoreAddToDictionaryFunction(this.DuplicateKeyBehavior); + + if (this.ParameterSetName.StartsWith("KeyProperty", StringComparison.Ordinal)) + { + this.KeySelector = ScriptBlock.Create(string.Concat("$args[0].'", this.KeyPropertyName, "'")); + this.KeyPropertyName = string.Empty; + } + else + { + this.KeySelector = this.KeySelector.ReplaceWithArgsZero(); + } + + if (this.ValuePropertyName is string s && !string.IsNullOrWhiteSpace(s)) + { + this.ValueSelector = ScriptBlock.Create(string.Concat("$args[0].'", this.ValuePropertyName, "'")); + this.ValuePropertyName = string.Empty; + } + else + { + this.ValueSelector = this.ValueSelector is null + ? this.ValuePropertyName is ScriptBlock valSc + ? valSc.ReplaceWithArgsZero() + : null + : this.ValueSelector.ReplaceWithArgsZero(); + } + + object?[]? inputObjects = this.InputObject; + if (!(inputObjects is null) && inputObjects.Length > 0) + { + _keyType = GetTypeForElement(inputObjects, this.KeySelector); + _valueType = this.GetValueType(_valueType, inputObjects); + } + } + + protected override bool ProcessCore() + { + bool flag = true; + object?[]? inputObjects = this.InputObject; + if (inputObjects is null || inputObjects.Length == 0) + return flag; + + _dictionary ??= this.CreateDictionary(inputObjects); + + unsafe + { + return this.AddToDictionary(inputObjects, (delegate*< ConvertToDictionaryCmdlet, object, object ?, void >)_addToDictionaryPtr); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0009:Member access should be qualified.", Justification = "Used in nameof()")] + private IDictionary CreateDictionary(object?[] inputObjects) + { + if (_keyType is null || _valueType is null) + { + _keyType = GetTypeForElement(inputObjects, this.KeySelector); + _valueType = this.GetValueType(_valueType, inputObjects); + } + + if (this.KeyComparer is null && _keyType.Equals(typeof(string))) + { + this.KeyComparer = StringComparer.OrdinalIgnoreCase; + } + + object[] args = this.KeyComparer is null + ? Array.Empty() + : new object[] { this.KeyComparer }; + + Type? dictType = null; + try + { + dictType = typeof(Dictionary<,>).MakeGenericType(_keyType, _valueType); + return Activator.CreateInstance(dictType, args) as IDictionary + ?? throw new InvalidOperationException("Somehow, Dictionary is not an IDictionary?"); + } + catch (Exception e) + { + ListFunctionsException ex = new($"Failed to instantiate dictionary with the arguments supplied - {e.Message}", e); + IDictionary data = ex.Data; + data["KeyType"] = _keyType; + data[nameof(InputObject)] = inputObjects.DeepClone(); + data[nameof(ValueType)] = _valueType; + data["DictionaryType"] = dictType; + + var rec = ex.ToRecord(ErrorCategory.InvalidOperation, targetObj: null); + + this.ThrowTerminatingError(rec); + throw; + } + } + + private unsafe bool AddToDictionary(object?[] inputObjects, delegate* addToDictionaryAction) + { + foreach (object item in inputObjects.AsValueEnumerable().Where(x => !(x is null))!) + { + try + { + object? key = this.KeySelector.Invoke(item).FirstOrDefault()?.BaseObject; + if (key is null) + continue; + + key = LanguagePrimitives.ConvertTo(key, _keyType); + + object? value = this.ValueSelector?.Invoke(item).FirstOrDefault()?.BaseObject is object o + ? LanguagePrimitives.ConvertTo(o, _valueType) + : item; + + addToDictionaryAction(this, key, value); + } + catch (PSInvalidCastException e) + { + var rec = e.ToRecord(ErrorCategory.InvalidArgument, item); + this.WriteError(rec); + } + catch (Exception e) + { + var rec = e.ToRecord(ErrorCategory.InvalidOperation, item); + this.ThrowTerminatingError(rec); + return false; + } + } + + return true; + } + + protected override void EndCore(bool wantsToStop) + { + if (wantsToStop) + return; + + if (_dictionary is null) + { + this.WriteObject(new Hashtable(StringComparer.OrdinalIgnoreCase)); + return; + } + + this.WriteObject(_dictionary, enumerateCollection: false); + } + + private Type GetValueType(Type? specifiedType, object?[] inputObjects) + { + if (this.DuplicateKeyBehavior == DuplicateKeyBehavior.Concatenate) + { + if (!(specifiedType is null) && !typeof(object).Equals(specifiedType)) + { + this.WriteWarning("ValueType is ignored when 'DuplicateKeyBehavior::Concatenate' is used as the values can either be objects or lists of objects."); + } + + return typeof(object); + } + + return specifiedType ?? GetTypeForElement(inputObjects, this.ValueSelector); + } + private static Type GetTypeForElement(object?[] inputObj, ScriptBlock? selector) + { + Type? type; + if (selector is null) + { + type = inputObj[0].GetBaseObject()?.GetType(); + } + else + { + var firstObj = selector.Invoke(inputObj).FirstOrDefault(); + if (firstObj is null) + { + return typeof(object); + } + + type = firstObj.GetBaseObject()?.GetType(); + } + + if (type is null || typeof(PSObject).IsAssignableFrom(type) || typeof(PSCustomObject).IsAssignableFrom(type)) + { + return typeof(object); + } + + return type; + } + + private static void AddConcat(ConvertToDictionaryCmdlet cmdlet, object key, object? value) + { + if (cmdlet._dictionary.Contains(key)) + { + cmdlet.WriteVerbose("Key exists, concatenating next value."); + object? existingValue = cmdlet._dictionary[key]; + if (!(existingValue is ObjectList objList)) + { + objList = new ObjectList() + { + existingValue, + }; + + cmdlet._dictionary[key] = objList; + } + + objList.Add(value); + } + else + { + cmdlet._dictionary.Add(key, value); + } + } + private static void AddSkip(ConvertToDictionaryCmdlet cmdlet, object key, object? value) + { + if (cmdlet._dictionary.Contains(key)) + { + cmdlet.WriteWarning("Key already exists, skipping value."); + return; + } + + cmdlet._dictionary.Add(key, value); + } + private static void AddVolatile(ConvertToDictionaryCmdlet cmdlet, object key, object? value) + { + try + { + cmdlet._dictionary.Add(key, value); + } + catch (ArgumentException e) + { + var rec = e.ToRecord(ErrorCategory.InvalidData, key); + cmdlet.WriteError(rec); + } + } + + private static nint StoreAddToDictionaryFunction(DuplicateKeyBehavior duplicateBehavior) + { + unsafe + { + delegate* action = duplicateBehavior switch + { + DuplicateKeyBehavior.Error => &AddVolatile, + DuplicateKeyBehavior.Skip => &AddSkip, + DuplicateKeyBehavior.Concatenate => &AddConcat, + _ => &AddVolatile, + }; + return (nint)action; + } + } + } +} diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/EqualityConstructingCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/EqualityConstructingCmdlet.cs index bf0a1f2..a179d1f 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Constructs/EqualityConstructingCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/EqualityConstructingCmdlet.cs @@ -11,6 +11,8 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Construct { public abstract class EqualityConstructingCmdlet : ListFunctionCmdletBase @@ -38,7 +40,7 @@ private RuntimeDefinedParameterDictionary DynParamLib protected abstract string CaseSensitiveParameterSetName { get; } public virtual int Capacity { get; set; } - protected bool CaseSensitive => RetrieveCaseSensitiveSetting(_caseSensitive); + protected bool CaseSensitive => IsParameterValueCaseSensitive(_caseSensitive); public virtual ActionPreference ScriptBlockErrorAction { get; set; } public object? GetDynamicParameters() @@ -52,7 +54,7 @@ private RuntimeDefinedParameterDictionary DynParamLib } #region PROCESSING - protected sealed override void BeginProcessing() + protected sealed override void BeginCore() { Type[]? genericTypes = this.GetGenericTypes(); IEqualityComparer? comparer = this.GetCustomEqualityComparer(this.GetEqualityForType()); @@ -71,20 +73,17 @@ protected virtual void Begin(T collection, Type genericBaseType) return; } - protected sealed override void ProcessRecord() + protected sealed override bool ProcessCore() { - this.Process(_collection, _collectionType); - } - protected virtual void Process(T collection, Type collectionType) - { - return; + return this.Process(_collection, _collectionType); } + protected abstract bool Process(T collection, Type collectionType); - protected sealed override void EndProcessing() + protected sealed override void EndCore(bool wantsToStop) { - this.End(_collection); + this.End(_collection, wantsToStop); } - protected virtual void End(T collection) + protected virtual void End(T collection, bool wantsToStop) { return; } @@ -153,14 +152,19 @@ protected void AddToCollection(T collection, object?[]? item, bool addIfNull) protected virtual IEqualityComparer? GetCustomEqualityComparer(Type genericType) { - return null; + if (!typeof(string).Equals(genericType)) + return null; + + return IsParameterValueCaseSensitive(_caseSensitive) + ? StringComparer.CurrentCulture + : StringComparer.OrdinalIgnoreCase; } protected abstract Type[]? GetGenericTypes(); protected abstract Type GetEqualityForType(); - private static bool RetrieveCaseSensitiveSetting(RuntimeDefinedParameter? parameter) + private static bool IsParameterValueCaseSensitive(RuntimeDefinedParameter? parameter) { - return !(parameter is null) && parameter.Value is SwitchParameter swParam && swParam.ToBool(); + return LanguagePrimitives.IsTrue(parameter?.Value); } #endregion diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewDictionaryCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewDictionaryCmdlet.cs index 49adfca..bbad00c 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewDictionaryCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewDictionaryCmdlet.cs @@ -13,6 +13,8 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Construct { [Cmdlet(VerbsCommon.New, "Dictionary", DefaultParameterSetName = "None")] @@ -26,7 +28,7 @@ public sealed class NewDictionaryCmdlet : EqualityConstructingCmdlet STR_DICT; - [Parameter] + [Parameter, Alias("Size")] [ValidateRange(0, int.MaxValue)] [PSDefaultValue(Value = 0)] public override int Capacity @@ -45,12 +47,12 @@ public SwitchParameter CloneValues [Parameter(Position = 0)] [ArgumentToTypeTransform] - public Type KeyType { get; set; } = null!; + public Type? KeyType { get; set; } = null!; [Parameter(Position = 1)] [ArgumentToTypeTransform] [PSDefaultValue(Value = typeof(object))] - public Type ValueType { get; set; } = null!; + public Type? ValueType { get; set; } = null!; [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = JUST_COPY)] [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = AND_COPY)] @@ -72,7 +74,7 @@ public SwitchParameter CloneValues [PSDefaultValue(Value = ActionPreference.Stop)] public override ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.Stop; - protected override void Process(IDictionary collection, Type collectionType) + protected override bool Process(IDictionary collection, Type collectionType) { if (null != this.InputObject && this.InputObject.Count > 0) { @@ -85,9 +87,14 @@ protected override void Process(IDictionary collection, Type collectionType) this.AddToCollection(collection, args, false); } } + + return true; } - protected override void End(IDictionary collection) + protected override void End(IDictionary collection, bool wantsToStop) { + if (wantsToStop) + return; + this.WriteObject(collection, false); } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewHashSetCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewHashSetCmdlet.cs index 9c042f6..c83647d 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewHashSetCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewHashSetCmdlet.cs @@ -12,6 +12,8 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Construct { [Cmdlet(VerbsCommon.New, "HashSet", DefaultParameterSetName = "None")] @@ -37,12 +39,12 @@ public sealed class NewHashSetCmdlet : EqualityConstructingCmdlet, IDyna public object[] InputObject { get; set; } = null!; [Parameter(Mandatory = true, ParameterSetName = WITH_CUSTOM_EQUALITY)] - [ValidateScriptVariable(PSComparingVariable.X, PSComparingVariable.LEFT)] - [ValidateScriptVariable(PSComparingVariable.Y, PSComparingVariable.RIGHT)] + [ValidateScriptVariable(PSComparingVariable.X, PSComparingVariable.LEFT, PSThisVariable.ARGS_FIRST)] + [ValidateScriptVariable(PSComparingVariable.Y, PSComparingVariable.RIGHT, PSThisVariable.ARGS_SECOND)] public ScriptBlock EqualityScript { get; set; } = null!; [Parameter(Mandatory = true, ParameterSetName = WITH_CUSTOM_EQUALITY)] - [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME)] + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.ARGS_FIRST)] public ScriptBlock HashCodeScript { get; set; } = null!; [Parameter(ParameterSetName = WITH_CUSTOM_EQUALITY)] @@ -51,24 +53,44 @@ public sealed class NewHashSetCmdlet : EqualityConstructingCmdlet, IDyna #region PROCESSING - protected override void Process(object collection, Type collectionType) + protected override bool Process(object collection, Type collectionType) { - if (this.InputObject is null) + bool flag = true; + if (this.InputObject is null || this.InputObject.Length == 0) { - return; + return flag; } object?[] args = new object[1]; foreach (object? item in this.InputObject) { - args[0] = item; - this.AddToCollection(collection, args, (x, types) => - LanguagePrimitives.ConvertTo(x, types[0])); + try + { + args[0] = item; + this.AddToCollection(collection, args, (x, types) => + LanguagePrimitives.ConvertTo(x, types[0])); + } + catch (PSInvalidCastException e) + { + var rec = e.ToRecord(ErrorCategory.InvalidArgument, item); + this.WriteError(rec); + } + catch (Exception e) + { + var rec = e.ToRecord(ErrorCategory.InvalidOperation, item); + this.WriteError(rec); + flag = false; + } } + + return flag; } - protected override void End(object collection) + protected override void End(object collection, bool wantsToStop) { + if (wantsToStop) + return; + this.WriteObject(collection, false); } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewListCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewListCmdlet.cs index 86de729..c0c90c5 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewListCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewListCmdlet.cs @@ -9,7 +9,10 @@ using System.Management.Automation; using System.Reflection; using System.Runtime.CompilerServices; +using ZLinq; +using AllowsNullAttribute = System.Diagnostics.CodeAnalysis.AllowNullAttribute; using PSAllowNullAttribute = System.Management.Automation.AllowNullAttribute; +#nullable enable namespace ListFunctions.Cmdlets.Construct { @@ -18,44 +21,52 @@ namespace ListFunctions.Cmdlets.Construct public sealed class NewListCmdlet : ListFunctionCmdletBase { internal static readonly Type ListTypeNoT = typeof(List<>); + private static readonly object[] _defaultCapacityArgs = new[] { (object)4 }; - IList _list = null!; - bool _listIsNull; - + private bool _isObjectType; + private IList _list = Array.Empty(); + private bool _listIsNull; + private Type? _genericType; [Parameter(Position = 1)] - [PSDefaultValue(Value = 0)] + [Alias("Size"), PSDefaultValue(Value = 4), ValidateRange(0, int.MaxValue)] public int Capacity { get; set; } [Parameter(Position = 0)] - [Alias("Type")] - [ArgumentToTypeTransform] - [PSDefaultValue(Value = typeof(object))] - public Type GenericType { get; set; } = null!; + [Alias("Type"), ArgumentToTypeTransform, PSDefaultValue(Value = typeof(object))] + [AllowsNull, PSAllowNull] + public Type GenericType + { + get => _genericType ??= this.SetToObjectType(); + set => _genericType = this.SetToObjectType(value); + } [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = "InitialAdd")] - [AllowEmptyCollection] - [PSAllowNull] - public object[] InputObject { get; set; } = null!; + [AllowEmptyCollection, PSAllowNull, AllowEmptyString] + public object?[]? InputObject { get; set; } [Parameter(ParameterSetName = "InitialAdd")] + [Alias("IncludeNulls")] public SwitchParameter IncludeNullElements { get; set; } - protected override void BeginProcessing() + protected override void BeginCore() { - _list = this.GenericType is null - ? new List(this.Capacity) + _list = _genericType is null + ? new List(this.Capacity > 0 ? this.Capacity : 4) : this.CreateNewList(this.Capacity, this.GenericType, out _listIsNull)!; } private IList? CreateNewList(int capacity, Type genericType, out bool listIsNull) { Type listType = ListTypeNoT.MakeGenericType(genericType); - object[] args = new object[] { capacity }; - listIsNull = false; + + object[] args = capacity > 0 + ? new[] { (object)capacity } + : _defaultCapacityArgs; try { + listIsNull = false; return (IList)Activator.CreateInstance(listType, args)!; } catch (Exception e) @@ -66,31 +77,83 @@ protected override void BeginProcessing() return null; } } + private Type SetToObjectType() + { + _isObjectType = true; + return typeof(object); + } + private Type SetToObjectType(Type? type) + { + if (type is null) + return this.SetToObjectType(); + + else if (typeof(object).Equals(type)) + _isObjectType = true; + + else + _isObjectType = false; + + return type; + } - protected override void ProcessRecord() + protected override bool ProcessCore() { - if (_listIsNull || this.InputObject is null || this.InputObject.Length <= 0) + bool flag = true; + if (_listIsNull || this.InputObject is null || this.InputObject.Length == 0) { - return; + return flag; } - Type genericType = this.GenericType; + try + { + if (_isObjectType) + { + this.AddItemsToList(_list, this.InputObject); + } + else + { + this.AddTypedItemsToList(_list, this.InputObject, this.GenericType); + } + + return flag; + } + catch + { + flag = false; + throw; + } + } + private void AddItemsToList(IList list, object?[] items) + { + foreach (object? item in items.AsValueEnumerable()) + { + if (item is null && !this.IncludeNullElements) + { + continue; + } - foreach (object? item in this.InputObject) + list.Add(item); + } + } + private void AddTypedItemsToList(IList list, object?[] items, Type type) + { + foreach (object? item in items.AsValueEnumerable()) { if (item is null && !this.IncludeNullElements) { continue; } - else if (this.TryConvertItem(item, genericType, out object? result)) + + if (this.TryConvertItem(item, type, out object? result)) { - _list.Add(result); + list.Add(result); } } } - protected override void EndProcessing() + + protected override void EndCore(bool wantsToStop) { - if (!_listIsNull) + if (!wantsToStop && !_listIsNull) { this.WriteObject(_list, false); } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewSortedSetCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewSortedSetCmdlet.cs index e3dbe88..625f125 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewSortedSetCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Constructs/NewSortedSetCmdlet.cs @@ -10,6 +10,8 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Constructs { [Cmdlet(VerbsCommon.New, "SortedSet", DefaultParameterSetName = "None")] @@ -28,8 +30,8 @@ public sealed class NewSortedSetCmdlet : ListFunctionCmdletBase public Type GenericType { get; set; } = null!; [Parameter(Mandatory = true, ParameterSetName = WITH_CUSTOM_EQUALITY)] - [ValidateScriptVariable(PSComparingVariable.X, PSComparingVariable.LEFT)] - [ValidateScriptVariable(PSComparingVariable.Y, PSComparingVariable.RIGHT)] + [ValidateScriptVariable(PSComparingVariable.X, PSComparingVariable.LEFT, PSThisVariable.ARGS_FIRST)] + [ValidateScriptVariable(PSComparingVariable.Y, PSComparingVariable.RIGHT, PSThisVariable.ARGS_SECOND)] public ScriptBlock ComparingScript { get; set; } = null!; [Parameter(ValueFromPipeline = true)] @@ -39,7 +41,7 @@ public sealed class NewSortedSetCmdlet : ListFunctionCmdletBase [PSDefaultValue(Value = ActionPreference.Stop)] public ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.Stop; - protected override void BeginProcessing() + protected override void BeginCore() { this.GenericType ??= typeof(object); IComparer? comparer = this.GetCustomComparer(this.GenericType); @@ -48,11 +50,12 @@ protected override void BeginProcessing() _set = _ctor.Construct(); } - protected override void ProcessRecord() + protected override bool ProcessCore() { - if (this.InputObject is null || this.InputObject.Length <= 0) + bool flag = true; + if (this.InputObject is null || this.InputObject.Length == 0) { - return; + return flag; } _addMethod ??= new AddMethodInvoker(_ctor); @@ -71,10 +74,15 @@ protected override void ProcessRecord() this.WriteError(caught.ToRecord(ErrorCategory.InvalidType, item)); } } + + return flag; } - protected override void EndProcessing() + protected override void EndCore(bool wantsToStop) { - this.WriteObject(_set, false); + if (!wantsToStop) + { + this.WriteObject(_set); + } } private IEnumerable GetAction() diff --git a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdlet.cs index 8bf7cbd..bcf823b 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdlet.cs @@ -1,4 +1,7 @@ using ListFunctions.Extensions; +using ListFunctions.Modern; +using ListFunctions.Modern.Pools; +using ListFunctions.Modern.Variables; using ListFunctions.Validation; using System; using System.Collections; @@ -8,64 +11,75 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Finds { [Cmdlet(VerbsCommon.Find, "IndexOf")] [Alias("Find-Index", "IndexOf")] [OutputType(typeof(int))] - public sealed class FindIndexCmdlet : FindIndexCmdletBase + public sealed class FindIndexCmdlet : ListFunctionCmdletBase { + private ScriptBlockFilter _filter = null!; + private int _currentIndex; + private List _list = null!; + [Parameter(Mandatory = true, Position = 0)] [Alias("ScriptBlock")] - public override ScriptBlock Condition { get; set; } = null!; + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.ARGS_FIRST)] + public ScriptBlock Condition { get; set; } = null!; [Parameter(Mandatory = true, ValueFromPipeline = true)] [Alias("List")] - [AllowEmptyCollection] - [ValidateNotNull] - [ListTransform] - public override IList InputObject { get; set; } = null!; - - [Parameter] - public override ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; + [AllowEmptyCollection, AllowEmptyString, AllowNull] + public object?[]? InputObject { get; set; } - static readonly Lazy> _indexMethods = - new Lazy>(BuildMethodCache); + [Parameter, Alias("ScriptErrorAction")] + public ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; - protected override MethodInfo GetFindIndexMethod(Type listType, Type genericType) + protected override void BeginCore() { - var list = new List(); + _filter = new ScriptBlockFilter(this.Condition, new PSVariable(ERROR_ACTION_PREFERENCE, this.ScriptBlockErrorAction)); + _list = ListPool.Rent(); + } + protected override bool ProcessCore() + { + if (this.InputObject is null || this.InputObject.Length == 0) + return true; // keep going - if (_indexMethods.IsValueCreated - && - _indexMethods.Value.TryGetValue(genericType, out MethodInfo? info)) + for (int i = 0; i < this.InputObject.Length; i++) { - return info; + if (_filter.IsTrue(this.InputObject[i])) + { + _currentIndex += i; + return false; // stop processing + } } - info = GetIndexMethodDefinition(listType, genericType); - - Debug.Assert(!(info is null)); - _indexMethods.Value.TryAdd(genericType, info); - - return info; + _currentIndex += this.InputObject.Length; + return true; } - private static MethodInfo GetIndexMethodDefinition(Type listType, Type genericType) + protected override void EndCore(bool wantsToStop) { - Type genPred = PredicateType.MakeGenericType(genericType); + int index = wantsToStop ? _currentIndex : -1; - return listType.GetMethod( - name: nameof(List.FindIndex), - bindingAttr: BindingFlags.Instance | BindingFlags.Public, - binder: null, - types: new Type[] { genPred }, - modifiers: null)!; + this.WriteObject(index); } - private static Dictionary BuildMethodCache() + protected override void Cleanup() { - return new Dictionary(3); + if (!(_filter is null)) + { + _filter.Dispose(); + _filter = null!; + } + + if (!(_list is null)) + { + ListPool.Return(_list); + _list = null!; + } } } } diff --git a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdletBase.cs b/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdletBase.cs deleted file mode 100644 index e1e0451..0000000 --- a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindIndexCmdletBase.cs +++ /dev/null @@ -1,121 +0,0 @@ -using ListFunctions.Extensions; -using ListFunctions.Internal; -using ListFunctions.Modern; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Reflection; - -namespace ListFunctions.Cmdlets.Finds -{ - public abstract class FindIndexCmdletBase : ListFunctionCmdletBase - { - protected static readonly Type PredicateType = typeof(Predicate<>); - - bool _wasList; - Type _genType = null!; - IList _list = null!; - MethodInfo _method = null!; - - public abstract ScriptBlock Condition { get; set; } - public abstract IList InputObject { get; set; } - public abstract ActionPreference ScriptBlockErrorAction { get; set; } - - protected sealed override void BeginProcessing() - { - if (!(this.InputObject is null)) - { - Type listType = this.InputObject.GetType(); - _genType = listType.GetGenericArguments().First(); - _method = this.GetFindIndexMethod(listType, _genType); - _list = this.InputObject; - _wasList = true; - } - } - protected sealed override void ProcessRecord() - { - if (_wasList || this.InputObject is null) - { - return; - } - - if (_genType is null) - { - _list = this.GetListAndGeneric(this.InputObject); - } - else - { - foreach (object? item in this.InputObject) - { - _list.Add(item); - } - } - } - protected sealed override void EndProcessing() - { - var filter = ScriptBlockFilter.Create(this.Condition, _genType, this.EnumerateVariables()); - object predicate = ScriptBlockFilter.ToPredicate(filter); - - int index = -1; - try - { - index = (int?)_method.Invoke(_list, new object[] { predicate }) ?? -1; - } - catch (Exception e) - { - this.WriteError(e.ToRecord(ErrorCategory.InvalidOperation, _list)); - } - - this.WriteObject(index); - } - protected abstract MethodInfo GetFindIndexMethod(Type listType, Type genericType); - - private static void AddListToList(ref IList inputObject, ref List list) - { - if (inputObject is PipelineItem pi) - { - list.Add(pi.Value); - } - else - { - foreach (object? item in inputObject) - { - list.Add(item); - } - } - } - private IEnumerable EnumerateVariables() - { - return new PSVariable[] { new PSVariable(ERROR_ACTION_PREFERENCE, this.ScriptBlockErrorAction) }; - } - private IList GetListAndGeneric(IList inputObject) - { - IList returnList; - Type listType; - Type genType; - - if (inputObject.IsFixedSize) - { - genType = typeof(object); - var list = new List(inputObject.Count); - AddListToList(ref inputObject, ref list); - - returnList = list; - listType = list.GetType(); - } - else - { - listType = inputObject.GetType(); - genType = listType.GetGenericArguments().First(); - returnList = inputObject; - } - - _genType = genType; - _method = this.GetFindIndexMethod(listType, _genType); - return returnList; - } - } -} - diff --git a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindLastIndexCmdlet.cs b/src/engine/ListFunctions-Next/Cmdlets/Finds/FindLastIndexCmdlet.cs index e2e5f0b..099c01c 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/Finds/FindLastIndexCmdlet.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/Finds/FindLastIndexCmdlet.cs @@ -1,4 +1,7 @@ using ListFunctions.Extensions; +using ListFunctions.Modern; +using ListFunctions.Modern.Pools; +using ListFunctions.Modern.Variables; using ListFunctions.Validation; using System; using System.Collections; @@ -8,62 +11,75 @@ using System.Management.Automation; using System.Reflection; +#nullable enable + namespace ListFunctions.Cmdlets.Finds { [Cmdlet(VerbsCommon.Find, "LastIndexOf")] [Alias("Find-LastIndex", "LastIndexOf")] [OutputType(typeof(int))] - public sealed class FindLastIndexCmdlet : FindIndexCmdletBase + public sealed class FindLastIndexCmdlet : ListFunctionCmdletBase { + private ScriptBlockFilter _filter = null!; + private List _list = null!; + [Parameter(Mandatory = true, Position = 0)] [Alias("ScriptBlock")] - public override ScriptBlock Condition { get; set; } = null!; + [ValidateScriptVariable(PSThisVariable.UNDERSCORE_NAME, PSThisVariable.THIS_NAME, PSThisVariable.PSITEM_NAME, PSThisVariable.ARGS_FIRST)] + public ScriptBlock Condition { get; set; } = null!; [Parameter(Mandatory = true, ValueFromPipeline = true)] [Alias("List")] - [AllowEmptyCollection] - [ValidateNotNull] - [ListTransform] - public override IList InputObject { get; set; } = null!; + [AllowEmptyCollection, AllowNull, AllowEmptyString] + public object?[]? InputObject { get; set; } [Parameter] - public override ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; + public ActionPreference ScriptBlockErrorAction { get; set; } = ActionPreference.SilentlyContinue; - static readonly Lazy> _indexMethods = - new Lazy>(BuildMethodCache); - - protected override MethodInfo GetFindIndexMethod(Type listType, Type genericType) + protected override void BeginCore() + { + _filter = new ScriptBlockFilter(this.Condition, new PSVariable(ERROR_ACTION_PREFERENCE, this.ScriptBlockErrorAction)); + _list = ListPool.Rent(); + } + protected override bool ProcessCore() { - if (_indexMethods.IsValueCreated - && - _indexMethods.Value.TryGetValue(genericType, out MethodInfo? info)) + if (!(this.InputObject is null)) { - return info; + _list.AddRange(this.InputObject); } - info = GetIndexMethodDefinition(listType, genericType); - - Debug.Assert(!(info is null)); - _indexMethods.Value.TryAdd(genericType, info); - - return info; + return true; } - - private static MethodInfo GetIndexMethodDefinition(Type listType, Type genericType) + protected override void EndCore(bool wantsToStop) { - Type genPred = PredicateType.MakeGenericType(genericType); + if (wantsToStop) + return; - return listType.GetMethod( - name: nameof(List.FindLastIndex), - bindingAttr: BindingFlags.Instance | BindingFlags.Public, - binder: null, - types: new Type[] { genPred }, - modifiers: null)!; + for (int i = _list.Count - 1; i >= 0; i--) + { + if (_filter.IsTrue(_list[i])) + { + this.WriteObject(i); + return; + } + } + + this.WriteObject(-1); } - private static Dictionary BuildMethodCache() + protected override void Cleanup() { - return new Dictionary(3); + if (!(_filter is null)) + { + _filter.Dispose(); + _filter = null!; + } + + if (!(_list is null)) + { + ListPool.Return(_list); + _list = null!; + } } } } diff --git a/src/engine/ListFunctions-Next/Cmdlets/ListFunctionCmdletBase.cs b/src/engine/ListFunctions-Next/Cmdlets/ListFunctionCmdletBase.cs index 199decd..5f5343f 100644 --- a/src/engine/ListFunctions-Next/Cmdlets/ListFunctionCmdletBase.cs +++ b/src/engine/ListFunctions-Next/Cmdlets/ListFunctionCmdletBase.cs @@ -1,9 +1,12 @@ using ListFunctions.Exceptions; using ListFunctions.Extensions; using System; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +#nullable enable + namespace ListFunctions.Cmdlets { public abstract class ListFunctionCmdletBase : PSCmdlet @@ -13,6 +16,82 @@ public abstract class ListFunctionCmdletBase : PSCmdlet protected const string ERROR_ACTION = "ErrorAction"; protected const string ERROR_ACTION_PREFERENCE = ERROR_ACTION + PREFERENCE; + private bool _wantsToStop; + + protected sealed override void BeginProcessing() + { + try + { + this.BeginCore(); + } + catch + { + this.CleanupCore(); + _wantsToStop = true; + throw; + } + } + protected sealed override void ProcessRecord() + { + if (_wantsToStop) + return; + + try + { + _wantsToStop = !this.ProcessCore(); + } + catch + { + try + { + this.StopProcessing(); + } + catch + { + throw; + } + finally + { + this.CleanupCore(); + _wantsToStop = true; + } + } + } + protected sealed override void EndProcessing() + { + try + { + this.EndCore(_wantsToStop); + } + finally + { + this.CleanupCore(); + } + } + protected virtual void BeginCore() + { + } + protected abstract bool ProcessCore(); + protected virtual void EndCore(bool wantsToStop) + { + } + + private void CleanupCore() + { + try + { + this.Cleanup(); + } + catch (Exception e) + { + Debug.Fail(e.Message); + } + } + protected virtual void Cleanup() + { + // Override to implement custom cleanup logic + } + protected ActionPreference GetErrorPreference() { if (!this.MyInvocation.BoundParameters.TryGetValue(ERROR_ACTION, out object? errorObj)) diff --git a/src/engine/ListFunctions-Next/Debug.ps1 b/src/engine/ListFunctions-Next/Debug.ps1 index 9491254..21f9276 100644 --- a/src/engine/ListFunctions-Next/Debug.ps1 +++ b/src/engine/ListFunctions-Next/Debug.ps1 @@ -15,7 +15,7 @@ param ( [Parameter(Mandatory=$false)] [string[]] $CopyToOutput = @( - 'MG.Collections' + 'ZLinq' ) ) @@ -48,7 +48,7 @@ foreach ($toCopy in $CopyToOutput) } $name, $version = $dependency.Name -split '\/' - if ([string]::IsNullOrEmpty($name) -or [string]::IsNullOrEmpty($version)) { + if ([string]::IsNullOrWhitespace($name) -or [string]::IsNullOrWhitespace($version)) { Write-Warning "Unable to parse name and version from '$toCopy'." continue @@ -61,7 +61,7 @@ foreach ($toCopy in $CopyToOutput) continue; } - $mems = $pso | Get-Member -MemberType NoteProperty | Where { $_.Name -clike "lib/*" } + $mems = $pso | Get-Member -MemberType NoteProperty | Where-Object { $_.Name -clike "lib/*" } foreach ($mem in $mems) { $fileName = [System.IO.Path]::GetFileName($mem.Name) @@ -82,7 +82,7 @@ $otherDll = "$PSScriptRoot\ListFunctions.Engine.dll" if (-not (Test-Path -Path $otherDll -PathType Leaf)) { throw "The specified module DLL was not found -> `"$otherDll`"" } -Import-Module $otherDll -ErrorAction Stop +Import-Module $otherDll -ErrorAction Stop -Force $dllPath = "$PSScriptRoot\$LibraryName.dll" if (-not (Test-Path -Path $dllPath -PathType Leaf)) { @@ -91,12 +91,6 @@ if (-not (Test-Path -Path $dllPath -PathType Leaf)) { if ($PSCmdlet.ShouldProcess($dllPath, "Importing Module")) { - Import-Module $dllPath -ErrorAction Stop + Import-Module $dllPath -ErrorAction Stop -Force Push-Location $myDesktop -} - -$psAll = @( - [pscustomobject] @{ Name = "mike" }, - [pscustomobject] @{ Name = "frank"}, - $null -) +} \ No newline at end of file diff --git a/src/engine/ListFunctions-Next/DuplicateKeyBehavior.cs b/src/engine/ListFunctions-Next/DuplicateKeyBehavior.cs new file mode 100644 index 0000000..22c462e --- /dev/null +++ b/src/engine/ListFunctions-Next/DuplicateKeyBehavior.cs @@ -0,0 +1,10 @@ +namespace ListFunctions +{ + public enum DuplicateKeyBehavior + { + Error, + Skip, + Concatenate, + } +} + diff --git a/src/engine/ListFunctions-Next/Exceptions/LFInvalidCastException.cs b/src/engine/ListFunctions-Next/Exceptions/LFInvalidCastException.cs index 661d7b2..a39baa4 100644 --- a/src/engine/ListFunctions-Next/Exceptions/LFInvalidCastException.cs +++ b/src/engine/ListFunctions-Next/Exceptions/LFInvalidCastException.cs @@ -43,32 +43,11 @@ public LFInvalidCastException(PSInvalidCastException inner, Type convertingTo, o this.HelpLink = inner.HelpLink; } -#if NET6_0_OR_GREATER - const string RECOM_ACT = "Validate that the object being passed can be converted to \""; - private static string ConstructRecommendedAction(Type convertingTo) - { - string name = convertingTo.GetTypeName(); - int length = RECOM_ACT.Length + name.Length + 2; - - return string.Create(length, name, (chars, state) => - { - RECOM_ACT.AsSpan().CopyTo(chars); - int pos = RECOM_ACT.Length; - - state.AsSpan().CopyTo(chars.Slice(pos)); - pos += state.Length; - - chars[pos++] = '"'; - chars[pos++] = '.'; - }); - } -#else const string RECOM_ACT = "Validate that the object being passed can be converted to \"{0}\"."; private static string ConstructRecommendedAction(Type convertingTo) { return string.Format(RECOM_ACT, convertingTo.GetTypeName()); } -#endif private static string FormatMessage(Exception inner, object? item, Type convertingTo, out string? itemAsStr, out string? itemType) { diff --git a/src/engine/ListFunctions-Next/ListFunctions-Next.csproj b/src/engine/ListFunctions-Next/ListFunctions-Next.csproj index 8ff046e..9652df7 100644 --- a/src/engine/ListFunctions-Next/ListFunctions-Next.csproj +++ b/src/engine/ListFunctions-Next/ListFunctions-Next.csproj @@ -1,10 +1,10 @@  - net6.0 + net9.0 Mike Garvey Yevrag35, LLC. - Copyright © Yevrag35, LLC. 2020-2023 All rights reserved. + Copyright © Yevrag35, LLC. 2020-2025 All rights reserved. https://github.com/Yevrag35/PowerShell-ListFunctions/blob/master/.icon/list-functions.png?raw=true https://github.com/Yevrag35/PowerShell-ListFunctions https://raw.githubusercontent.com/Yevrag35/PowerShell-ListFunctions/master/LICENSE @@ -12,25 +12,28 @@ ListFunctions ListFunctions ListFunctions.Next + true disable - enable - 2.0.0 - 2.0.0 - 2.0.0 + disable + 3.1.0 + 3.1.0 + 3.1.0 - - - - - - - Always - - + + + Always + + + + + Never + Never + + - + diff --git a/src/engine/ListFunctions-Next/Validation/ArgumentToTypeNameTransformAttribute.cs b/src/engine/ListFunctions-Next/Validation/ArgumentToTypeNameTransformAttribute.cs index e0d4c8a..2293103 100644 --- a/src/engine/ListFunctions-Next/Validation/ArgumentToTypeNameTransformAttribute.cs +++ b/src/engine/ListFunctions-Next/Validation/ArgumentToTypeNameTransformAttribute.cs @@ -4,8 +4,17 @@ using System.Management.Automation.Internal; using System.Management.Automation.Language; +#nullable enable + namespace ListFunctions.Validation { + /// + /// Provides a mechanism to transform an input argument into a .NET object. + /// + /// This attribute is used to convert various input formats, such as , , or , into a corresponding instance. If the input cannot be resolved to a valid type, the transformation defaults to . internal sealed class ArgumentToTypeTransformAttribute : ArgumentTransformationAttribute { const string PSREADLINE = "PSReadLine"; @@ -40,7 +49,7 @@ private static Type ResolveFromAst(Ast ast, PSModuleInfo? runningModule) } catch (ParseException e) { - if (!(runningModule is null) && PSREADLINE.Equals(runningModule.Name, StringComparison.InvariantCultureIgnoreCase)) + if (!(runningModule is null) && PSREADLINE.Equals(runningModule.Name, StringComparison.OrdinalIgnoreCase)) { return typeof(object); } @@ -63,7 +72,7 @@ private static Type ResolveFromName(string typeName, PSModuleInfo? runningModule } catch (ParseException e) { - if (!(runningModule is null) && PSREADLINE.Equals(runningModule.Name, StringComparison.InvariantCultureIgnoreCase)) + if (!(runningModule is null) && PSREADLINE.Equals(runningModule.Name, StringComparison.OrdinalIgnoreCase)) { return typeof(object); } diff --git a/src/engine/ListFunctions-Next/Validation/ListTransformAttribute.cs b/src/engine/ListFunctions-Next/Validation/ListTransformAttribute.cs index b72be2f..5e4c0a3 100644 --- a/src/engine/ListFunctions-Next/Validation/ListTransformAttribute.cs +++ b/src/engine/ListFunctions-Next/Validation/ListTransformAttribute.cs @@ -8,8 +8,18 @@ using System.Linq; using System.Management.Automation; +#nullable enable + namespace ListFunctions.Validation { + /// + /// Specifies that the target property or field should be transformed into a list representation when used in a + /// PowerShell pipeline or similar context. + /// + /// This attribute is applied to properties or fields to ensure that input data is converted into + /// a list-like structure. If the input data is already a generic list, it is returned as-is. If the input data is + /// an enumerable type, its elements are collected into a new list. Otherwise, the input data is wrapped in a object. [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class ListTransformAttribute : ArgumentTransformationAttribute { diff --git a/src/engine/ListFunctions-Next/Validation/ValidateScriptVariableAttribute.cs b/src/engine/ListFunctions-Next/Validation/ValidateScriptVariableAttribute.cs index e6f9e01..efee450 100644 --- a/src/engine/ListFunctions-Next/Validation/ValidateScriptVariableAttribute.cs +++ b/src/engine/ListFunctions-Next/Validation/ValidateScriptVariableAttribute.cs @@ -1,16 +1,28 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using ZLinq; + +#nullable enable namespace ListFunctions.Validation { + /// + /// Specifies that a script block must contain at least one of the specified variable names to be considered valid. + /// + /// This attribute is used to validate that a script block contains at least one of the required + /// variable names. If the script block does not include any of the specified variables, a is thrown during validation. [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = false)] public sealed class ValidateScriptVariableAttribute : ValidateArgumentsAttribute { - readonly HashSet _names; - public string[] MustContainAny { get; } + public const string Args = "args"; + + private HashSet? _mustContainIndexes; + private HashSet? _mustContainNames; public ValidateScriptVariableAttribute(params string[] variableNames) { @@ -19,42 +31,176 @@ public ValidateScriptVariableAttribute(params string[] variableNames) throw new ArgumentNullException(nameof(variableNames)); } - _names = new HashSet(StringComparer.InvariantCultureIgnoreCase); - if (variableNames.Length <= 0) + if (variableNames.Length == 0) { throw new ArgumentException("Must contain at least 1 variable name."); } - this.MustContainAny = variableNames; + HashSet? indexes = null; + HashSet? names = null; + + foreach (string name in variableNames) + { + if (name.StartsWith(Args, StringComparison.OrdinalIgnoreCase) && TryParseIndexFromName(name, out int index)) + { + indexes ??= new HashSet(); + _ = indexes.Add(index); + } + else + { + names ??= new HashSet(StringComparer.OrdinalIgnoreCase); + _ = names.Add(name); + } + } + + _mustContainIndexes = indexes; + _mustContainNames = names; } protected override void Validate(object arguments, EngineIntrinsics engineIntrinsics) { - _names.Clear(); + if (arguments is string str) + { + arguments = ScriptBlock.Create(str); + } + if (!(arguments is ScriptBlock block)) { return; } - IEnumerable allVars = EnumerateAllVariablesInBlock(block); + if (!IsAllValid(block, _mustContainNames, _mustContainIndexes)) + { + throw new ValidationMetadataException($"At least one of the following variables must be included in the script block: ${string.Join(", $", (IEnumerable?)_mustContainNames ?? Array.Empty())}"); + } + //EnumerateAllVariablesInBlock(block); + + //if (DoesNotContainAny(ref allVars, this.MustContainAny)) + //{ + //} + } + +// private static bool DoesNotContainAny(ref +//#if NET5_0_OR_GREATER +// ValueEnumerable, Ast, VariableExpressionAst>, VariableExpressionAst, string>, string> +//#else +// IEnumerable +//#endif +// allVars, string[] mustContainAny) +// { +// foreach (string foundVarName in allVars) +// { +// if (ValidArgsIndex.Args.Equals(foundVarName, StringComparison.OrdinalIgnoreCase)) +// { +// return false; +// } - _names.UnionWith(allVars); +// foreach (string mustVarName in mustContainAny) +// { +// if (mustVarName.Equals(foundVarName, StringComparison.OrdinalIgnoreCase)) +// { +// return false; +// } +// } +// } - if (!_names.Overlaps(this.MustContainAny)) +// return true; +// } + + private static bool IsAllValid(ScriptBlock block, HashSet? mustContainNames, HashSet? mustContainIndexes) + { + if (mustContainIndexes is null && mustContainNames is null) { - throw new ValidationMetadataException($"At least one of the following variables must be included in the script block: ${string.Join(", $", this.MustContainAny)}"); + throw new ArgumentException("At least one of the parameters must be non-null.", nameof(mustContainNames)); } + + bool allowsArgs = !(mustContainIndexes is null || mustContainIndexes.Count == 0); + + IEnumerable asts = allowsArgs + ? block.Ast.FindAll(searchNestedScriptBlocks: false, predicate: x => IsVariableAst(x) || IsArgsIndexed(x)) + : block.Ast.FindAll(searchNestedScriptBlocks: false, predicate: IsVariableAst); + + return allowsArgs + ? IsValidWithIndexes(asts, mustContainNames, mustContainIndexes!) + : IsValidNoIndexes(asts, mustContainNames!); } - private static IEnumerable EnumerateAllVariablesInBlock(ScriptBlock block) + private static bool IsVariableAst(Ast ast) { - return block.Ast.FindAll(x => - x is VariableExpressionAst varAst - && !varAst.IsConstantVariable() + return ast is VariableExpressionAst varAst + && !varAst.IsConstantVariable() && !varAst.Splatted - && varAst.VariablePath.IsVariable, true) - .OfType() - .Select(x => x.VariablePath.UserPath); + && varAst.VariablePath.IsVariable + && !Args.Equals(varAst.VariablePath.UserPath, StringComparison.OrdinalIgnoreCase); + } + private static bool IsArgsIndexed(Ast ast) + { + return ast is IndexExpressionAst indexAst + && indexAst.Target is VariableExpressionAst varAst + && varAst.VariablePath.IsVariable + && Args.Equals(varAst.VariablePath.UserPath, StringComparison.OrdinalIgnoreCase); + } + + private static bool IsValidWithIndexes(IEnumerable asts, HashSet? anyNames, HashSet orAnyIndexes) + { + bool isNotNull = !(anyNames is null || anyNames.Count == 0); + foreach (Ast ast in asts.AsValueEnumerable()) + { + if (ast is VariableExpressionAst varAst && isNotNull && anyNames!.Contains(varAst.VariablePath.UserPath)) + { + return true; + } + else if (ast is IndexExpressionAst indexAst && indexAst.Index is ConstantExpressionAst constAst && constAst.Value is int index + && + orAnyIndexes.Contains(index)) + { + return true; + } + } + + return false; + } + private static bool IsValidNoIndexes(IEnumerable asts, HashSet anyNames) + { + foreach (VariableExpressionAst varAst in asts.AsValueEnumerable()) + { + if (anyNames.Contains(varAst.VariablePath.UserPath)) + { + return true; + } + } + + return false; + } + + private static bool TryParseIndexFromName(string name, out int parsedIndex) + { +#if NETCOREAPP + ReadOnlySpan span = name.AsSpan(Args.Length); + if (span[0] == '[' && span[^1] == ']') + { + span = span.Slice(1, span.Length - 2); + } + + if (int.TryParse(span, out int index) && index >= 0) + { + parsedIndex = index; + return true; + } +#else + string trimmed = name[Args.Length] == '[' && name[name.Length - 1] == ']' + ? name.Substring(Args.Length + 1, name.Length - Args.Length - 2) + : name.Substring(Args.Length); + + if (int.TryParse(trimmed, out int index) && index >= 0) + { + parsedIndex = index; + return true; + } +#endif + + parsedIndex = default; + return false; } } } diff --git a/src/engine/ListFunctions.Engine/Debug.ps1 b/src/engine/ListFunctions.Engine/Debug.ps1 index 2408ea4..57082f2 100644 --- a/src/engine/ListFunctions.Engine/Debug.ps1 +++ b/src/engine/ListFunctions.Engine/Debug.ps1 @@ -63,35 +63,7 @@ foreach ($toCopy in $CopyToOutput) } } -Import-Module "$PSScriptRoot\$LibraryName.dll" -ErrorAction Stop -Verbose +Import-Module "$PSScriptRoot\$LibraryName.dll" -ErrorAction Stop -Force $myDesktop = [System.Environment]::GetFolderPath("Desktop") -Push-Location $myDesktop - -$o1 = [pscustomobject]@{ - Hi = "asdf" - Bye = "jkl;" -} -$o2 = [pscustomobject]@{ - Hi = "asdf" - Bye = "jkl;" -} -$o3 = [pscustomobject]@{ - Hi = "1234" - Bye = "too late" -} -$o4 = [pscustomobject]@{ - Hi = "" - Bye = "too late again" -} -$o5 = [pscustomobject]@{ - Hi = $null - Bye = "too late" -} - -$eqScr = { $x.Hi -eq $y.Hi } -#$hashScr = { } -$compar = { $x.Hi.CompareTo($y.Hi) } - -$eq = New-Object "ListFunctions.ScriptBlockEqualityComparer[object]"($eqScr, $hashScr) -$cm = New-Object "ListFunctions.ScriptBlockComparer[object]"($compar) \ No newline at end of file +Push-Location $myDesktop \ No newline at end of file diff --git a/src/engine/ListFunctions.Engine/Exceptions/ListFunctionsException.cs b/src/engine/ListFunctions.Engine/Exceptions/ListFunctionsException.cs new file mode 100644 index 0000000..108a12f --- /dev/null +++ b/src/engine/ListFunctions.Engine/Exceptions/ListFunctionsException.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ListFunctions.Exceptions +{ + public class ListFunctionsException : Exception + { + public ListFunctionsException(string message, Exception? innerException) + : base(message, innerException) + { + } + } +} + diff --git a/src/engine/ListFunctions.Engine/Extensions/DictionaryExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/DictionaryExtensions.cs index 1dc7bec..eac677c 100644 --- a/src/engine/ListFunctions.Engine/Extensions/DictionaryExtensions.cs +++ b/src/engine/ListFunctions.Engine/Extensions/DictionaryExtensions.cs @@ -1,7 +1,4 @@ -using ListFunctions.Internal; -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; namespace ListFunctions.Extensions { diff --git a/src/engine/ListFunctions.Engine/Extensions/EnumerableExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..91b00f9 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Extensions/EnumerableExtensions.cs @@ -0,0 +1,26 @@ +#if !NETCOREAPP +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; + +namespace ZLinq +{ + public static class EnumerableExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable AsValueEnumerable(this IEnumerable source) + { + return source; + } + + public static IEnumerable AsValueEnumerable(this IEnumerable source) + { + return source.Cast(); + } + } +} + +#endif \ No newline at end of file diff --git a/src/engine/ListFunctions.Engine/Extensions/ExceptionExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/ExceptionExtensions.cs index 007474f..189f946 100644 --- a/src/engine/ListFunctions.Engine/Extensions/ExceptionExtensions.cs +++ b/src/engine/ListFunctions.Engine/Extensions/ExceptionExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; diff --git a/src/engine/ListFunctions.Engine/Extensions/ObjectExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/ObjectExtensions.cs new file mode 100644 index 0000000..3950c60 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Extensions/ObjectExtensions.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; +using System.Reflection; +using System.Text; + +#nullable enable + +namespace ListFunctions.Extensions +{ + public static class ObjectExtensions + { + [return: NotNullIfNotNull(nameof(obj))] + internal static object? CloneIf(this object? obj) + { + if (obj is ICloneable cloneable) + { + return cloneable.Clone(); + } + else if (obj is PSObject pso) + { + return pso.Copy(); + } + else if (obj is PSCustomObject customObj) + { + return PSObject.AsPSObject(customObj).Copy(); + } + else if (obj is PSMemberInfo member) + { + return member.Copy(); + } + else + { + return obj; + } + } + + public static object?[] DeepClone(this object?[]? source) + { + if (source is null || source.Length == 0) + return Array.Empty(); + + return Array.ConvertAll(source, CloneIf); + } + } +} + diff --git a/src/engine/ListFunctions.Engine/Extensions/PSObjectExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/PSObjectExtensions.cs index 7f4d4cf..b5b838c 100644 --- a/src/engine/ListFunctions.Engine/Extensions/PSObjectExtensions.cs +++ b/src/engine/ListFunctions.Engine/Extensions/PSObjectExtensions.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; diff --git a/src/engine/ListFunctions.Engine/Extensions/PSVariableCollectionExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/PSVariableCollectionExtensions.cs index 79225be..3ecb739 100644 --- a/src/engine/ListFunctions.Engine/Extensions/PSVariableCollectionExtensions.cs +++ b/src/engine/ListFunctions.Engine/Extensions/PSVariableCollectionExtensions.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Management.Automation; -using System.Management.Automation.Internal; namespace ListFunctions.Extensions { @@ -14,7 +12,7 @@ public static class PSVariableCollectionExtensions [return: NotNullIfNotNull(nameof(defaultIfNull))] public static T GetFirstValue(this Collection? collection, Func convert, T defaultIfNull = default!) { - if (collection is null || collection.Count <= 0 || !collection[0].TryGetBaseObject(out object? o)) + if (collection is null || collection.Count == 0 || collection[0] is not PSObject pso || !pso.TryGetBaseObject(out object? o)) { return defaultIfNull; } @@ -33,10 +31,10 @@ public static T GetFirstValue(this Collection? collection, Func(this Collection? collection, Func convert) { - if (collection is null || collection.Count <= 0) + if (collection is null || collection.Count == 0) return default; - object? last = collection.LastOrDefault()?.ImmediateBaseObject; + object? last = collection[collection.Count - 1]?.ImmediateBaseObject; if (last is null) return default; diff --git a/src/engine/ListFunctions.Engine/Extensions/ScriptBlockExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/ScriptBlockExtensions.cs new file mode 100644 index 0000000..80c192f --- /dev/null +++ b/src/engine/ListFunctions.Engine/Extensions/ScriptBlockExtensions.cs @@ -0,0 +1,41 @@ +using ListFunctions.Modern.Variables; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Language; +using System.Text.RegularExpressions; +using ZLinq; + +#nullable enable + +namespace ListFunctions.Extensions +{ + public static partial class ScriptBlockExtensions + { + public static ScriptBlock ReplaceWithArgsZero(this ScriptBlock scriptBlock) + { + Guard.NotNull(scriptBlock, nameof(scriptBlock)); + + string script = scriptBlock.ToString(); + string newScript = ReplaceString(script); + + return !string.Equals(script, newScript, StringComparison.OrdinalIgnoreCase) + ? ScriptBlock.Create(newScript) + : scriptBlock; + } + + private static string ReplaceString(string script) + { +#if !NETCOREAPP + return Regex.Replace(script, @"\$(?:(?:_|PSItem|this)(\s|\;|$|\.))", "$args[0]$1", RegexOptions.IgnoreCase); + } +#else + return ReplaceDefaultNames().Replace(script, "$args[0]$1"); + } + + [GeneratedRegex(@"\$(?:(?:_|PSItem|this)(\s|\;|$|\.))", RegexOptions.IgnoreCase, "en-US")] + private static partial Regex ReplaceDefaultNames(); +#endif + } +} diff --git a/src/engine/ListFunctions.Engine/Extensions/SetExtensions.cs b/src/engine/ListFunctions.Engine/Extensions/SetExtensions.cs new file mode 100644 index 0000000..8952c52 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Extensions/SetExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ZLinq; +#if NET5_0_OR_GREATER +using ZLinq.Linq; + +namespace ZLinq +{ + public static class SetExtensions + { + public static void UnionWithRef(this HashSet set, ref ValueEnumerable collection) + where TEnumerator : struct, IValueEnumerator +#if NET8_0_OR_GREATER + , allows ref struct +#endif + { + foreach (T item in collection) + { + _ = set.Add(item); + } + } + } +} +#endif \ No newline at end of file diff --git a/src/engine/ListFunctions.Engine/Internal/DoubleBool.cs b/src/engine/ListFunctions.Engine/Internal/DoubleBool.cs index 5cea0d6..280a548 100644 --- a/src/engine/ListFunctions.Engine/Internal/DoubleBool.cs +++ b/src/engine/ListFunctions.Engine/Internal/DoubleBool.cs @@ -1,6 +1,4 @@ -using System; - -namespace ListFunctions.Internal +namespace ListFunctions.Internal { internal ref struct DoubleBool { @@ -15,6 +13,6 @@ private DoubleBool(bool initialize) internal static DoubleBool InitializeNew() => new DoubleBool(false); - public static implicit operator bool(DoubleBool dub) => dub.Bool1 && dub.Bool2; + public static implicit operator bool(DoubleBool dub) => dub.Bool1 & dub.Bool2; } } diff --git a/src/engine/ListFunctions.Engine/Internal/Empty.cs b/src/engine/ListFunctions.Engine/Internal/Empty.cs index bdec581..9e874f7 100644 --- a/src/engine/ListFunctions.Engine/Internal/Empty.cs +++ b/src/engine/ListFunctions.Engine/Internal/Empty.cs @@ -1,24 +1,25 @@ -using ListFunctions.Internal; -using MG.Collections; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.InteropServices; +using ZLinq; -namespace ListFunctions +namespace ListFunctions.Internal { - public static class Empty + public static class Empty { - public static IReadOnlyList List => Array.Empty(); - public static IReadOnlySet Set => ReadOnlyEmpty.Default; - } - public static class Empty where TKey : notnull - { - public static IReadOnlyDictionary Dictionary => ReadOnlyEmpty.Default; - } + public static IReadOnlySet Set() => EmptyHolder.Default; + public static IReadOnlyDictionary Dictionary() where TKey : notnull => EmptyHolder.Default; - internal readonly struct ReadOnlyEmpty : IReadOnlyList, IReadOnlySet, IReadOnlyDictionary + private static class EmptyHolder where TKey : notnull + { + public static readonly ReadOnlyEmpty Default = new(); + } + } + [StructLayout(LayoutKind.Sequential)] + internal sealed class ReadOnlyEmpty : IReadOnlyList, IReadOnlySet, IReadOnlyDictionary where TKey : notnull { TValue IReadOnlyDictionary.this[TKey key] @@ -33,10 +34,12 @@ TValue IReadOnlyDictionary.this[TKey key] TValue IReadOnlyList.this[int index] => default!; public int Count => 0; - IEnumerable IReadOnlyDictionary.Keys => Enumerable.Empty(); - IEnumerable IReadOnlyDictionary.Values => Enumerable.Empty(); + IEnumerable IReadOnlyDictionary.Keys => Array.Empty(); + IEnumerable IReadOnlyDictionary.Values => Array.Empty(); - internal static ReadOnlyEmpty Default => default; + internal ReadOnlyEmpty() + { + } public bool Contains(TValue item) { @@ -55,7 +58,7 @@ public IEnumerator GetEnumerator() public bool IsProperSubsetOf(IEnumerable other) { - return other.Any(); + return other.AsValueEnumerable().Any(); } public bool IsProperSupersetOf(IEnumerable other) @@ -80,7 +83,7 @@ public bool Overlaps(IEnumerable other) public bool SetEquals(IEnumerable other) { - return !other.Any(); + return !other.AsValueEnumerable().Any(); } public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue value) diff --git a/src/engine/ListFunctions.Engine/Internal/EnumerableExtensions.cs b/src/engine/ListFunctions.Engine/Internal/EnumerableExtensions.cs index 8754bc4..054c76b 100644 --- a/src/engine/ListFunctions.Engine/Internal/EnumerableExtensions.cs +++ b/src/engine/ListFunctions.Engine/Internal/EnumerableExtensions.cs @@ -4,54 +4,14 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace ListFunctions.Internal { internal static class EnumerableExtensions { - const string COUNT = "Count"; - - internal static bool TryGetCount(this IEnumerable nonGenCollection, out int count) - { - if (nonGenCollection is ICollection icol) - { - count = icol.Count; - return true; - } - else if (nonGenCollection is Array arr) - { - count = arr.Length; - return true; - } - - Type type = nonGenCollection.GetType(); - PropertyInfo? countProp; - try - { - countProp = type.GetProperty(COUNT, BindingFlags.Public | BindingFlags.Instance, null, typeof(int), Array.Empty(), Array.Empty()); - - if (countProp is null) - { - count = 0; - return false; - } - - if (countProp.GetValue(nonGenCollection) is int number) - { - count = number; - return true; - } - } - catch (Exception e) - { - Debug.Fail(e.Message); - } - - count = 0; - return false; - } - #if NET5_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool TryGetCount(this IEnumerable enumerable, out int count) { return enumerable.TryGetNonEnumeratedCount(out count); @@ -59,14 +19,23 @@ internal static bool TryGetCount(this IEnumerable enumerable, out int coun #else internal static bool TryGetCount(this IEnumerable collection, out int count) { - if (collection is ICollection icol) + switch (collection) { - count = icol.Count; - return true; - } - else - { - return TryGetCount(nonGenCollection: collection, out count); + case IReadOnlyCollection roCol: + count = roCol.Count; + return true; + + case ICollection icol: + count = icol.Count; + return true; + + case ICollection nonGenCol: + count = nonGenCol.Count; + return true; + + default: + count = 0; + return false; } } #endif diff --git a/src/engine/ListFunctions.Engine/Internal/EqualityExtensions.cs b/src/engine/ListFunctions.Engine/Internal/EqualityExtensions.cs index ff0eab1..5ee5be5 100644 --- a/src/engine/ListFunctions.Engine/Internal/EqualityExtensions.cs +++ b/src/engine/ListFunctions.Engine/Internal/EqualityExtensions.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Diagnostics; -using System.Text; namespace ListFunctions.Internal { diff --git a/src/engine/ListFunctions.Engine/Internal/PipelineItem.cs b/src/engine/ListFunctions.Engine/Internal/PipelineItem.cs index 8f91729..c12db94 100644 --- a/src/engine/ListFunctions.Engine/Internal/PipelineItem.cs +++ b/src/engine/ListFunctions.Engine/Internal/PipelineItem.cs @@ -1,40 +1,58 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; namespace ListFunctions.Internal { - public readonly struct PipelineItem : IList + /// + /// Represents a single item in a pipeline, which can be treated as a collection with at most one element. + /// + /// This struct provides a way to encapsulate a single object as a collection-like structure. It + /// implements and interfaces, allowing it to be used in + /// scenarios where a collection is expected, but only one item (or none) is present. The item can be accessed via + /// the property or through the collection interfaces. If the item is null, the collection is + /// considered empty. + [StructLayout(LayoutKind.Sequential)] + public readonly struct PipelineItem : IList, IReadOnlyList { - readonly bool _isNotEmpty; - //readonly Type? _type; readonly object? _value; + object? IReadOnlyList.this[int index] + { + get + { + if (index != 0) + throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be 0."); + + return _value; + } + } object? IList.this[int index] { - get => index == 0 ? _value : throw new ArgumentOutOfRangeException("index"); + get => index == 0 ? _value : throw new ArgumentOutOfRangeException(nameof(index)); set => throw new NotSupportedException(); } - int ICollection.Count => _isNotEmpty ? 1 : 0; + int IReadOnlyCollection.Count => _value is null ? 0 : 1; + int ICollection.Count => _value is null ? 0 : 1; bool ICollection.IsSynchronized => false; bool IList.IsReadOnly => true; bool IList.IsFixedSize => true; object ICollection.SyncRoot => this; - //public Type Type => _type ?? typeof(object); #if NET6_0_OR_GREATER [MemberNotNullWhen(false, nameof(_value), nameof(Value))] #endif - public bool IsEmpty => !_isNotEmpty; + public bool IsEmpty => _value is null; public object? Value => _value; public PipelineItem(object? item) { _value = item; - _isNotEmpty = !(item is null); - //_type = item?.GetType() ?? typeof(object); } int IList.Add(object? value) => throw new NotSupportedException(); @@ -45,9 +63,13 @@ public void AddToList(ref IList list) void IList.Clear() => throw new NotSupportedException(); public bool Contains(object? value) { - return (value is null && this.IsEmpty) - || - (_isNotEmpty && _value!.Equals(value)); + if (this.IsEmpty) + return value is null; + + else if (value is null) + return false; + + return _value!.Equals(value); } public void CopyTo(Array array, int index) { @@ -60,25 +82,65 @@ public void CopyTo(Array array, int index) } int IList.IndexOf(object? value) { - if (this.IsEmpty && value is null) + return this.Contains(value) ? 0 : -1; + } + void IList.Insert(int index, object? value) => throw new NotSupportedException(); + void IList.Remove(object? value) => throw new NotSupportedException(); + void IList.RemoveAt(int index) => throw new NotSupportedException(); + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An enumerator that can be used to iterate through the collection. + public Enumerator GetEnumerator() + { + return new Enumerator(_value); + } + /// + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + /// + [DebuggerStepThrough] + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + public struct Enumerator : IEnumerator + { + private short _index; + + public object? Current { get; } + + internal Enumerator(object? item) { - return 0; + this.Current = item; + _index = -1; } - else if (_isNotEmpty && _value!.Equals(value)) + + readonly void IDisposable.Dispose() { - return 0; + return; } - else + + public bool MoveNext() { - return -1; + if (_index == -1) + { + _index = 0; + return true; + } + + _index = 1; + return false; + } + + void IEnumerator.Reset() + { + _index = -1; } - } - void IList.Insert(int index, object value) => throw new NotSupportedException(); - void IList.Remove(object? value) => throw new NotSupportedException(); - void IList.RemoveAt(int index) => throw new NotSupportedException(); - public IEnumerator GetEnumerator() - { - yield return _value; } } } diff --git a/src/engine/ListFunctions.Engine/Internal/ReadOnlySet.cs b/src/engine/ListFunctions.Engine/Internal/ReadOnlySet.cs new file mode 100644 index 0000000..71658a5 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Internal/ReadOnlySet.cs @@ -0,0 +1,217 @@ +#if !NETSTANDARD2_0 +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IReadOnlySet<>))] +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.ObjectModel.ReadOnlySet<>))] + +#else + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace System.Collections.Generic +{ + public interface IReadOnlySet : IReadOnlyCollection + { + // + // Summary: + // Determines if the set contains a specific item. + // + // Parameters: + // item: + // The item to check if the set contains. + // + // Returns: + // true if found; otherwise false. + bool Contains(T item); + // + // Summary: + // Determines whether the current set is a proper (strict) subset of a specified + // collection. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the current set is a proper subset of other; otherwise false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool IsProperSubsetOf(IEnumerable other); + // + // Summary: + // Determines whether the current set is a proper (strict) superset of a specified + // collection. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the collection is a proper superset of other; otherwise false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool IsProperSupersetOf(IEnumerable other); + // + // Summary: + // Determine whether the current set is a subset of a specified collection. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the current set is a subset of other; otherwise false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool IsSubsetOf(IEnumerable other); + // + // Summary: + // Determine whether the current set is a super set of a specified collection. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the current set is a subset of other; otherwise false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool IsSupersetOf(IEnumerable other); + // + // Summary: + // Determines whether the current set overlaps with the specified collection. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the current set and other share at least one common element; otherwise, + // false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool Overlaps(IEnumerable other); + // + // Summary: + // Determines whether the current set and the specified collection contain the same + // elements. + // + // Parameters: + // other: + // The collection to compare to the current set. + // + // Returns: + // true if the current set is equal to other; otherwise, false. + // + // Exceptions: + // T:System.ArgumentNullException: + // other is null. + bool SetEquals(IEnumerable other); + } +} +namespace System.Collections.ObjectModel +{ + public sealed class ReadOnlySet : IReadOnlySet, ISet + { + readonly HashSet _set; + public ReadOnlySet(ISet set) + { + _set = new HashSet(set); + } + public int Count => _set.Count; + + public bool IsReadOnly => true; + + void ICollection.Add(T item) + { + throw new NotSupportedException(); + } + bool ISet.Add(T item) + { + throw new NotImplementedException(); + } + + void ICollection.Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(T item) => _set.Contains(item); + + public void CopyTo(T[] array, int arrayIndex) + { + _set.CopyTo(array, arrayIndex); + } + + void ISet.ExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() => _set.GetEnumerator(); + + void ISet.IntersectWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + return _set.IsProperSubsetOf(other); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + return _set.IsProperSupersetOf(other); + } + + public bool IsSubsetOf(IEnumerable other) + { + return _set.IsSubsetOf(other); + } + + public bool IsSupersetOf(IEnumerable other) + { + return _set.IsSupersetOf(other); + } + + public bool Overlaps(IEnumerable other) + { + return _set.Overlaps(other); + } + + bool ICollection.Remove(T item) + { + throw new NotSupportedException(); + } + + public bool SetEquals(IEnumerable other) + { + return _set.SetEquals(other); + } + + void ISet.SymmetricExceptWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + void ISet.UnionWith(IEnumerable other) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} + +#endif \ No newline at end of file diff --git a/src/engine/ListFunctions.Engine/Internal/ScriptBlockExtensions.cs b/src/engine/ListFunctions.Engine/Internal/ScriptBlockExtensions.cs index f5ffef6..c3c6b71 100644 --- a/src/engine/ListFunctions.Engine/Internal/ScriptBlockExtensions.cs +++ b/src/engine/ListFunctions.Engine/Internal/ScriptBlockExtensions.cs @@ -3,7 +3,6 @@ using System.Collections.ObjectModel; using System.Management.Automation.Language; using System.Management.Automation; -using System.Runtime.CompilerServices; using ListFunctions.Extensions; using System.Diagnostics.CodeAnalysis; @@ -32,7 +31,7 @@ internal static bool IsProperScriptBlock(this ScriptBlock scriptBlock) } ReadOnlyCollection statements = scriptAst.EndBlock.Statements; - if (statements.Count <= 0) + if (statements.Count == 0) { return false; } diff --git a/src/engine/ListFunctions.Engine/Internal/SingleValueReadOnlySet.cs b/src/engine/ListFunctions.Engine/Internal/SingleValueReadOnlySet.cs index 4b4c9a5..74636a6 100644 --- a/src/engine/ListFunctions.Engine/Internal/SingleValueReadOnlySet.cs +++ b/src/engine/ListFunctions.Engine/Internal/SingleValueReadOnlySet.cs @@ -1,19 +1,32 @@ -using MG.Collections; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; +using ZLinq; namespace ListFunctions.Internal { + internal static class SingleValueReadOnlySet + { + internal static SingleValueReadOnlySet Create(IEnumerable collection, IEqualityComparer? equalityComparer = null) where T : notnull + { + return new SingleValueReadOnlySet(collection.AsValueEnumerable().First(), equalityComparer); + } + internal static SingleValueReadOnlySet Create(T value, IEqualityComparer? equalityComparer = null) where T : notnull + { + return new SingleValueReadOnlySet(value, equalityComparer); + } + } + + [StructLayout(LayoutKind.Sequential)] internal readonly struct SingleValueReadOnlySet : IReadOnlySet where T : notnull { readonly IEqualityComparer _equality; - readonly bool _isNotEmpty; readonly T _value; public int Count => 1; - internal bool IsEmpty => !_isNotEmpty; + internal bool IsEmpty => _value is null; internal T Value => _value; public SingleValueReadOnlySet(T value, IEqualityComparer? equalityComparer) @@ -25,7 +38,6 @@ public SingleValueReadOnlySet(T value, IEqualityComparer? equalityComparer) _equality = ResolveComparer(equalityComparer); _value = value; - _isNotEmpty = true; } private static IEqualityComparer ResolveComparer(IEqualityComparer? supplied) @@ -33,7 +45,7 @@ private static IEqualityComparer ResolveComparer(IEqualityComparer? suppli if (supplied is null) { supplied = typeof(string).Equals(typeof(T)) - ? (IEqualityComparer)StringComparer.InvariantCultureIgnoreCase + ? (IEqualityComparer)StringComparer.OrdinalIgnoreCase : EqualityComparer.Default; } @@ -42,12 +54,7 @@ private static IEqualityComparer ResolveComparer(IEqualityComparer? suppli public bool Contains(T item) { - if (this.IsEmpty || item is null) - { - return false; - } - - return _equality.Equals(_value, item); + return !this.IsEmpty && _equality.Equals(_value, item); } public bool IsProperSubsetOf(IEnumerable other) @@ -55,18 +62,14 @@ public bool IsProperSubsetOf(IEnumerable other) Guard.NotNull(other, nameof(other)); if (this.IsEmpty) { - return other.Any(); + return other.AsValueEnumerable().Any(); } + var enumerator = other.AsValueEnumerable().GetEnumerator(); DoubleBool dub = DoubleBool.InitializeNew(); - foreach (T item in other) + while (!dub && enumerator.MoveNext()) { - if (dub) - { - return true; - } - - if (_equality.Equals(_value, item)) + if (_equality.Equals(_value, enumerator.Current)) { dub.Bool1 = true; } @@ -87,7 +90,7 @@ public bool IsProperSupersetOf(IEnumerable other) return false; } - return !other.Any(); + return !other.AsValueEnumerable().Any(); } public bool IsSubsetOf(IEnumerable other) @@ -98,9 +101,9 @@ public bool IsSubsetOf(IEnumerable other) return true; } - foreach (T item in other) + foreach (T item in other.AsValueEnumerable()) { - if (!(item is null) && _equality.Equals(_value, item)) + if (_equality.Equals(_value, item)) { return true; } @@ -114,12 +117,12 @@ public bool IsSupersetOf(IEnumerable other) Guard.NotNull(other, nameof(other)); if (this.IsEmpty) { - return !other.Any(); + return !other.AsValueEnumerable().Any(); } - foreach (T item in other) + foreach (T item in other.AsValueEnumerable()) { - if (item is null || !_equality.Equals(_value, item)) + if (!_equality.Equals(_value, item)) { return false; } @@ -136,9 +139,9 @@ public bool Overlaps(IEnumerable other) return false; } - foreach (T item in other) + foreach (T item in other.AsValueEnumerable()) { - if (!(item is null) && _equality.Equals(_value, item)) + if (_equality.Equals(_value, item)) { return true; } @@ -152,7 +155,7 @@ public bool SetEquals(IEnumerable other) Guard.NotNull(other, nameof(other)); if (this.IsEmpty) { - return !other.Any(); + return !other.AsValueEnumerable().Any(); } if (other.TryGetCount(out int count) && count != 1) @@ -160,28 +163,53 @@ public bool SetEquals(IEnumerable other) return false; } - foreach (T item in other) - { - count++; - if (count != 1 || item is null || !_equality.Equals(_value, item)) - { - return false; - } - } - - return count == 1; + return other.AsValueEnumerable().SequenceEqual(this, _equality); } - public IEnumerator GetEnumerator() + public Enumerator GetEnumerator() { - if (_isNotEmpty) - { - yield return _value; - } + return new(_value); + } + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } + + public struct Enumerator : IEnumerator + { + readonly T _value; + int _index; + public readonly T Current => _value; + readonly object? IEnumerator.Current => this.Current; + + internal Enumerator(T value) + { + _value = value; + _index = -1; + } + + public bool MoveNext() + { + if (_index == -1) + { + _index = 0; + return true; + } + + _index = 1; + return false; + } + public void Reset() + { + _index = -1; + } + public readonly void Dispose() + { + } + } } } diff --git a/src/engine/ListFunctions.Engine/Legacy/ComparingContext.cs b/src/engine/ListFunctions.Engine/Legacy/ComparingContext.cs index a9d7643..c6486c7 100644 --- a/src/engine/ListFunctions.Engine/Legacy/ComparingContext.cs +++ b/src/engine/ListFunctions.Engine/Legacy/ComparingContext.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Management.Automation; -using System.Text; namespace ListFunctions.Legacy { diff --git a/src/engine/ListFunctions.Engine/Legacy/HashCodeContext.cs b/src/engine/ListFunctions.Engine/Legacy/HashCodeContext.cs index beb3bb4..c149ee8 100644 --- a/src/engine/ListFunctions.Engine/Legacy/HashCodeContext.cs +++ b/src/engine/ListFunctions.Engine/Legacy/HashCodeContext.cs @@ -1,7 +1,5 @@ -using System; using System.Collections.Generic; using System.Management.Automation; -using System.Text; namespace ListFunctions.Legacy { diff --git a/src/engine/ListFunctions.Engine/Legacy/ScriptBlockComparer.cs b/src/engine/ListFunctions.Engine/Legacy/ScriptBlockComparer.cs index 2fa16cb..06ad72e 100644 --- a/src/engine/ListFunctions.Engine/Legacy/ScriptBlockComparer.cs +++ b/src/engine/ListFunctions.Engine/Legacy/ScriptBlockComparer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Text; namespace ListFunctions.Legacy { diff --git a/src/engine/ListFunctions.Engine/Legacy/ScriptBlockEqualityComparer.cs b/src/engine/ListFunctions.Engine/Legacy/ScriptBlockEqualityComparer.cs index 81ac848..e0c9183 100644 --- a/src/engine/ListFunctions.Engine/Legacy/ScriptBlockEqualityComparer.cs +++ b/src/engine/ListFunctions.Engine/Legacy/ScriptBlockEqualityComparer.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Management.Automation; namespace ListFunctions.Legacy diff --git a/src/engine/ListFunctions.Engine/ListFunctions.Engine.csproj b/src/engine/ListFunctions.Engine/ListFunctions.Engine.csproj index 15daa04..b6dadce 100644 --- a/src/engine/ListFunctions.Engine/ListFunctions.Engine.csproj +++ b/src/engine/ListFunctions.Engine/ListFunctions.Engine.csproj @@ -1,36 +1,43 @@ - netstandard2.0;net6.0 + netstandard2.0;net9.0 enable ListFunctions ListFunctions.Engine Mike Garvey Yevrag35, LLC. - Copyright © 2022-2024 Yevrag35, LLC. All rights reserved. + Copyright © 2022-2025 Yevrag35, LLC. All rights reserved. https://github.com/Yevrag35/PowerShell-ListFunctions.git Git https://github.com/Yevrag35/PowerShell-ListFunctions - 2.0.0 + 3.1.0 ListFunctions.Engine - 2.0.0 - 2.0.0 + 3.1.0 + 3.1.0 + true + false ListFunctions.Engine - .NET Standard 2.0 ListFunctions.Engine - .NET Standard 2.0 - 8.0 + 10.0 ListFunctions.Engine - .NET 6 ListFunctions.Engine - .NET 6 - Latest + latest ListFunctions.Engine - .NET 8 ListFunctions.Engine - .NET 8 - Latest + latest + + + ListFunctions.Engine - .NET 9 + ListFunctions.Engine - .NET 9 + preview @@ -42,6 +49,12 @@ portable + + + <_Parameter1>ListFunctions.Next + + + PreserveNewest @@ -56,13 +69,16 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/engine/ListFunctions.Engine/Modern/AddMethodInvoker.cs b/src/engine/ListFunctions.Engine/Modern/AddMethodInvoker.cs index f367b6f..552c018 100644 --- a/src/engine/ListFunctions.Engine/Modern/AddMethodInvoker.cs +++ b/src/engine/ListFunctions.Engine/Modern/AddMethodInvoker.cs @@ -1,7 +1,5 @@ using ListFunctions.Modern.Constructors; using System; -using System.Collections; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; diff --git a/src/engine/ListFunctions.Engine/Modern/ComparingBase.cs b/src/engine/ListFunctions.Engine/Modern/ComparingBase.cs index d0037b4..98e99cd 100644 --- a/src/engine/ListFunctions.Engine/Modern/ComparingBase.cs +++ b/src/engine/ListFunctions.Engine/Modern/ComparingBase.cs @@ -1,12 +1,10 @@ using ListFunctions.Internal; using System; -using System.Collections.Generic; using System.Management.Automation; -using System.Text; namespace ListFunctions.Modern { - public abstract class ComparingBase + public abstract class ComparingBase { public ScriptBlock Script { get; } diff --git a/src/engine/ListFunctions.Engine/Modern/ComparingBlock.cs b/src/engine/ListFunctions.Engine/Modern/ComparingBlock.cs index 3f7ed62..fe5176f 100644 --- a/src/engine/ListFunctions.Engine/Modern/ComparingBlock.cs +++ b/src/engine/ListFunctions.Engine/Modern/ComparingBlock.cs @@ -8,6 +8,7 @@ using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; +using ZLinq; namespace ListFunctions.Modern { @@ -21,7 +22,8 @@ public static class ComparingBlock public static IComparer Create(ScriptBlock scriptBlock, Type genericType, IEnumerable? additionalVariables) { MethodInfo genMeth = _getInit.Value.MakeGenericMethod(genericType); - return (IComparer)genMeth.Invoke(null, new object[] { scriptBlock, additionalVariables! }); + return genMeth.Invoke(null, new object[] { scriptBlock, additionalVariables! }) as IComparer + ?? throw new InvalidOperationException("Unable to create generic comparing block instance."); } public static ComparingBlock Create(ScriptBlock scriptBlock, IEnumerable? additionalVariables) { @@ -36,16 +38,15 @@ private static MethodInfo InitializeLazyMethod() } } - public sealed class ComparingBlock : ComparingBase, IComparer, IComparingBlock + public sealed class ComparingBlock : ComparingBase, IComparer, IComparingBlock { readonly PSVariable[] _additionalVariables; - readonly Type _checkingType; readonly ScriptBlock _compareScript; readonly PSComparingVariable _left; readonly PSComparingVariable _right; readonly List _varList; - Type IComparingBlock.ChecksType => _checkingType; + Type IComparingBlock.ChecksType => typeof(T); public T CurrentLeft => _left.Value; public T CurrentRight => _right.Value; @@ -65,16 +66,15 @@ internal ComparingBlock(ScriptBlock scriptBlock, bool preValidated, IEnumerable< { _additionalVariables = additionalVariables is null ? Array.Empty() - : additionalVariables.ToArray(); + : additionalVariables.AsValueEnumerable().ToArray(); - _checkingType = typeof(T); _varList = new List(4); - _left = PSComparingVariable.Left(); - _right = PSComparingVariable.Right(); + _left = PSComparingVariable.Left(); + _right = PSComparingVariable.Right(); _compareScript = scriptBlock; } - public int Compare(T left, T right) + public int Compare(T? left, T? right) { if (left is null && right is null) { diff --git a/src/engine/ListFunctions.Engine/Modern/Constructors/DictionaryCtor.cs b/src/engine/ListFunctions.Engine/Modern/Constructors/DictionaryCtor.cs index 026bf86..f40b6cc 100644 --- a/src/engine/ListFunctions.Engine/Modern/Constructors/DictionaryCtor.cs +++ b/src/engine/ListFunctions.Engine/Modern/Constructors/DictionaryCtor.cs @@ -1,12 +1,8 @@ -using ListFunctions.Extensions; -using ListFunctions.Modern.Exceptions; using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; namespace ListFunctions.Modern.Constructors { @@ -26,8 +22,8 @@ public DictionaryCtor(IEqualityComparer? comparer, Type? keyType, Type? valueTyp protected override Hashtable ConstructTDefault(IEqualityComparer comparer) { var comp = this.IsCaseSensitive - ? StringComparer.InvariantCulture - : StringComparer.InvariantCultureIgnoreCase; + ? StringComparer.CurrentCulture + : StringComparer.OrdinalIgnoreCase; return new Hashtable(comp); } @@ -46,7 +42,7 @@ protected override bool ShouldConstructDefault(IEqualityComparer? comparer, Type return base.ShouldConstructDefault(comparer, genericTypes) || ( - !(comparer is IEqualityBlock) + comparer is not IEqualityBlock && genericTypes.All(x => ObjectType.Equals(x)) ); diff --git a/src/engine/ListFunctions.Engine/Modern/Constructors/EqualityCollectionCtor.cs b/src/engine/ListFunctions.Engine/Modern/Constructors/EqualityCollectionCtor.cs index c20765b..c1dbf29 100644 --- a/src/engine/ListFunctions.Engine/Modern/Constructors/EqualityCollectionCtor.cs +++ b/src/engine/ListFunctions.Engine/Modern/Constructors/EqualityCollectionCtor.cs @@ -1,11 +1,6 @@ -using ListFunctions.Extensions; -using ListFunctions.Modern.Exceptions; using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; namespace ListFunctions.Modern.Constructors diff --git a/src/engine/ListFunctions.Engine/Modern/Constructors/GenericCollectionCtor.cs b/src/engine/ListFunctions.Engine/Modern/Constructors/GenericCollectionCtor.cs index 1ff39e5..7930f75 100644 --- a/src/engine/ListFunctions.Engine/Modern/Constructors/GenericCollectionCtor.cs +++ b/src/engine/ListFunctions.Engine/Modern/Constructors/GenericCollectionCtor.cs @@ -1,12 +1,10 @@ using ListFunctions.Extensions; using ListFunctions.Modern.Exceptions; using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection; namespace ListFunctions.Modern.Constructors { diff --git a/src/engine/ListFunctions.Engine/Modern/Constructors/SortingCollectorCtor.cs b/src/engine/ListFunctions.Engine/Modern/Constructors/SortingCollectorCtor.cs index d6b3909..3b077bd 100644 --- a/src/engine/ListFunctions.Engine/Modern/Constructors/SortingCollectorCtor.cs +++ b/src/engine/ListFunctions.Engine/Modern/Constructors/SortingCollectorCtor.cs @@ -5,7 +5,6 @@ using System.Linq.Expressions; using System.Management.Automation; using System.Reflection; -using System.Text; namespace ListFunctions.Modern.Constructors { @@ -53,7 +52,8 @@ public IComparer GetComparer() } var genMeth = _getComparerMethod.Value.MakeGenericMethod(_sortedType); - return (IComparer)genMeth.Invoke(null, null); + return genMeth.Invoke(null, null) as IComparer + ?? throw new InvalidOperationException("Failed to get default comparer."); } protected override IEnumerable? GetConstructorArguments(Type[] genericTypes) { diff --git a/src/engine/ListFunctions.Engine/Modern/Conversion.cs b/src/engine/ListFunctions.Engine/Modern/Conversion.cs index 91e30d5..5cdd9ed 100644 --- a/src/engine/ListFunctions.Engine/Modern/Conversion.cs +++ b/src/engine/ListFunctions.Engine/Modern/Conversion.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using System.Reflection; namespace ListFunctions.Modern { diff --git a/src/engine/ListFunctions.Engine/Modern/EqualityBlock.cs b/src/engine/ListFunctions.Engine/Modern/EqualityBlock.cs index b9cd00e..08b2481 100644 --- a/src/engine/ListFunctions.Engine/Modern/EqualityBlock.cs +++ b/src/engine/ListFunctions.Engine/Modern/EqualityBlock.cs @@ -1,6 +1,5 @@ using ListFunctions.Internal; using ListFunctions.Modern.Variables; -using MG.Collections; using System; using System.Collections; using System.Collections.Generic; @@ -8,6 +7,7 @@ using System.Linq; using System.Management.Automation; using System.Reflection; +using ZLinq; namespace ListFunctions.Modern { @@ -34,19 +34,21 @@ public static IEqualityBlock CreateBlock(Type type, IHashCodeBlock hashCodeBlock MethodInfo genMeth = _genMeth.MakeGenericMethod(type); object[] args = new object[] { hashCodeBlock, equalityScript, additionalVariables! }; - return (IEqualityBlock)genMeth.Invoke(null, args); + return genMeth.Invoke(null, args) as IEqualityBlock + ?? throw new InvalidOperationException("Unable to create generic equality block instance."); } - + static readonly MethodInfo _genMeth = typeof(EqualityBlock) - .GetMethod(nameof(CreateGenericBlock), BindingFlags.Static | BindingFlags.NonPublic); + .GetMethod(nameof(CreateGenericBlock), BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("Unable to find generic method definition for CreateGenericBlock."); - private static IEqualityBlock CreateGenericBlock(IHashCodeBlock hashCodeBlock, ScriptBlock equalityScript, IEnumerable? additionalVariables) + private static EqualityBlock CreateGenericBlock(IHashCodeBlock hashCodeBlock, ScriptBlock equalityScript, IEnumerable? additionalVariables) { return new EqualityBlock(equalityScript, hashCodeBlock, additionalVariables); } } - public sealed class EqualityBlock : ComparingBase, IEqualityBlock, IEqualityComparer + public sealed class EqualityBlock : ComparingBase, IEqualityBlock, IEqualityComparer { readonly List _varList; readonly PSVariable[] _additionalVariables; @@ -75,7 +77,7 @@ internal EqualityBlock(ScriptBlock scriptBlock, IHashCodeBlock hashCodeBlock, IE Guard.NotNull(hashCodeBlock, nameof(hashCodeBlock)); _additionalVariables = additionalVariables is null ? Array.Empty() - : additionalVariables.ToArray(); + : additionalVariables.AsValueEnumerable().ToArray(); _checksType = typeof(T); if (!typeof(T).Equals(hashCodeBlock.HashesType)) @@ -85,11 +87,11 @@ internal EqualityBlock(ScriptBlock scriptBlock, IHashCodeBlock hashCodeBlock, IE _hashCodeBlock = hashCodeBlock; _varList = new List(4); - _left = PSComparingVariable.Left(); - _right = PSComparingVariable.Right(); + _left = PSComparingVariable.Left(); + _right = PSComparingVariable.Right(); } - public bool Equals(T x, T y) + public bool Equals([System.Diagnostics.CodeAnalysis.AllowNull] T x, [System.Diagnostics.CodeAnalysis.AllowNull] T y) { if (x is null && y is null) { @@ -114,7 +116,7 @@ bool IEqualityComparer.Equals(object? x, object? y) return true; } - if (TryConvert(x, out T isX) && TryConvert(y, out T isY)) + if (TryConvert(x, out T? isX) && TryConvert(y, out T? isY)) { return this.Equals(isX, isY); } diff --git a/src/engine/ListFunctions.Engine/Modern/Exceptions/ActivatorCtorException.cs b/src/engine/ListFunctions.Engine/Modern/Exceptions/ActivatorCtorException.cs index c5a7a6b..62afa89 100644 --- a/src/engine/ListFunctions.Engine/Modern/Exceptions/ActivatorCtorException.cs +++ b/src/engine/ListFunctions.Engine/Modern/Exceptions/ActivatorCtorException.cs @@ -1,7 +1,5 @@ using ListFunctions.Extensions; using System; -using System.Collections.Generic; -using System.Text; namespace ListFunctions.Modern.Exceptions { diff --git a/src/engine/ListFunctions.Engine/Modern/Exceptions/EqualityScriptException.cs b/src/engine/ListFunctions.Engine/Modern/Exceptions/EqualityScriptException.cs index a023e4e..bfbfceb 100644 --- a/src/engine/ListFunctions.Engine/Modern/Exceptions/EqualityScriptException.cs +++ b/src/engine/ListFunctions.Engine/Modern/Exceptions/EqualityScriptException.cs @@ -1,5 +1,4 @@ -using ListFunctions.Internal; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; diff --git a/src/engine/ListFunctions.Engine/Modern/Exceptions/HashCodeScriptException.cs b/src/engine/ListFunctions.Engine/Modern/Exceptions/HashCodeScriptException.cs index 30479dd..0cd13cc 100644 --- a/src/engine/ListFunctions.Engine/Modern/Exceptions/HashCodeScriptException.cs +++ b/src/engine/ListFunctions.Engine/Modern/Exceptions/HashCodeScriptException.cs @@ -1,5 +1,4 @@ -using ListFunctions.Internal; -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; @@ -28,7 +27,7 @@ private HashCodeScriptException(object? offender, string? scriptStatement, Type? this.HashCodeBlockType = blockType ?? typeof(object); } -//#if !NET8_0_OR_GREATER +#if !NET8_0_OR_GREATER private HashCodeScriptException(SerializationInfo info, StreamingContext context) : base(info, context) { @@ -43,7 +42,7 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont base.GetObjectData(info, context); } -//#endif +#endif public static HashCodeScriptException FromBlockException(Exception exception, [MaybeNull] in T obj) { diff --git a/src/engine/ListFunctions.Engine/Modern/Exceptions/ScriptBlockInvocationException.cs b/src/engine/ListFunctions.Engine/Modern/Exceptions/ScriptBlockInvocationException.cs index 6819d8d..ee253b5 100644 --- a/src/engine/ListFunctions.Engine/Modern/Exceptions/ScriptBlockInvocationException.cs +++ b/src/engine/ListFunctions.Engine/Modern/Exceptions/ScriptBlockInvocationException.cs @@ -1,4 +1,5 @@ -using ListFunctions.Internal; +using ListFunctions.Extensions; +using ListFunctions.Internal; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -64,22 +65,6 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont } #endif - - [return: NotNullIfNotNull(nameof(obj))] - protected static object? CopyObject(object? obj) - { - switch (obj) - { - case ICloneable cloneable: - return cloneable.Clone(); - - case PSObject pso: - return pso.Copy(); - - default: - return obj; - } - } private static string FormatMessage(string message, Exception? inner, string? statement) { if (string.IsNullOrWhiteSpace(statement)) @@ -105,8 +90,7 @@ public static string GetScriptStatement(InvocationInfo info) try { - string? statement = _statementProp.Value.GetValue(info) as string; - return statement ?? info.Line; + return _statementProp.Value.GetValue(info) as string ?? info.Line; } catch (Exception e) { @@ -135,7 +119,7 @@ private static void AddToDict(ref Dictionary dict, PSVariable? return; } - object? val = CopyObject(variable.Value); + object? val = variable.Value.CloneIf(); #if NET5_0_OR_GREATER _ = dict.TryAdd(variable.Name, val); @@ -150,7 +134,7 @@ private static void AddToDict(ref Dictionary dict, PSVariable? { if (variables is null || variables.Count <= 0) { - return Empty.Dictionary; + return Empty.Dictionary(); } var dict = new Dictionary(variables.Count, StringComparer.InvariantCultureIgnoreCase); diff --git a/src/engine/ListFunctions.Engine/Modern/Guard.cs b/src/engine/ListFunctions.Engine/Modern/Guard.cs index db7011e..ba175a3 100644 --- a/src/engine/ListFunctions.Engine/Modern/Guard.cs +++ b/src/engine/ListFunctions.Engine/Modern/Guard.cs @@ -1,33 +1,37 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace ListFunctions { public static class Guard { - /// - public static void NotNull([NotNull] T? value, string? parameterName) where T : class - { - if (value is null) - { - parameterName ??= nameof(value); - throw new ArgumentNullException(parameterName); - } - } - +#if NET5_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public static void NotNull([NotNull] object? obj, string? parameterName) { +#if NET5_0_OR_GREATER + ArgumentNullException.ThrowIfNull(obj, parameterName); +#else if (obj is null) { parameterName ??= nameof(obj); throw new ArgumentNullException(parameterName); } +#endif } /// /// +#if NET5_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif public static void NotNullOrEmpty([NotNull] string? value, string? parameterName) { +#if NET5_0_OR_GREATER + ArgumentException.ThrowIfNullOrEmpty(value, parameterName); +#else parameterName ??= nameof(value); if (value is null) { @@ -37,6 +41,23 @@ public static void NotNullOrEmpty([NotNull] string? value, string? parameterName { throw new ArgumentException($"'{parameterName}' cannot be an empty string.", parameterName); } +#endif + } + +#if NET5_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static void ThrowIfGreaterThanOrEqual(int value, int other, string? parameterName) + { +#if NET5_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(value, other, parameterName); +#else + if (value >= other) + { + parameterName ??= nameof(value); + throw new ArgumentOutOfRangeException(parameterName, value, $"'{parameterName}' must be less than {other}."); + } +#endif } } } diff --git a/src/engine/ListFunctions.Engine/Modern/HashCodeBlock.cs b/src/engine/ListFunctions.Engine/Modern/HashCodeBlock.cs index 9079dc0..b2deff9 100644 --- a/src/engine/ListFunctions.Engine/Modern/HashCodeBlock.cs +++ b/src/engine/ListFunctions.Engine/Modern/HashCodeBlock.cs @@ -2,7 +2,6 @@ using ListFunctions.Internal; using ListFunctions.Modern.Exceptions; using ListFunctions.Modern.Variables; -using MG.Collections; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -16,21 +15,22 @@ public static class HashCodeBlock public static IHashCodeBlock CreateBlock(Type type, ScriptBlock scriptBlock) { MethodInfo genMethod = _genMeth.MakeGenericMethod(type); - object[] args = new object[] { scriptBlock }; + object[] args = new[] { scriptBlock }; - return (IHashCodeBlock)genMethod.Invoke(null, args); + return genMethod.Invoke(null, args) as IHashCodeBlock ?? throw new InvalidOperationException("Unable to create generic hash code block instance."); } static readonly MethodInfo _genMeth = typeof(HashCodeBlock) - .GetMethod(nameof(CreateGenericBlock), BindingFlags.Static | BindingFlags.NonPublic); - private static IHashCodeBlock CreateGenericBlock(ScriptBlock scriptBlock) + .GetMethod(nameof(CreateGenericBlock), BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("Unable to find generic method definition for CreateGenericBlock."); + private static HashCodeBlock CreateGenericBlock(ScriptBlock scriptBlock) { return new HashCodeBlock(scriptBlock); } } - public sealed class HashCodeBlock : ComparingBase, IHashCodeBlock + public sealed class HashCodeBlock : ComparingBase, IHashCodeBlock { - readonly PSThisVariable _thisVar; + readonly PSThisVariable _thisVar; readonly List _varList; readonly Type _hashesType; @@ -40,8 +40,8 @@ public HashCodeBlock(ScriptBlock scriptBlock) : base(scriptBlock, preValidated: false) { _hashesType = typeof(T); - _thisVar = new PSThisVariable(); - _varList = new List(1); + _thisVar = new PSThisVariable(); + _varList = new(); } public int GetHashCode(T obj, IEnumerable additionalVariables) @@ -49,37 +49,41 @@ public int GetHashCode(T obj, IEnumerable additionalVariables) if (obj is null) { var argNull = new ArgumentNullException(nameof(obj)); - throw HashCodeScriptException.FromBlockException(argNull, in obj); + throw HashCodeScriptException.FromBlockException(argNull, obj); } var list = this.SetContextVariables(obj, additionalVariables); if (!this.Script.TryInvokeWithContext(list, x => x, out object? hashObj, out Exception? ex)) { - throw HashCodeScriptException.FromBlockException(ex, in obj, this.SetContextVariables(obj, additionalVariables)); + throw HashCodeScriptException.FromBlockException(ex, obj, this.SetContextVariables(obj, additionalVariables)); } - return hashObj?.GetHashCode() ?? this.ThrowNullHashCode(in obj, additionalVariables); + return hashObj?.GetHashCode() ?? this.ThrowNullHashCode(obj, additionalVariables); } - private List SetContextVariables(T obj, IEnumerable additionalVariables) + private List SetContextVariables(T obj, IEnumerable? additionalVariables) { _varList.Clear(); - _thisVar.AddToVarList(obj, _varList); - _varList.AddRange(additionalVariables); + //_thisVar.AddToVarList(obj, _varList); + _thisVar.SetValue(obj); + _thisVar.InsertIntoList(_varList); + + if (additionalVariables is not null) + _varList.AddRange(additionalVariables); return _varList; } [DoesNotReturn] - private int ThrowNullHashCode(in T obj, IEnumerable additionalVariables) + private int ThrowNullHashCode(T obj, IEnumerable additionalVariables) { var baseEx = new ArgumentOutOfRangeException(nameof(obj), "The hash code script block returned a null value when an non-null one was expected."); - string objStr = obj!.ToString(); + string? objStr = obj?.ToString(); var rec = new ErrorRecord(baseEx, "NullObjHashCode", ErrorCategory.InvalidResult, obj); rec.CategoryInfo.Activity = "GetHashCode(T obj, IEnumerable additionalVariables)"; rec.CategoryInfo.Reason = "A hash code script block should never return a 'null' value."; - rec.CategoryInfo.TargetName = objStr; + rec.CategoryInfo.TargetName = objStr ?? string.Empty; rec.CategoryInfo.TargetType = typeof(T).GetTypeName(); var inner = new RuntimeException($"Unable to retrieve an integer hash code from object: \"{objStr}\" using the supplied script block.", baseEx, rec); @@ -94,7 +98,7 @@ int IHashCodeBlock.GetHashCode(object obj, IEnumerable additionalVar { genObj = tObj; } - else if (LanguagePrimitives.TryConvertTo(obj, out T result)) + else if (LanguagePrimitives.TryConvertTo(obj, out T result) && result is not null) { genObj = result; } diff --git a/src/engine/ListFunctions.Engine/Modern/IResettable.cs b/src/engine/ListFunctions.Engine/Modern/IResettable.cs new file mode 100644 index 0000000..31219f2 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Modern/IResettable.cs @@ -0,0 +1,13 @@ +namespace ListFunctions.Modern +{ + public interface IPoolable : IResettable + { + void Initialize(); + } + + public interface IResettable + { + bool TryReset(); + } +} + diff --git a/src/engine/ListFunctions.Engine/Modern/ObjectList.cs b/src/engine/ListFunctions.Engine/Modern/ObjectList.cs new file mode 100644 index 0000000..229126e --- /dev/null +++ b/src/engine/ListFunctions.Engine/Modern/ObjectList.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ListFunctions.Modern +{ + public sealed class ObjectList : List + { + public ObjectList() : base(2) { } + } +} + diff --git a/src/engine/ListFunctions.Engine/Modern/Pools/ListPool.cs b/src/engine/ListFunctions.Engine/Modern/Pools/ListPool.cs new file mode 100644 index 0000000..147af45 --- /dev/null +++ b/src/engine/ListFunctions.Engine/Modern/Pools/ListPool.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +#nullable enable + +namespace ListFunctions.Modern.Pools +{ +#if !NETCOREAPP + public +#else + internal +#endif + static class ListPool where T : class? + { + private const int DEFAULT_LIST_CAPACITY = 10; + private static readonly Lazy>> _bag = new( + () => new ConcurrentBag>() { new List(DEFAULT_LIST_CAPACITY) }); + + public static List Rent() + { + if (!_bag.Value.TryTake(out var list)) + { + list = new List(DEFAULT_LIST_CAPACITY); + } + + return list; + } + + public static void Return(List list) + { + Guard.NotNull(list, nameof(list)); + list.Clear(); +#if NET5_0_OR_GREATER + list.EnsureCapacity(DEFAULT_LIST_CAPACITY); +#endif + + _bag.Value.Add(list); + } + } +} diff --git a/src/engine/ListFunctions.Engine/Modern/Pools/ObjPool.cs b/src/engine/ListFunctions.Engine/Modern/Pools/ObjPool.cs new file mode 100644 index 0000000..8ae82fb --- /dev/null +++ b/src/engine/ListFunctions.Engine/Modern/Pools/ObjPool.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +#nullable enable + +namespace ListFunctions.Modern.Pools +{ +#if !NETCOREAPP + public +#else + internal +#endif + static class ObjPool where T : class, IPoolable, new() + { + private static readonly Lazy> _bag = new(() => + { + return new ConcurrentBag() { new T() }; + }); + + public static T Rent() + { + if (!_bag.Value.TryTake(out T? item)) + { + item = new T(); + } + + item.Initialize(); + return item; + } + + public static void Return(T returningItem) + { + Guard.NotNull(returningItem, nameof(returningItem)); + if (returningItem.TryReset()) + { + _bag.Value.Add(returningItem); + } + } + } +} + diff --git a/src/engine/ListFunctions.Engine/Modern/ReflectionResolver.cs b/src/engine/ListFunctions.Engine/Modern/ReflectionResolver.cs index 1f8b6eb..7e5fb75 100644 --- a/src/engine/ListFunctions.Engine/Modern/ReflectionResolver.cs +++ b/src/engine/ListFunctions.Engine/Modern/ReflectionResolver.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -15,8 +14,10 @@ public static class ReflectionResolver static ReflectionResolver() { Type type = typeof(ReflectionResolver); - _colAddMethod = type.GetMethod(nameof(GetCollectionAdd)); - _dictAddMethod = type.GetMethod(nameof(GetDictionaryAdd)); + _colAddMethod = type.GetMethod(nameof(GetCollectionAdd)) + ?? throw new InvalidOperationException("Unable to find method definition for GetCollectionAdd."); + _dictAddMethod = type.GetMethod(nameof(GetDictionaryAdd)) + ?? throw new InvalidOperationException("Unable to find method definition for GetDictionaryAdd."); } public static MethodInfo GetAddMethod(Type collectionType, Type[] types) @@ -49,7 +50,7 @@ public static MethodInfo GetAddMethod(Type collectionType, Type[] types) throw new ArgumentException("Type argument exception."); } - return (MethodInfo)getAdd.Invoke(null, null); + return getAdd.Invoke(null, null) as MethodInfo ?? throw new InvalidOperationException("Unable to get Add method."); } public static MethodInfo GetCollectionAdd() where TCol : ICollection diff --git a/src/engine/ListFunctions.Engine/Modern/ScriptBlockFilter.cs b/src/engine/ListFunctions.Engine/Modern/ScriptBlockFilter.cs index 09ba435..3a3f5d6 100644 --- a/src/engine/ListFunctions.Engine/Modern/ScriptBlockFilter.cs +++ b/src/engine/ListFunctions.Engine/Modern/ScriptBlockFilter.cs @@ -1,160 +1,121 @@ -using ListFunctions.Extensions; using ListFunctions.Internal; +using ListFunctions.Modern.Pools; using ListFunctions.Modern.Variables; -using MG.Collections; using System; +using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using System.Reflection; +using ZLinq; + +#nullable enable namespace ListFunctions.Modern { - public abstract class ScriptBlockFilter + public sealed class ScriptBlockFilter : IDisposable { - private protected ScriptBlockFilter() - { - } - - protected abstract object MakePredicate(); - - public static ScriptBlockFilter Create(ScriptBlock scriptBlock, Type genericType, IEnumerable? additionalVariables) - { - Type filterType = typeof(ScriptBlockFilter<>); - Type genFilter = filterType.MakeGenericType(genericType); - - return (ScriptBlockFilter)Activator.CreateInstance(genFilter, - new object[] { scriptBlock, additionalVariables! }); - } - public static object ToPredicate(ScriptBlockFilter genericScriptBlock) - { - return genericScriptBlock.MakePredicate(); - } - } - - public sealed class ScriptBlockFilter : ScriptBlockFilter - { - static readonly IEqualityComparer _equality = new PSVariableNameEquality(); - - readonly IReadOnlySet _additionalVariables; - readonly bool _hasAdditionalVariables; - readonly ScriptBlock _scriptBlock; - Predicate? _predicate; - readonly PSThisVariable _thisVar; - readonly List _varList; - - public ScriptBlockFilter(ScriptBlock scriptBlock) - : this(scriptBlock, null) - { - } - public ScriptBlockFilter(ScriptBlock scriptBlock, IEnumerable? additionalVariables) + private bool _disposed; + private PSThisVariable _constants; + private List _extraVariables; + private readonly ScriptBlock _scriptBlock; + private List _variables; + + public ScriptBlockFilter(ScriptBlock scriptBlock, params +#if NET9_0_OR_GREATER + ReadOnlySpan +#else + PSVariable[] +#endif + additionalVariables) { Guard.NotNull(scriptBlock, nameof(scriptBlock)); - _thisVar = new PSThisVariable(); - _scriptBlock = scriptBlock; - int count = 1; - if (!(additionalVariables is null) && additionalVariables.TryGetCount(out int enumCount)) - { - count += enumCount; - } - - _varList = new List(count); - _additionalVariables = GetAdditionalVariables(additionalVariables, out bool hasAdditional); - _hasAdditionalVariables = hasAdditional; + _extraVariables = ListPool.Rent(); + _extraVariables.AddRange(additionalVariables +#if !NET9_0_OR_GREATER + ?? Array.Empty() + #endif + ); + _variables = ListPool.Rent(); + _constants = ObjPool.Rent(); } - private static IReadOnlySet GetAdditionalVariables(IEnumerable? collection, out bool hasAdditional) + private List InitializeContext(object? value) { - hasAdditional = true; - if (collection is null || collection.TryGetCount(out int count) && count == 0) - { - hasAdditional = false; - return Empty.Set; - } - else if (count == 1) - { - return new SingleValueReadOnlySet(collection.First(), _equality); - } - - return new ReadOnlySet(collection, _equality); + _variables.Clear(); + _constants.SetValue(value); + _constants.InsertIntoList(_variables); + _variables.AddRange(_extraVariables); + return _variables; } - public bool Any(IEnumerable? collection) + public bool All(ICollection? collection) { - if (collection is null) - { + if (collection is null || collection.Count == 0) return false; - } - bool flag = false; - foreach (T item in collection) + foreach (object? item in collection) { - if (this.IsTrue(item)) - { - flag = true; - break; - } + if (!this.IsTrue(item)) + return false; } - return flag; + return true; } - - public bool All(IEnumerable? collection) + public bool Any(ICollection? collection) { - if (collection is null || collection.TryGetCount(out int count) && count <= 0) - { + if (collection is null || collection.Count == 0) return false; - } - - bool result = true; - - foreach (T item in collection) - { - if (!this.IsTrue(item)) - { - result = false; - break; - } - } - - return result; - } - private List InitializeContext(T value) - { - _varList.Clear(); - _thisVar.AddToVarList(value, _varList); - if (_hasAdditionalVariables) + foreach (object? item in collection) { - _varList.AddRange(_additionalVariables); + if (this.IsTrue(item)) + return true; } - return _varList; + return false; } - public bool IsTrue(T value) + public bool IsTrue(object? value) { - List list = this.InitializeContext(value); - Collection results = _scriptBlock.InvokeWithContext(null, list, Array.Empty()); + List variables = this.InitializeContext(value); - return results.GetFirstValue(ConvertToBool); + return _scriptBlock.InvokeWithContext( + variables: variables, + selectAs: LanguagePrimitives.IsTrue); } - private Predicate ToPredicate() + public void Dispose() { - return _predicate ??= new Predicate(x => this.IsTrue(x)); + this.Dispose(disposing: true); + GC.SuppressFinalize(this); } - protected override object MakePredicate() + private void Dispose(bool disposing) { - return this.ToPredicate(); - } + if (!_disposed) + { + if (disposing) + { + if (!(_constants is null)) + { + ObjPool.Return(_constants); + _constants = null!; + } + + if (!(_extraVariables is null)) + { + ListPool.Return(_extraVariables); + _extraVariables = null!; + } + + if (!(_variables is null)) + { + ListPool.Return(_variables); + _variables = null!; + } + } - public static implicit operator Predicate(ScriptBlockFilter filter) - { - return filter.ToPredicate(); + _disposed = true; + } } - - private static bool ConvertToBool(object value) => Convert.ToBoolean(value); } -} +} \ No newline at end of file diff --git a/src/engine/ListFunctions.Engine/Modern/Variables/PSComparingVariable.cs b/src/engine/ListFunctions.Engine/Modern/Variables/PSComparingVariable.cs index 2dc00d7..9f469b4 100644 --- a/src/engine/ListFunctions.Engine/Modern/Variables/PSComparingVariable.cs +++ b/src/engine/ListFunctions.Engine/Modern/Variables/PSComparingVariable.cs @@ -1,56 +1,83 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace ListFunctions.Modern.Variables { - public class PSComparingVariable + public abstract class PSComparingVariable { public const string X = "x"; public const string Y = "y"; public const string LEFT = "left"; public const string RIGHT = "right"; + private static readonly string[] _left = new[] { X, LEFT }; + private static readonly string[] _right = new[] { Y, RIGHT }; + + public abstract object? InstanceValue { get; } private protected PSComparingVariable() { } + + internal static PSComparingVariable Left() + { + return new PSComparingVariable(_left); + } + internal static PSComparingVariable Right() + { + return new PSComparingVariable(_right); + } } internal sealed class PSComparingVariable : PSComparingVariable { - static readonly IReadOnlyList _left = new string[] { X, LEFT }; - static readonly IReadOnlyList _right = new string[] { Y, RIGHT }; - - readonly PSVariable[] _allVars; - T _value = default!; + private readonly PSVariable[] _allVars; + private readonly T _value = default!; internal T Value => _value; + public override object? InstanceValue => this.Value; + + internal PSComparingVariable(string[] names) + { + PopulateVariables(ref _allVars, names); + } - private PSComparingVariable(IReadOnlyList names) - : base() + + + private static void PopulateVariables([NotNull] ref PSVariable[]? allVars, string[] names) { - _allVars = new PSVariable[names.Count]; - for (int i = 0; i < names.Count; i++) +#if NET5_0_OR_GREATER + allVars = new PSVariable[names.Length]; + ref string f = ref MemoryMarshal.GetArrayDataReference(names); + ref PSVariable fVar = ref MemoryMarshal.GetArrayDataReference(allVars); + + for (int i = 0; i < names.Length; i++) { - _allVars[i] = new PSVariable(names[i], null); + Unsafe.Add(ref fVar, i) = new PSVariable(Unsafe.Add(ref f, i), value: null); } +#else + allVars = Array.ConvertAll(names, x => new PSVariable(x, value: null)); +#endif } internal void AddToVarList(T value, List variables) { + object? val = value; +#if NET5_0_OR_GREATER + ref PSVariable f = ref MemoryMarshal.GetArrayDataReference(_allVars); + for (int i = 0; i < _allVars.Length; i++) + { + variables.Add(Unsafe.Add(ref f, i)); + } +#else foreach (PSVariable v in _allVars) { - v.Value = value; + v.Value = val; variables.Add(v); } - } - - internal static PSComparingVariable Left() - { - return new PSComparingVariable(_left); - } - internal static PSComparingVariable Right() - { - return new PSComparingVariable(_right); +#endif } } } diff --git a/src/engine/ListFunctions.Engine/Modern/Variables/PSThisVariable.cs b/src/engine/ListFunctions.Engine/Modern/Variables/PSThisVariable.cs index 3100d79..cf49863 100644 --- a/src/engine/ListFunctions.Engine/Modern/Variables/PSThisVariable.cs +++ b/src/engine/ListFunctions.Engine/Modern/Variables/PSThisVariable.cs @@ -1,22 +1,22 @@ -using ListFunctions.Internal; -using MG.Collections; -using System; +using System; using System.Collections.Generic; -using System.Linq; using System.Management.Automation; namespace ListFunctions.Modern.Variables { - public abstract class PSThisVariable + public class PSThisVariable : IPoolable { public const string UNDERSCORE_NAME = "_"; public const string THIS_NAME = "this"; public const string PSITEM_NAME = "psitem"; + public const string ARGS_FIRST = "args[0]"; + public const string ARGS_SECOND = "args[1]"; - static readonly Lazy> _names = new Lazy>(GetThisNames); + static readonly Lazy> _names = new Lazy>(GetThisNames); - readonly PSVariable[] _allVars; - private protected PSThisVariable() + private readonly PSVariable[] _allVars; + public object? ObjValue { get; private set; } + public PSThisVariable() { _allVars = new PSVariable[3]; _allVars[0] = new PSVariable(UNDERSCORE_NAME, null); @@ -24,7 +24,7 @@ private protected PSThisVariable() _allVars[2] = new PSVariable(PSITEM_NAME, null); } - private protected void InsertIntoList(List list) + public void InsertIntoList(List list) { list.InsertRange(0, _allVars); } @@ -38,41 +38,33 @@ internal static bool IsThisVariable(PSVariable variable) Guard.NotNull(variable, nameof(variable)); return _names.Value.Contains(variable.Name); } - private protected void SetValue(object? value) + public void SetValue(object? value) { + this.ObjValue = value; foreach (PSVariable v in _allVars) { v.Value = value; } } - private static IReadOnlySet GetThisNames() + private static HashSet GetThisNames() { - return new ReadOnlySet(EnumerateNames(), StringComparer.InvariantCultureIgnoreCase); - } - private static IEnumerable EnumerateNames() - { - yield return UNDERSCORE_NAME; - yield return THIS_NAME; - yield return PSITEM_NAME; + return new(StringComparer.OrdinalIgnoreCase) + { + UNDERSCORE_NAME, + THIS_NAME, + PSITEM_NAME, + }; } - } - internal sealed class PSThisVariable : PSThisVariable - { - T _value = default!; - - internal T Value => _value; - - internal PSThisVariable() - : base() + void IPoolable.Initialize() { } - - internal void AddToVarList(T value, List variables) + public bool TryReset() { - this.SetValue(value); - this.InsertIntoList(variables); + Array.ForEach(_allVars, v => v.Value = null); + this.ObjValue = null; + return true; } } }