├── .editorconfig ├── .gitignore ├── .paket └── Paket.Restore.targets ├── CSharpx.sln ├── Directory.Build.props ├── LICENSE ├── README.md ├── assets └── icon.png ├── paket-files ├── .gitignore └── paket.restore.cached ├── paket.dependencies ├── paket.lock ├── src ├── .editorconfig └── CSharpx │ ├── CSharpx.csproj │ ├── CryptoRandom.cs │ ├── Either.cs │ ├── EnumerableExtensions.cs │ ├── ExceptionExtensions.cs │ ├── FSharpResultExtensions.cs │ ├── Maybe.cs │ ├── Result.cs │ ├── Strings.cs │ ├── Unit.cs │ └── paket.references └── tests └── CSharpx.Specs ├── CSharpx.Specs.csproj ├── Fakes └── Arbitrary.cs ├── Outcomes ├── CharExtensionsSpecs..cs ├── EitherSpecs.cs ├── EnumerableExtensionsSpecs.cs ├── FSharpResultExtensionsSpecs.cs ├── MaybeSpecs.cs ├── ResultSpecs.cs ├── StringExtensionsSpecs.cs ├── StringUtilSpecs.cs └── UnitSpecs.cs └── paket.references /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [] 4 | end_of_line = crlf 5 | insert_final_newline = false 6 | 7 | [*.xml] 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.{json,yml}] 12 | indent_style = space 13 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | .vscode/ 4 | .vs 5 | tools/ 6 | [Oo]bj/ 7 | [Bb]in/ 8 | .nuget/ 9 | _ReSharper.* 10 | packages/ 11 | artifacts/ 12 | *.user 13 | *.suo 14 | *.userprefs 15 | *DS_Store 16 | *.sln.ide -------------------------------------------------------------------------------- /.paket/Paket.Restore.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 8 | 9 | $(MSBuildVersion) 10 | 15.0.0 11 | false 12 | true 13 | 14 | true 15 | $(MSBuildThisFileDirectory) 16 | $(MSBuildThisFileDirectory)..\ 17 | $(PaketRootPath)paket-files\paket.restore.cached 18 | $(PaketRootPath)paket.lock 19 | classic 20 | proj 21 | assembly 22 | native 23 | /Library/Frameworks/Mono.framework/Commands/mono 24 | mono 25 | 26 | 27 | $(PaketRootPath)paket.bootstrapper.exe 28 | $(PaketToolsPath)paket.bootstrapper.exe 29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\ 30 | 31 | "$(PaketBootStrapperExePath)" 32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)" 33 | 34 | 35 | 36 | 37 | true 38 | true 39 | 40 | 41 | True 42 | 43 | 44 | False 45 | 46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/')) 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | $(PaketRootPath)paket 56 | $(PaketToolsPath)paket 57 | 58 | 59 | 60 | 61 | 62 | $(PaketRootPath)paket.exe 63 | $(PaketToolsPath)paket.exe 64 | 65 | 66 | 67 | 68 | 69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json")) 70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"')) 71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | <_PaketCommand>dotnet paket 83 | 84 | 85 | 86 | 87 | 88 | $(PaketToolsPath)paket 89 | $(PaketBootStrapperExeDir)paket 90 | 91 | 92 | paket 93 | 94 | 95 | 96 | 97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)")) 98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)" 99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)" 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | true 122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608 123 | false 124 | true 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 134 | 135 | 136 | 137 | 138 | 139 | 141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``)) 142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``)) 143 | 144 | 145 | 146 | 147 | %(PaketRestoreCachedKeyValue.Value) 148 | %(PaketRestoreCachedKeyValue.Value) 149 | 150 | 151 | 152 | 153 | true 154 | false 155 | true 156 | 157 | 158 | 162 | 163 | true 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached 183 | 184 | $(MSBuildProjectFullPath).paket.references 185 | 186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 187 | 188 | $(MSBuildProjectDirectory)\paket.references 189 | 190 | false 191 | true 192 | true 193 | references-file-or-cache-not-found 194 | 195 | 196 | 197 | 198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)')) 199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)')) 200 | references-file 201 | false 202 | 203 | 204 | 205 | 206 | false 207 | 208 | 209 | 210 | 211 | true 212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths) 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | false 224 | true 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length) 236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0]) 237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1]) 238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4]) 239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5]) 240 | 241 | 242 | %(PaketReferencesFileLinesInfo.PackageVersion) 243 | All 244 | runtime 245 | runtime 246 | true 247 | true 248 | 249 | 250 | 251 | 252 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0]) 262 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1]) 263 | 264 | 265 | %(PaketCliToolFileLinesInfo.PackageVersion) 266 | 267 | 268 | 269 | 273 | 274 | 275 | 276 | 277 | 278 | false 279 | 280 | 281 | 282 | 283 | 284 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/> 285 | 286 | 287 | 288 | 289 | 290 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile) 291 | true 292 | false 293 | true 294 | false 295 | true 296 | false 297 | true 298 | false 299 | true 300 | $(PaketIntermediateOutputPath)\$(Configuration) 301 | $(PaketIntermediateOutputPath) 302 | 303 | 304 | 305 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/> 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 363 | 364 | 407 | 408 | 450 | 451 | 492 | 493 | 494 | 495 | -------------------------------------------------------------------------------- /CSharpx.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpx", "src/CSharpx/CSharpx.csproj", "{2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpx.Specs", "tests/CSharpx.Specs/CSharpx.Specs.csproj", "{BBDB5904-5A75-49E7-A57E-EB92E32A7527}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|x64.Build.0 = Debug|Any CPU 27 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Debug|x86.Build.0 = Debug|Any CPU 29 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|x64.ActiveCfg = Release|Any CPU 32 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|x64.Build.0 = Release|Any CPU 33 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|x86.ActiveCfg = Release|Any CPU 34 | {2F3B15E9-0A38-4B2C-8304-78566D1BC3ED}.Release|x86.Build.0 = Release|Any CPU 35 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|x64.Build.0 = Debug|Any CPU 39 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Debug|x86.Build.0 = Debug|Any CPU 41 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|x64.ActiveCfg = Release|Any CPU 44 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|x64.Build.0 = Release|Any CPU 45 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|x86.ActiveCfg = Release|Any CPU 46 | {BBDB5904-5A75-49E7-A57E-EB92E32A7527}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MSBuildThisFileDirectory) 4 | false 5 | 6 | 7 | $(DefineConstants);NETFRAMEWORK 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers 13 | all 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 - 2021 Giacomo Stelluti Scala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is no longer maintained 2 | 3 | Issue reports and pull requests will not be attended. 4 | 5 | Development has been moved to **SharpX** project. 6 | 7 | * SharpX: [View on GitHub](https://github.com/gsscoder/sharpx). 8 | -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gsscoder/CSharpx/280d8f9aa741721129942af6ae8f5fc0ea689fb3/assets/icon.png -------------------------------------------------------------------------------- /paket-files/.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | paket.restore.cached -------------------------------------------------------------------------------- /paket-files/paket.restore.cached: -------------------------------------------------------------------------------- 1 | { 2 | "packagesDownloadedHash": "6B2E26D9EDA25D28CDD720E738C17FB4ED33446ADA4FA14898AAE2636705EDC2", 3 | "projectsRestoredHash": "6B2E26D9EDA25D28CDD720E738C17FB4ED33446ADA4FA14898AAE2636705EDC2" 4 | } -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | 3 | nuget coverlet.collector 1.0.1 4 | nuget FluentAssertions 5.10.3 5 | nuget FsCheck 3.0.0-alpha4 prerelease 6 | nuget FsCheck.Xunit 3.0.0-alpha4 prerelease 7 | nuget Microsoft.NET.Test.Sdk 16.2.0 8 | nuget xunit 2.4.0 9 | nuget xunit.runner.visualstudio 2.4.0 10 | nuget FSharp.Core 4.7.0 -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{csproj,config}] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | # C# files 6 | [*.cs] 7 | 8 | #### Core EditorConfig Options #### 9 | 10 | # Indentation and spacing 11 | indent_size = 4 12 | indent_style = space 13 | tab_width = 4 14 | 15 | # New line preferences 16 | end_of_line = crlf 17 | insert_final_newline = false 18 | 19 | #### .NET Coding Conventions #### 20 | 21 | # Organize usings 22 | dotnet_separate_import_directive_groups = false 23 | dotnet_sort_system_directives_first = true 24 | file_header_template = unset 25 | 26 | # this. and Me. preferences 27 | dotnet_style_qualification_for_event = false 28 | dotnet_style_qualification_for_field = false 29 | dotnet_style_qualification_for_method = false 30 | dotnet_style_qualification_for_property = false 31 | 32 | # Language keywords vs BCL types preferences 33 | dotnet_style_predefined_type_for_locals_parameters_members = true 34 | dotnet_style_predefined_type_for_member_access = true 35 | 36 | # Parentheses preferences 37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity 38 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity 39 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary 40 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity 41 | 42 | # Modifier preferences 43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members 44 | 45 | # Expression-level preferences 46 | dotnet_style_coalesce_expression = true 47 | dotnet_style_collection_initializer = true 48 | dotnet_style_explicit_tuple_names = true 49 | dotnet_style_null_propagation = true 50 | dotnet_style_object_initializer = true 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | dotnet_style_prefer_auto_properties = true 53 | dotnet_style_prefer_compound_assignment = true 54 | dotnet_style_prefer_conditional_expression_over_assignment = true 55 | dotnet_style_prefer_conditional_expression_over_return = true 56 | dotnet_style_prefer_inferred_anonymous_type_member_names = true 57 | dotnet_style_prefer_inferred_tuple_names = true 58 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true 59 | dotnet_style_prefer_simplified_boolean_expressions = true 60 | dotnet_style_prefer_simplified_interpolation = true 61 | 62 | # Field preferences 63 | dotnet_style_readonly_field = true 64 | 65 | # Parameter preferences 66 | dotnet_code_quality_unused_parameters = all 67 | 68 | # Suppression preferences 69 | dotnet_remove_unnecessary_suppression_exclusions = none 70 | 71 | #### C# Coding Conventions #### 72 | 73 | # var preferences 74 | csharp_style_var_elsewhere = false 75 | csharp_style_var_for_built_in_types = false 76 | csharp_style_var_when_type_is_apparent = false 77 | 78 | # Expression-bodied members 79 | csharp_style_expression_bodied_accessors = true 80 | csharp_style_expression_bodied_constructors = false 81 | csharp_style_expression_bodied_indexers = true 82 | csharp_style_expression_bodied_lambdas = true 83 | csharp_style_expression_bodied_local_functions = false 84 | csharp_style_expression_bodied_methods = false 85 | csharp_style_expression_bodied_operators = false 86 | csharp_style_expression_bodied_properties = true 87 | 88 | # Pattern matching preferences 89 | csharp_style_pattern_matching_over_as_with_null_check = true 90 | csharp_style_pattern_matching_over_is_with_cast_check = true 91 | csharp_style_prefer_not_pattern = true 92 | csharp_style_prefer_pattern_matching = true 93 | csharp_style_prefer_switch_expression = true 94 | 95 | # Null-checking preferences 96 | csharp_style_conditional_delegate_call = true 97 | 98 | # Modifier preferences 99 | csharp_prefer_static_local_function = true 100 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 101 | 102 | # Code-block preferences 103 | csharp_prefer_braces = true 104 | csharp_prefer_simple_using_statement = true 105 | 106 | # Expression-level preferences 107 | csharp_prefer_simple_default_expression = true 108 | csharp_style_deconstructed_variable_declaration = true 109 | csharp_style_implicit_object_creation_when_type_is_apparent = true 110 | csharp_style_inlined_variable_declaration = true 111 | csharp_style_pattern_local_over_anonymous_function = true 112 | csharp_style_prefer_index_operator = true 113 | csharp_style_prefer_range_operator = true 114 | csharp_style_throw_expression = true 115 | csharp_style_unused_value_assignment_preference = discard_variable 116 | csharp_style_unused_value_expression_statement_preference = discard_variable 117 | 118 | # 'using' directive preferences 119 | csharp_using_directive_placement = outside_namespace 120 | 121 | #### C# Formatting Rules #### 122 | 123 | # New line preferences 124 | csharp_new_line_before_catch = true 125 | csharp_new_line_before_else = true 126 | csharp_new_line_before_finally = true 127 | csharp_new_line_before_members_in_anonymous_types = true 128 | csharp_new_line_before_members_in_object_initializers = true 129 | csharp_new_line_before_open_brace = anonymous_methods,anonymous_types,lambdas,methods,object_collection_array_initializers,properties,types 130 | csharp_new_line_between_query_expression_clauses = true 131 | 132 | # Indentation preferences 133 | csharp_indent_block_contents = true 134 | csharp_indent_braces = false 135 | csharp_indent_case_contents = true 136 | csharp_indent_case_contents_when_block = true 137 | csharp_indent_labels = one_less_than_current 138 | csharp_indent_switch_labels = true 139 | 140 | # Space preferences 141 | csharp_space_after_cast = false 142 | csharp_space_after_colon_in_inheritance_clause = true 143 | csharp_space_after_comma = true 144 | csharp_space_after_dot = false 145 | csharp_space_after_keywords_in_control_flow_statements = true 146 | csharp_space_after_semicolon_in_for_statement = true 147 | csharp_space_around_binary_operators = before_and_after 148 | csharp_space_around_declaration_statements = false 149 | csharp_space_before_colon_in_inheritance_clause = true 150 | csharp_space_before_comma = false 151 | csharp_space_before_dot = false 152 | csharp_space_before_open_square_brackets = false 153 | csharp_space_before_semicolon_in_for_statement = false 154 | csharp_space_between_empty_square_brackets = false 155 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 156 | csharp_space_between_method_call_name_and_opening_parenthesis = false 157 | csharp_space_between_method_call_parameter_list_parentheses = false 158 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 159 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 160 | csharp_space_between_method_declaration_parameter_list_parentheses = false 161 | csharp_space_between_parentheses = false 162 | csharp_space_between_square_brackets = false 163 | 164 | # Wrapping preferences 165 | csharp_preserve_single_line_blocks = true 166 | csharp_preserve_single_line_statements = true 167 | 168 | #### Naming styles #### 169 | 170 | # Naming rules 171 | 172 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 173 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 174 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 175 | 176 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 177 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 178 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 179 | 180 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 181 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 182 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 183 | 184 | # Symbol specifications 185 | 186 | dotnet_naming_symbols.interface.applicable_kinds = interface 187 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 188 | dotnet_naming_symbols.interface.required_modifiers = 189 | 190 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 191 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 192 | dotnet_naming_symbols.types.required_modifiers = 193 | 194 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 195 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 196 | dotnet_naming_symbols.non_field_members.required_modifiers = 197 | 198 | # Naming styles 199 | 200 | dotnet_naming_style.pascal_case.required_prefix = 201 | dotnet_naming_style.pascal_case.required_suffix = 202 | dotnet_naming_style.pascal_case.word_separator = 203 | dotnet_naming_style.pascal_case.capitalization = pascal_case 204 | 205 | dotnet_naming_style.begins_with_i.required_prefix = I 206 | dotnet_naming_style.begins_with_i.required_suffix = 207 | dotnet_naming_style.begins_with_i.word_separator = 208 | dotnet_naming_style.begins_with_i.capitalization = pascal_case -------------------------------------------------------------------------------- /src/CSharpx/CSharpx.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netstandard2.0;net47 6 | Functional programming and other utilities for C# 7 | Functional programming and other utilities for C# 8 | 2.8.0-rc.2 9 | gsscoder 10 | Copyright © Giacomo Stelluti Scala, 2015-2021 11 | https://github.com/gsscoder/csharpx 12 | https://github.com/gsscoder/csharpx 13 | MIT 14 | functional;utility;api;library 15 | icon.png 16 | 8.0 17 | 18 | 19 | ../../artifacts/CSharpx/Debug 20 | 21 | 22 | ../../artifacts/CSharpx/Release 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | 31 | 32 | 33 | <_Parameter1>$(MSBuildProjectName).Specs 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/CSharpx/CryptoRandom.cs: -------------------------------------------------------------------------------- 1 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 2 | 3 | using System; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Security.Cryptography; 6 | 7 | namespace CSharpx 8 | { 9 | /// A thread safe random number generator based on the RNGCryptoServiceProvider. 10 | #if !CSX_TYPES_INTERNAL 11 | public 12 | #endif 13 | class CryptoRandom : Random 14 | { 15 | readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider(); 16 | byte[] _buffer; 17 | int _bufferPosition; 18 | 19 | /// Gets a value indicating whether this instance has random pool enabled. 20 | public bool IsRandomPoolEnabled { get; private set; } 21 | 22 | /// Initializes a new instance of the CryptoRandom class with. Using this 23 | /// overload will enable the random buffer pool. 24 | public CryptoRandom() : this(true) { } 25 | 26 | /// Initializes a new instance of the CryptoRandom class. This method will 27 | /// disregard whatever value is passed as seed and it's only implemented in order to be fully 28 | /// backwards compatible with System.Random. Using this overload will enable the random 29 | /// buffer pool. 30 | [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "ignoredSeed", 31 | Justification = "Cannot remove this parameter as we implement the full API of System.Random")] 32 | public CryptoRandom(int seed) : this(true) { } 33 | 34 | /// Initializes a new instance of the CryptoRandom class with optional random 35 | /// buffer. 36 | public CryptoRandom(bool enableRandomPool) 37 | { 38 | IsRandomPoolEnabled = enableRandomPool; 39 | } 40 | 41 | void InitBuffer() 42 | { 43 | if (IsRandomPoolEnabled) { 44 | if (_buffer == null || _buffer.Length != 512) { 45 | _buffer = new byte[512]; 46 | } 47 | } 48 | else { 49 | if (_buffer == null || _buffer.Length != 4) { 50 | _buffer = new byte[4]; 51 | } 52 | } 53 | _rng.GetBytes(_buffer); 54 | _bufferPosition = 0; 55 | } 56 | 57 | /// Returns a non-negative random integer. 58 | public override int Next() => 59 | // Mask away the sign bit so that we always return nonnegative integers 60 | (int)GetRandomUInt32() & 0x7FFFFFFF; 61 | 62 | /// Returns a non-negative random integer that is less than the specified 63 | /// maximum. 64 | public override int Next(int maxValue) 65 | { 66 | if (maxValue < 0) throw new ArgumentOutOfRangeException(nameof(maxValue)); 67 | 68 | return Next(0, maxValue); 69 | } 70 | 71 | /// Returns a non-negative random integer that is within a specified range. 72 | public override int Next(int minValue, int maxValue) 73 | { 74 | if (minValue > maxValue) throw new ArgumentOutOfRangeException(nameof(minValue)); 75 | if (minValue == maxValue) return minValue; 76 | 77 | long diff = maxValue - minValue; 78 | while (true) { 79 | uint rand = GetRandomUInt32(); 80 | long max = 1 + (long)uint.MaxValue; 81 | long remainder = max % diff; 82 | if (rand < max - remainder) { 83 | return (int)(minValue + (rand % diff)); 84 | } 85 | } 86 | } 87 | 88 | /// Returns a random floating-point number that is greater than or equal to 0.0, and 89 | /// less than 1.0. 90 | public override double NextDouble() => GetRandomUInt32() / (1.0 + uint.MaxValue); 91 | 92 | /// Fills the elements of a specified array of bytes with random numbers. 93 | public override void NextBytes(byte[] buffer) 94 | { 95 | if (buffer == null) throw new ArgumentNullException(nameof(buffer)); 96 | 97 | lock (this) 98 | { 99 | if (IsRandomPoolEnabled && _buffer == null) { 100 | InitBuffer(); 101 | } 102 | // Can we fit the requested number of bytes in the buffer? 103 | if (IsRandomPoolEnabled && _buffer.Length <= buffer.Length) 104 | { 105 | int count = buffer.Length; 106 | EnsureRandomBuffer(count); 107 | Buffer.BlockCopy(_buffer, _bufferPosition, buffer, 0, count); 108 | _bufferPosition += count; 109 | } 110 | else { 111 | // Draw bytes directly from the RNGCryptoProvider 112 | _rng.GetBytes(buffer); 113 | } 114 | } 115 | } 116 | 117 | uint GetRandomUInt32() 118 | { 119 | lock (this) { 120 | EnsureRandomBuffer(4); 121 | uint rand = BitConverter.ToUInt32(_buffer, _bufferPosition); 122 | _bufferPosition += 4; 123 | return rand; 124 | } 125 | } 126 | 127 | void EnsureRandomBuffer(int requiredBytes) 128 | { 129 | if (_buffer == null) { 130 | InitBuffer(); 131 | } 132 | 133 | if (requiredBytes > _buffer.Length) throw new ArgumentOutOfRangeException(nameof(requiredBytes), 134 | "Cannot be greater than random buffer."); 135 | 136 | if ((_buffer.Length - _bufferPosition) < requiredBytes) { 137 | InitBuffer(); 138 | } 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /src/CSharpx/Either.cs: -------------------------------------------------------------------------------- 1 | //reguires: Unit.cs, Maybe.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace CSharpx 8 | { 9 | #region Either Type 10 | /// Discriminator for Either. 11 | #if !CSX_TYPES_INTERNAL 12 | public 13 | #endif 14 | enum EitherType 15 | { 16 | /// Failed computation case. 17 | Left, 18 | /// Sccessful computation case. 19 | Right 20 | } 21 | 22 | #if !CSX_TYPES_INTERNAL 23 | public 24 | #endif 25 | /// The Either type represents values with two possibilities: a value of type 26 | /// Either T U is either Left T or Right U. The Either type is 27 | /// sometimes used to represent a value which is either correct or an error; by convention, the 28 | /// Left constructor is used to hold an error value and the Right constructor is 29 | /// used to hold a correct value (mnemonic: "right" also means "correct"). 30 | struct Either 31 | { 32 | readonly TLeft _leftValue; 33 | readonly TRight _rightValue; 34 | 35 | internal Either(TLeft value) 36 | { 37 | _leftValue = value; 38 | _rightValue = default; 39 | Tag = EitherType.Left; 40 | } 41 | 42 | internal Either(TRight value) 43 | { 44 | _leftValue = default; 45 | _rightValue = value; 46 | Tag = EitherType.Right; 47 | } 48 | 49 | public EitherType Tag { get; private set; } 50 | 51 | #region Basic Match Methods 52 | /// Matches a Left value returning true and value itself via an output 53 | /// parameter. 54 | public bool MatchLeft(out TLeft value) 55 | { 56 | value = Tag == EitherType.Left ? _leftValue : default; 57 | return Tag == EitherType.Left; 58 | } 59 | 60 | /// Matches a Right value returning true and value itself via an output 61 | /// parameter. 62 | public bool MatchRight(out TRight value) 63 | { 64 | value = Tag == EitherType.Right ? _rightValue : default; 65 | return Tag == EitherType.Right; 66 | } 67 | #endregion 68 | } 69 | 70 | #if !CSX_TYPES_INTERNAL 71 | public 72 | #endif 73 | static class Either 74 | { 75 | #region Value Case Constructors 76 | /// Builds the Left case of an Either value. 77 | public static Either Left(TLeft value) => 78 | new Either(value); 79 | 80 | /// Builds the Right case of an Either value. 81 | public static Either Right(TRight value) => 82 | new Either(value); 83 | #endregion 84 | 85 | #region Monad 86 | /// Inject a value into the Either type, returning Right case. 87 | public static Either Return(TRight value) => 88 | Either.Right(value); 89 | 90 | /// Monadic bind. 91 | public static Either Bind( 92 | Either either, Func> func) 93 | { 94 | if (func == null) throw new ArgumentNullException(nameof(func)); 95 | 96 | if (either.MatchRight(out TRight right)) { 97 | return func(right); 98 | } 99 | return Either.Left(either.GetLeft()); 100 | } 101 | #endregion 102 | 103 | #region Functor 104 | /// Transforms a Either right value by using a specified mapping function. 105 | public static Either Map(Either either, 106 | Func func) 107 | { 108 | if (func == null) throw new ArgumentNullException(nameof(func)); 109 | 110 | if (either.MatchRight(out TRight right)) { 111 | return Either.Right(func(right)); 112 | } 113 | return Either.Left(either.GetLeft()); 114 | } 115 | #endregion 116 | 117 | #region Bifunctor 118 | /// Maps both parts of a Either type. Applies the first function if Either 119 | /// is Left. Otherwise applies the second function. 120 | public static Either Bimap(Either either, 121 | Func mapLeft, Func mapRight) 122 | { 123 | if (mapLeft == null) throw new ArgumentNullException(nameof(mapLeft)); 124 | if (mapRight == null) throw new ArgumentNullException(nameof(mapRight)); 125 | 126 | if (either.MatchRight(out TRight right)) { 127 | return Either.Right(mapRight(right)); 128 | } 129 | return Either.Left(mapLeft(either.GetLeft())); 130 | } 131 | #endregion 132 | 133 | /// Fail with a message. Not part of mathematical definition of a monad. 134 | public static Either Fail(string message) => throw new Exception(message); 135 | 136 | /// Wraps a function, encapsulates any exception thrown within to a Either. 137 | public static Either Try(Func func) 138 | { 139 | if (func == null) throw new ArgumentNullException(nameof(func)); 140 | 141 | try { 142 | return new Either(func()); 143 | } 144 | catch (Exception ex) { 145 | return new Either(ex); 146 | } 147 | } 148 | 149 | /// Attempts to cast an object. Stores the cast value in Right if successful, otherwise 150 | /// stores the exception in Left. 151 | public static Either Cast(object obj) => Either.Try(() => (TRight)obj); 152 | 153 | /// Converts a Just value to a Right and a Nothing value to a 154 | /// Left. 155 | public static Either FromMaybe(Maybe maybe, TLeft left) 156 | { 157 | if (maybe.Tag == MaybeType.Just) { 158 | return Either.Right(maybe.FromJust()); 159 | } 160 | return Either.Left(left); 161 | } 162 | 163 | static TLeft GetLeft(this Either either) => either.FromLeft(); 164 | } 165 | 166 | #if !CSX_TYPES_INTERNAL 167 | public 168 | #endif 169 | static class EitherExtensions 170 | { 171 | #region LINQ Operators 172 | /// Map operation compatible with LINQ. 173 | public static Either Select( 174 | this Either either, 175 | Func selector) => Either.Map(either, selector); 176 | 177 | /// Map operation compatible with LINQ. 178 | public static Either SelectMany(this Either result, 179 | Func> func) => Either.Bind(result, func); 180 | #endregion 181 | 182 | #region Alternative Match Method 183 | public static Unit Match(this Either either, 184 | Func onLeft, Func onRight) 185 | { 186 | if (onLeft == null) throw new ArgumentNullException(nameof(onLeft)); 187 | if (onRight == null) throw new ArgumentNullException(nameof(onRight)); 188 | 189 | return either.MatchRight(out TRight right) switch { 190 | true => onRight(right), 191 | _ => onLeft(either.FromLeft()) 192 | }; 193 | } 194 | #endregion 195 | 196 | /// Equivalent to monadic Return operation. Builds a Right value 197 | /// by default. 198 | public static Either ToEither(this TRight value) => Either.Return(value); 199 | 200 | /// Equivalent to monadic Bind. 201 | public static Either Bind( 202 | this Either either, 203 | Func> func) => Either.Bind(either, func); 204 | 205 | /// Equivalent to monadic Map. 206 | public static Either Map( 207 | this Either either, 208 | Func func) => Either.Map(either, func); 209 | 210 | /// Eviqualent to monadic Bimap. 211 | public static Either Bimap( 212 | this Either either, 213 | Func mapLeft, 214 | Func mapRight) => Either.Bimap(either, mapLeft, mapRight); 215 | 216 | /// Returns true if it is in form of Left. 217 | public static bool IsLeft(this Either either) => 218 | either.Tag == EitherType.Left; 219 | 220 | /// Returns true if it is in form of Right. 221 | public static bool IsRight(this Either either) => 222 | either.Tag == EitherType.Right; 223 | 224 | /// Extracts the element out of Left and returns a default value (or noneValue 225 | /// when given) if it is in form of Right. 226 | public static TLeft FromLeft(this Either either, 227 | TLeft noneValue = default) => either.MatchLeft(out TLeft value) ? value : noneValue; 228 | 229 | /// Extracts the element out of Left and throws an exception if it is form of 230 | /// Right. 231 | public static TLeft FromLeftOrFail(this Either either, 232 | Exception exceptionToThrow = null) 233 | { 234 | if (either.MatchLeft(out TLeft value)) { 235 | return value; 236 | } 237 | throw exceptionToThrow ?? new Exception("The value is empty."); 238 | } 239 | 240 | /// Extracts the element out of Left and returns a default (or noneValue 241 | /// when given) value if it is in form ofRight. 242 | public static TRight FromRight(this Either either, 243 | TRight noneValue = default) => either.MatchRight(out TRight value) ? value : noneValue; 244 | 245 | /// Extracts the element out of Left and throws an exception if it is form of 246 | /// Right. 247 | public static TRight FromRightOrFail(this Either either, 248 | Exception exceptionToThrow = null) 249 | { 250 | if (either.MatchRight(out TRight value)) { 251 | return value; 252 | } 253 | throw exceptionToThrow ?? new Exception("The value is empty."); 254 | } 255 | 256 | #region IEnumerable 257 | /// Extracts from a sequence of Either all the Left elements. All the 258 | /// Left elements are extracted in order. 259 | public static IEnumerable Lefts(this IEnumerable> source) 260 | { 261 | if (source == null) throw new ArgumentNullException(nameof(source)); 262 | 263 | return _(); IEnumerable _() 264 | { 265 | foreach (var either in source) { 266 | if (either.Tag == EitherType.Left) yield return either.FromLeft(); 267 | } 268 | } 269 | } 270 | 271 | /// Extracts from a sequence of Either all the Right elements. All the 272 | /// Rights elements are extracted in order. 273 | public static IEnumerable Rights(this IEnumerable> source) 274 | { 275 | if (source == null) throw new ArgumentNullException(nameof(source)); 276 | 277 | return _(); IEnumerable _() 278 | { 279 | foreach (var either in source) { 280 | if (either.Tag == EitherType.Right) yield return either.FromRight(); 281 | } 282 | } 283 | } 284 | 285 | /// Partitions a sequence of Either into two sequences. All the Left 286 | /// elements are extracted, in order, to the first component of the pair. Similarly the Right 287 | /// elements are extracted to the second component of the pair. 288 | public static (IEnumerable, IEnumerable) Partition( 289 | this IEnumerable> source) 290 | { 291 | if (source == null) throw new ArgumentNullException(nameof(source)); 292 | 293 | var lefts = new List(); 294 | var rights = new List(); 295 | 296 | foreach (var either in source) { 297 | if (either.Tag == EitherType.Left) lefts.Add(either.FromLeft()); 298 | else rights.Add(either.FromRight()); 299 | } 300 | return (lefts, rights); 301 | } 302 | #endregion 303 | } 304 | #endregion 305 | } -------------------------------------------------------------------------------- /src/CSharpx/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | //requires: Unit.cs, Either.cs, CryptoRandom.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.Globalization; 8 | using System.Linq; 9 | using System.Text; 10 | using LinqEnumerable = System.Linq.Enumerable; 11 | 12 | namespace CSharpx 13 | { 14 | #if !CSX_TYPES_INTERNAL 15 | public 16 | #endif 17 | static class EnumerableExtensions 18 | { 19 | #region Internal 20 | static IEnumerable AssertCountImpl(IEnumerable source, 21 | int count, Func errorSelector) 22 | { 23 | var collection = source as ICollection; // Optimization for collections 24 | if (collection != null) { 25 | if (collection.Count != count) { 26 | throw errorSelector(collection.Count.CompareTo(count), count); 27 | } 28 | return source; 29 | } 30 | return ExpectingCountYieldingImpl(source, count, errorSelector); 31 | } 32 | 33 | static IEnumerable ExpectingCountYieldingImpl(IEnumerable source, 34 | int count, Func errorSelector) 35 | { 36 | var iterations = 0; 37 | foreach (var element in source) { 38 | iterations++; 39 | if (iterations > count) { 40 | throw errorSelector(1, count); 41 | } 42 | yield return element; 43 | } 44 | if (iterations != count) { 45 | throw errorSelector(-1, count); 46 | } 47 | } 48 | #endregion 49 | 50 | /// Returns the cartesian product of two sequences by combining each element of the 51 | /// first set with each in the second and applying the user=define projection to the 52 | /// pair. 53 | public static IEnumerable Cartesian(this IEnumerable first, IEnumerable second, Func resultSelector) 54 | { 55 | if (first == null) throw new ArgumentNullException(nameof(first)); 56 | if (second == null) throw new ArgumentNullException(nameof(second)); 57 | if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); 58 | 59 | return from element1 in first 60 | from element2 in second // TODO buffer to avoid multiple enumerations 61 | select resultSelector(element1, element2); 62 | } 63 | 64 | /// Prepends a single value to a sequence. 65 | public static IEnumerable Prepend(this IEnumerable source, TSource value) 66 | { 67 | if (source == null) throw new ArgumentNullException(nameof(source)); 68 | 69 | return LinqEnumerable.Concat(LinqEnumerable.Repeat(value, 1), source); 70 | } 71 | 72 | #region Concat 73 | /// Returns a sequence consisting of the head element and the given tail 74 | /// elements. 75 | public static IEnumerable Concat(this T head, IEnumerable tail) 76 | { 77 | if (tail == null) throw new ArgumentNullException(nameof(tail)); 78 | 79 | return tail.Prepend(head); 80 | } 81 | 82 | /// Returns a sequence consisting of the head elements and the given tail element. 83 | /// 84 | public static IEnumerable Concat(this IEnumerable head, T tail) 85 | { 86 | if (head == null) throw new ArgumentNullException(nameof(head)); 87 | 88 | return LinqEnumerable.Concat(head, LinqEnumerable.Repeat(tail, 1)); 89 | } 90 | #endregion 91 | 92 | #region Exclude 93 | /// Excludes elements from a sequence starting at a given 94 | /// index. 95 | public static IEnumerable Exclude(this IEnumerable source, int startIndex, int count) 96 | { 97 | if (source == null) throw new ArgumentNullException(nameof(source)); 98 | if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex)); 99 | if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); 100 | 101 | return ExcludeImpl(source, startIndex, count); 102 | } 103 | 104 | static IEnumerable ExcludeImpl(IEnumerable source, int startIndex, int count) 105 | { 106 | var index = -1; 107 | var endIndex = startIndex + count; 108 | using var iter = source.GetEnumerator(); 109 | // yield the first part of the sequence 110 | while (iter.MoveNext() && ++index < startIndex) { 111 | yield return iter.Current; 112 | } 113 | // skip the next part (up to count elements) 114 | while (++index < endIndex && iter.MoveNext()) { 115 | continue; 116 | } 117 | // yield the remainder of the sequence 118 | while (iter.MoveNext()) { 119 | yield return iter.Current; 120 | } 121 | } 122 | #endregion 123 | 124 | #region Index 125 | /// Returns a sequence of KeyValuePair where the key is the zero-based index 126 | /// of the value in the source sequence. 127 | public static IEnumerable> Index( 128 | this IEnumerable source) => source.Index(0); 129 | 130 | /// Returns a sequence of KeyValuePair where the key is the index of the value 131 | /// in the source sequence. An additional parameter specifies the starting index. 132 | public static IEnumerable> Index( 133 | this IEnumerable source, int startIndex) => source.Select((element, index) => 134 | new KeyValuePair(startIndex + index, element)); 135 | #endregion 136 | 137 | #region Fold 138 | /// Returns the result of applying a function to a sequence of 1 element. 139 | public static TResult Fold(this IEnumerable source, 140 | Func folder) => FoldImpl(source, 1, folder, null, null, null); 141 | 142 | /// Returns the result of applying a function to a sequence of 2 elements. 143 | public static TResult Fold(this IEnumerable source, 144 | Func folder) => FoldImpl(source, 2, null, folder, null, null); 145 | 146 | /// Returns the result of applying a function to a sequence of 3 elements. 147 | public static TResult Fold(this IEnumerable source, 148 | Func folder) => FoldImpl(source, 3, null, null, folder, null); 149 | 150 | /// Returns the result of applying a function to a sequence of 4 elements. 151 | public static TResult Fold(this IEnumerable source, 152 | Func folder) => FoldImpl(source, 4, null, null, null, folder); 153 | 154 | static TResult FoldImpl(IEnumerable source, int count, 155 | Func folder1, 156 | Func folder2, 157 | Func folder3, 158 | Func folder4) 159 | { 160 | if (source == null) throw new ArgumentNullException(nameof(source)); 161 | if (count == 1 && folder1 == null 162 | || count == 2 && folder2 == null 163 | || count == 3 && folder3 == null 164 | || count == 4 && folder4 == null) 165 | { // ReSharper disable NotResolvedInText 166 | throw new ArgumentNullException("folder"); // ReSharper restore NotResolvedInText 167 | } 168 | 169 | var elements = new T[count]; 170 | foreach (var e in AssertCountImpl( 171 | source.Index(), count, OnFolderSourceSizeErrorSelector)) { 172 | elements[e.Key] = e.Value; 173 | } 174 | 175 | return count switch 176 | { 177 | 1 => folder1(elements[0]), 178 | 2 => folder2(elements[0], elements[1]), 179 | 3 => folder3(elements[0], elements[1], elements[2]), 180 | 4 => folder4(elements[0], elements[1], elements[2], elements[3]), 181 | _ => throw new NotSupportedException(), 182 | }; 183 | } 184 | 185 | static readonly Func OnFolderSourceSizeErrorSelector = OnFolderSourceSizeError; 186 | 187 | static Exception OnFolderSourceSizeError(int cmp, int count) 188 | { 189 | var message = cmp < 0 190 | ? "Sequence contains too few elements when exactly {0} {1} expected." 191 | : "Sequence contains too many elements when exactly {0} {1} expected."; 192 | return new Exception(string.Format(message, count.ToString("N0"), count == 1 ? "was" : "were")); 193 | } 194 | #endregion 195 | 196 | #region Pairwise 197 | /// Returns a sequence resulting from applying a function to each element in the 198 | /// source sequence and its predecessor, with the exception of the first element which is 199 | /// only returned as the predecessor of the second element. 200 | public static IEnumerable Pairwise(this IEnumerable source, Func resultSelector) 201 | { 202 | if (source == null) throw new ArgumentNullException(nameof(source)); 203 | if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector)); 204 | 205 | return PairwiseImpl(source, resultSelector); 206 | } 207 | 208 | static IEnumerable PairwiseImpl(this IEnumerable source, Func resultSelector) 209 | { 210 | using var e = source.GetEnumerator(); 211 | if (!e.MoveNext()) { 212 | yield break; 213 | } 214 | var previous = e.Current; 215 | while (e.MoveNext()) { 216 | yield return resultSelector(previous, e.Current); 217 | previous = e.Current; 218 | } 219 | } 220 | #endregion 221 | 222 | # region ToDelimitedString 223 | /// Creates a delimited string from a sequence of values. The delimiter used depends 224 | /// on the current culture of the executing thread. 225 | public static string ToDelimitedString( 226 | this IEnumerable source) => ToDelimitedString(source, null); 227 | 228 | /// Creates a delimited string from a sequence of values and 229 | /// a given delimiter. 230 | public static string ToDelimitedString( 231 | this IEnumerable source, string delimiter) 232 | { 233 | if (source == null) throw new ArgumentNullException(nameof(source)); 234 | 235 | return ToDelimitedStringImpl(source, delimiter, (sb, e) => sb.Append(e)); 236 | } 237 | 238 | static string ToDelimitedStringImpl(IEnumerable source, string delimiter, 239 | Func append) 240 | { 241 | delimiter = delimiter ?? CultureInfo.CurrentCulture.TextInfo.ListSeparator; 242 | var builder = new StringBuilder(); 243 | var iterations = 0; 244 | foreach (var value in source) { 245 | if (iterations++ > 0) builder.Append(delimiter); 246 | append(builder, value); 247 | } 248 | return builder.ToString(); 249 | } 250 | #endregion 251 | 252 | #region DistinctBy 253 | /// Returns all distinct elements of the given source, where "distinctness" 254 | /// is determined via a projection and the default equality comparer for the projected 255 | /// type. 256 | public static IEnumerable DistinctBy(this IEnumerable source, 257 | Func keySelector) 258 | { 259 | if (source == null) throw new ArgumentNullException(nameof(source)); 260 | if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); 261 | 262 | return source.DistinctBy(keySelector, null); 263 | } 264 | 265 | /// Returns all distinct elements of the given source, where "distinctness" 266 | /// is determined via a projection and the specified comparer for the projected type. 267 | public static IEnumerable DistinctBy(this IEnumerable source, 268 | Func keySelector, IEqualityComparer comparer) 269 | { 270 | if (source == null) throw new ArgumentNullException(nameof(source)); 271 | if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); 272 | 273 | return _(); IEnumerable _() 274 | { 275 | var knownKeys = new HashSet(comparer); 276 | foreach (var element in source) { 277 | if (knownKeys.Add(keySelector(element))) 278 | yield return element; 279 | } 280 | } 281 | } 282 | #endregion 283 | 284 | #region Repeat 285 | /// Repeats the sequence the specified number of times. 286 | public static IEnumerable Repeat(this IEnumerable source, int count) 287 | { 288 | if (source == null) throw new ArgumentNullException(nameof(source)); 289 | if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), 290 | "Repeat count must be greater than or equal to zero."); 291 | 292 | return RepeatImpl(source, count); 293 | } 294 | 295 | /// Repeats the sequence forever. 296 | public static IEnumerable Repeat(this IEnumerable source) 297 | { 298 | if (source == null) throw new ArgumentNullException(nameof(source)); 299 | 300 | return RepeatImpl(source, null); 301 | } 302 | 303 | static IEnumerable RepeatImpl(IEnumerable source, int? count) 304 | { 305 | var memo = source.Materialize(); 306 | using (memo as IDisposable) { 307 | while (count == null || count-- > 0) { 308 | foreach (var item in memo) 309 | yield return item; 310 | } 311 | } 312 | } 313 | #endregion 314 | 315 | /// Safe function that returns Just(first element) or Nothing. 316 | public static Maybe TryHead(this IEnumerable source) 317 | { 318 | if (source == null) throw new ArgumentNullException(nameof(source)); 319 | 320 | using var e = source.GetEnumerator(); return e.MoveNext() 321 | ? Maybe.Just(e.Current) 322 | : Maybe.Nothing(); 323 | } 324 | 325 | /// Safe function that returns Just(last element) or Nothing. 326 | public static Maybe TryLast(this IEnumerable source) 327 | { 328 | if (source == null) throw new ArgumentNullException(nameof(source)); 329 | 330 | using var e = source.GetEnumerator(); 331 | if (!e.MoveNext()) return Maybe.Nothing(); 332 | T result = e.Current; 333 | while (e.MoveNext()) result = e.Current; 334 | return Maybe.Just(result); 335 | } 336 | 337 | /// Turns an empty sequence to Nothing, otherwise Just(sequence). 338 | public static Maybe> ToMaybe(this IEnumerable source) 339 | { 340 | if (source == null) throw new ArgumentNullException(nameof(source)); 341 | 342 | using var e = source.GetEnumerator(); 343 | return e.MoveNext() 344 | ? Maybe.Just(source) 345 | : Maybe.Nothing>(); 346 | } 347 | 348 | /// Applies a function to each element of the source sequence and returns a new 349 | /// sequence of elements where the function returns Just(value). 350 | public static IEnumerable Choose(this IEnumerable source, 351 | Func> chooser) 352 | { 353 | if (source == null) throw new ArgumentNullException(nameof(source)); 354 | if (chooser == null) throw new ArgumentNullException(nameof(chooser)); 355 | 356 | return _(); IEnumerable _() 357 | { 358 | foreach (var item in source) { 359 | var result = chooser(item); 360 | if (result.MatchJust(out TResult value)) { 361 | yield return value; 362 | } 363 | } 364 | } 365 | } 366 | 367 | /// Returns the first element of a sequence as Just, or Nothing if the sequence 368 | /// contains no elements. 369 | public static Maybe FirstOrNothing(this IEnumerable source) { 370 | if (source == null) throw new ArgumentNullException(nameof(source)); 371 | 372 | if (source is IList list) { 373 | if (list.Count > 0) return Maybe.Just(list[0]); 374 | } 375 | else { 376 | using IEnumerator e = source.GetEnumerator(); 377 | if (e.MoveNext()) return Maybe.Just(e.Current); 378 | } 379 | return Maybe.Nothing(); 380 | } 381 | 382 | /// Returns the first element of the sequence as Just that satisfies a condition or 383 | /// Nothing if no such element is found. 384 | public static Maybe FirstOrNothing(this IEnumerable source, 385 | Func predicate) { 386 | if (source == null) throw new ArgumentNullException(nameof(source)); 387 | if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 388 | 389 | foreach (TSource element in source) { 390 | if (predicate(element)) return Maybe.Just(element); 391 | } 392 | return Maybe.Nothing(); 393 | } 394 | 395 | /// Returns the last element of a sequence as Just, or Nothing if the sequence 396 | /// contains no elements. 397 | public static Maybe LastOrNothing(this IEnumerable source) 398 | { 399 | if (source == null) throw new ArgumentNullException(nameof(source)); 400 | 401 | if (source is IList list) { 402 | int count = list.Count; 403 | if (count > 0) return Maybe.Just(list[count - 1]); 404 | } 405 | else { 406 | using IEnumerator e = source.GetEnumerator(); 407 | if (e.MoveNext()) { 408 | TSource result; 409 | do { 410 | result = e.Current; 411 | } while (e.MoveNext()); 412 | return Maybe.Just(result); 413 | } 414 | } 415 | return Maybe.Nothing(); 416 | } 417 | 418 | /// Returns the last element of a sequence as Just that satisfies a condition or Nothing if 419 | /// no such element is found. 420 | public static Maybe LastOrNothing(this IEnumerable source, Func predicate) { 421 | if (source == null) throw new ArgumentNullException(nameof(source)); 422 | if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 423 | 424 | var result = Maybe.Nothing(); 425 | foreach (var element in source) { 426 | if (predicate(element)) { 427 | result = Maybe.Just(element); 428 | } 429 | } 430 | return result; 431 | } 432 | 433 | /// Returns the only element of a sequence as Just, or Nothing if the sequence is 434 | /// empty. 435 | public static Maybe SingleOrNothing(this IEnumerable source) { 436 | if (source == null) throw new ArgumentNullException(nameof(source)); 437 | 438 | if (source is IList list) { 439 | switch (list.Count) { 440 | case 0: return Maybe.Nothing(); 441 | case 1: return Maybe.Just(list[0]); 442 | } 443 | } 444 | else { 445 | using IEnumerator e = source.GetEnumerator(); 446 | if (!e.MoveNext()) return Maybe.Nothing(); 447 | TSource result = e.Current; 448 | if (!e.MoveNext()) return Maybe.Just(result); 449 | } 450 | return Maybe.Nothing(); 451 | } 452 | 453 | /// Returns the only element of a sequence that satisfies a specified condition as 454 | /// Just or a Nothing if no such element exists; this method throws an exception if more than 455 | /// one element satisfies the condition. 456 | public static Maybe SingleOrNothing(this IEnumerable source, Func predicate) { 457 | if (source == null) throw new ArgumentNullException(nameof(source)); 458 | if (predicate == null) throw new ArgumentNullException(nameof(predicate)); 459 | 460 | var result = Maybe.Nothing(); 461 | var count = 0L; 462 | foreach (var element in source) { 463 | if (predicate(element)) { 464 | result = Maybe.Just(element); 465 | checked { count++; } 466 | } 467 | } 468 | return count switch 469 | { 470 | 0 => Maybe.Nothing(), 471 | 1 => result, 472 | _ => throw new InvalidOperationException("Sequence contains more than one element"), 473 | }; 474 | } 475 | 476 | /// Returns the element at a specified index in a sequence as Just or Nothing 477 | /// if the index is out of range. 478 | public static Maybe ElementAtOrNothing(this IEnumerable source, int index) { 479 | if (source == null) throw new ArgumentNullException(nameof(source)); 480 | 481 | if (index >= 0) { 482 | if (source is IList list) { 483 | if (index < list.Count) return Maybe.Just(list[index]); 484 | } 485 | else { 486 | using IEnumerator e = source.GetEnumerator(); 487 | while (true) { 488 | if (!e.MoveNext()) break; 489 | if (index == 0) return Maybe.Just(e.Current); 490 | index--; 491 | } 492 | } 493 | } 494 | return Maybe.Nothing(); 495 | } 496 | 497 | /// Immediately executes the given action on each element in the source sequence. 498 | public static Unit ForEach(this IEnumerable source, Action action) 499 | { 500 | if (source == null) throw new ArgumentNullException(nameof(source)); 501 | if (action == null) throw new ArgumentNullException(nameof(action)); 502 | 503 | foreach (var element in source) { 504 | action(element); 505 | } 506 | return Unit.Default; 507 | } 508 | 509 | /// Return everything except first element and throws exception if empty. 510 | public static IEnumerable Tail(this IEnumerable source) 511 | { 512 | if (source == null) throw new ArgumentNullException(nameof(source)); 513 | 514 | return _(); IEnumerable _() 515 | { 516 | using var e = source.GetEnumerator(); 517 | if (!e.MoveNext()) { 518 | throw new ArgumentException( 519 | "The input sequence has an insufficient number of elements."); 520 | } 521 | while (e.MoveNext()) { 522 | yield return e.Current; 523 | } 524 | } 525 | } 526 | 527 | /// Return everything except first element without throwing exception if empty. 528 | public static IEnumerable TailOrEmpty(this IEnumerable source) 529 | { 530 | if (source == null) throw new ArgumentNullException(nameof(source)); 531 | 532 | return _(); IEnumerable _() 533 | { 534 | using (var e = source.GetEnumerator()) { 535 | if (e.MoveNext()) { 536 | while (e.MoveNext()) { 537 | yield return e.Current; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | 544 | /// Partition a sequence in to chunks of given size. Each chunk is an array of the 545 | /// resulting sequence. 546 | public static IEnumerable ChunkBySize(this IEnumerable source, int chunkSize) 547 | { 548 | if (source == null) throw new ArgumentNullException(nameof(source)); 549 | if (chunkSize <= 0) throw new ArgumentException("The input must be positive."); 550 | 551 | return _(); IEnumerable _() 552 | { 553 | using var e = source.GetEnumerator(); 554 | while (e.MoveNext()) { 555 | var result = new T[chunkSize]; 556 | result[0] = e.Current; 557 | var i = 1; 558 | while (i < chunkSize && e.MoveNext()) { 559 | result[i] = e.Current; 560 | i++; 561 | } 562 | yield return i == chunkSize ? result : SubArray(result, 0, i); 563 | } 564 | } 565 | 566 | T[] SubArray(T[] array, int index, int length) { 567 | T[] result = new T[length]; 568 | Array.Copy(array, index, result, 0, length); 569 | return result; 570 | } 571 | } 572 | 573 | /// Splits an array into two parts at a given index. The first part ends just before 574 | /// the element at the given index; the second part starts with the element at the given 575 | /// index. 576 | public static (T[], T[]) SplitAt(this IEnumerable source, int index) 577 | { 578 | if (source == null) throw new ArgumentNullException(nameof(source)); 579 | if (index < 0) throw new ArgumentException("The input must be non-negative."); 580 | if (source.Count() < index) throw new ArgumentException("The input sequence has an insufficient number of elements."); 581 | 582 | if (index == 0) return (new T[] {}, source.ToArray()); 583 | if (index == source.Count()) return (source.ToArray(), new T[] {}); 584 | var left = source.Take(index).ToArray(); 585 | var right = source.Skip(index).Take(source.Count() - index).ToArray(); 586 | return (left, right); 587 | } 588 | 589 | /// Selects a random element. 590 | public static T Choice(this IEnumerable source) 591 | { 592 | if (source == null) throw new ArgumentNullException(nameof(source)); 593 | 594 | var index = new CryptoRandom().Next(source.Count() - 1); 595 | return source.ElementAt(index); 596 | } 597 | 598 | /// Takes an element and a sequence and `intersperses' that element between its 599 | /// elements. 600 | public static IEnumerable Intersperse(this IEnumerable source, T element) 601 | { 602 | if (source == null) throw new ArgumentNullException(nameof(source)); 603 | if (element == null) throw new ArgumentNullException(nameof(element)); 604 | 605 | return _(); IEnumerable _() 606 | { 607 | var count = source.Count(); 608 | var last = count - 1; 609 | for (var i = 0; i < count; i++) { 610 | yield return source.ElementAt(i); 611 | if (i != last) { 612 | yield return element; 613 | } 614 | } 615 | } 616 | } 617 | 618 | #region Materialize 619 | class MaterializedEnumerable : IEnumerable 620 | { 621 | readonly ICollection _inner; 622 | 623 | internal MaterializedEnumerable(IEnumerable enumerable) => 624 | _inner = enumerable as ICollection ?? enumerable.ToArray(); 625 | 626 | public IEnumerator GetEnumerator() => _inner.GetEnumerator(); 627 | 628 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 629 | } 630 | 631 | /// Captures the current state of a sequence. 632 | public static IEnumerable Materialize(this IEnumerable source) => 633 | source switch 634 | { 635 | null => throw new ArgumentNullException(nameof(source)), 636 | MaterializedEnumerable _ => source, 637 | _ => new MaterializedEnumerable(source), 638 | }; 639 | #endregion 640 | 641 | /// Flattens a sequence by one level. 642 | public static IEnumerable FlattenOnce(this IEnumerable> source) 643 | { 644 | if (source == null) throw new ArgumentNullException(nameof(source)); 645 | 646 | return _(); IEnumerable _() 647 | { 648 | foreach (var element in source) { 649 | foreach (var subelement in element) { 650 | yield return subelement; 651 | } 652 | } 653 | } 654 | } 655 | } 656 | } -------------------------------------------------------------------------------- /src/CSharpx/ExceptionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace CSharpx 5 | { 6 | #if !CSX_TYPES_INTERNAL 7 | public 8 | #endif 9 | static class ExceptionExtensions 10 | { 11 | public static string ToStringEx(this Exception exception) 12 | { 13 | var builder = new StringBuilder(capacity: 256); 14 | builder.AppendLine(exception.Message); 15 | if (exception.StackTrace != null) { 16 | builder.AppendLine("--- Stack trace:"); 17 | builder.AppendLine(exception.StackTrace); 18 | } 19 | if (exception.InnerException != null) { 20 | builder.AppendLine("--- Inner exception:"); 21 | builder.AppendLine(exception.InnerException.Message); 22 | if(exception.InnerException.StackTrace != null) { 23 | builder.AppendLine("--- Inner exception stack trace:"); 24 | builder.AppendLine(exception.InnerException.StackTrace); 25 | } 26 | } 27 | return builder.ToString(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/CSharpx/FSharpResultExtensions.cs: -------------------------------------------------------------------------------- 1 | //requires Maybe.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Runtime.CompilerServices; 6 | using Microsoft.FSharp.Core; 7 | 8 | namespace CSharpx 9 | { 10 | #if !CSX_TYPES_INTERNAL 11 | public 12 | #endif 13 | static class FSharpResultExtensions 14 | { 15 | /// Allows pattern matching on FSharpResult. 16 | public static void Match(this FSharpResult result, 17 | Action onOk, 18 | Action onError) 19 | { 20 | if (onOk == null) throw new ArgumentNullException(nameof(onOk)); 21 | if (onError == null) throw new ArgumentNullException(nameof(onError)); 22 | 23 | if (result.IsOk) { 24 | onOk(result.ResultValue); 25 | return; 26 | } 27 | onError(result.ErrorValue); 28 | } 29 | 30 | /// Allows pattern matching on FSharpResult. 31 | public static TResult Either(this FSharpResult result, 32 | Func onOk, 33 | Func onError) 34 | { 35 | if (onOk == null) throw new ArgumentNullException(nameof(onOk)); 36 | if (onError == null) throw new ArgumentNullException(nameof(onError)); 37 | 38 | return Either(onOk, onError, result); 39 | } 40 | 41 | /// Lifts a Func into an FSharpResult and applies it on the given 42 | /// result. 43 | public static FSharpResult Map( 44 | this FSharpResult result, 45 | Func func) 46 | { 47 | if (func == null) throw new ArgumentNullException(nameof(func)); 48 | 49 | return Lift(func, result); 50 | } 51 | 52 | /// If the wrapped function is a success and the given result is a success the 53 | /// function is applied on the value. Otherwise the exisiting error is returned. 54 | public static FSharpResult Bind( 55 | this FSharpResult result, 56 | Func> func) 57 | { 58 | if (func == null) throw new ArgumentNullException(nameof(func)); 59 | 60 | return Bind(func, result); 61 | } 62 | 63 | /// If the given result is a success the wrapped value will be returned. Otherwise 64 | /// the function throws an exception with the string representation of the error. 65 | public static T ReturnOrFail(this FSharpResult result) 66 | { 67 | Func raiseExn = err => throw new Exception(err.ToString()); 68 | 69 | return Either(value => value, raiseExn, result); 70 | } 71 | 72 | /// Unwraps a value applying a function o returns another value on fail. 73 | public static TResult Return( 74 | this FSharpResult result, 75 | Func func, TResult noneValue) => Either(func, value => noneValue, result); 76 | 77 | /// Builds a Maybe discarding error type. 78 | public static Maybe ToMaybe(this FSharpResult result) => 79 | result.IsOk ? Maybe.Just(result.ResultValue) : Maybe.Nothing(); 80 | 81 | public static Either ToEither(this FSharpResult result) => 82 | result.IsOk 83 | ? CSharpx.Either.Right(result.ResultValue) 84 | : CSharpx.Either.Left(result.ErrorValue); 85 | 86 | /// Takes a result and maps it with okFunc if it is a success, otherwise it maps it with 87 | /// errorFunc. 88 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 89 | public static TResult Either( 90 | Func okFunc, 91 | Func errorFunc, 92 | FSharpResult result) 93 | { 94 | if (okFunc == null) throw new ArgumentNullException(nameof(okFunc)); 95 | if (errorFunc == null) throw new ArgumentNullException(nameof(errorFunc)); 96 | 97 | if (result.IsOk) { 98 | return okFunc(result.ResultValue); 99 | } 100 | return errorFunc(result.ErrorValue); 101 | } 102 | 103 | /// If the result is a success it executes the given function on the value. Otherwise the 104 | /// exisiting error is returned. 105 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 106 | public static FSharpResult Bind( 107 | Func> func, 108 | FSharpResult result) 109 | { 110 | if (func == null) throw new ArgumentNullException(nameof(func)); 111 | 112 | Func> okFunc = 113 | value => func(value); 114 | Func> errorFunc = 115 | error => FSharpResult.NewError(error); 116 | return Either(okFunc, errorFunc, result); 117 | } 118 | 119 | /// If the wrapped function is a success and the given result is a success the function is 120 | /// applied on the value. Otherwise the exisiting error is returned. 121 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 122 | public static FSharpResult Apply( 123 | FSharpResult, TError> wrappedFunc, 124 | FSharpResult result 125 | ) 126 | { 127 | if (wrappedFunc.IsOk && result.IsOk) { 128 | return FSharpResult.NewOk( 129 | wrappedFunc.ResultValue(result.ResultValue)); 130 | } 131 | return FSharpResult.NewError(result.ErrorValue); 132 | } 133 | 134 | /// Lifts a function into a result container and applies it on the given result. 135 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 136 | public static FSharpResult Lift( 137 | Func func, 138 | FSharpResult result) => 139 | Apply(FSharpResult, TError>.NewOk(func), result); 140 | } 141 | } -------------------------------------------------------------------------------- /src/CSharpx/Maybe.cs: -------------------------------------------------------------------------------- 1 | //requires: Unit.cs, Either.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | 9 | namespace CSharpx 10 | { 11 | #region Maybe Type 12 | /// Discriminator for Maybe. 13 | #if !CSX_TYPES_INTERNAL 14 | public 15 | #endif 16 | enum MaybeType 17 | { 18 | /// Computation case without a value. 19 | Nothing, 20 | /// Computation case with a value. 21 | Just 22 | } 23 | 24 | /// The Maybe type models an optional value. A value of type Maybe either 25 | /// contains a value (represented as Just a), or it is empty (represented as 26 | /// Nothing). 27 | #if !CSX_TYPES_INTERNAL 28 | public 29 | #endif 30 | struct Maybe : IEquatable> 31 | { 32 | #if DEBUG 33 | internal 34 | #endif 35 | readonly T _value; 36 | 37 | internal Maybe(T value) 38 | { 39 | _value = value; 40 | Tag = MaybeType.Just; 41 | } 42 | 43 | /// Type discriminator. 44 | public MaybeType Tag { get; private set; } 45 | 46 | /// Determines whether this instance and another specified Maybe object have the same value. 47 | public override bool Equals(object other) 48 | { 49 | if (other == null) return false; 50 | var otherType = other.GetType(); 51 | if (otherType != GetType()) return false; 52 | var otherTag = (MaybeType)otherType.GetProperty( 53 | "Tag", BindingFlags.Public | BindingFlags.Instance).GetValue(other); 54 | if (otherTag != Tag) return false; 55 | if (otherTag == MaybeType.Nothing && Tag == MaybeType.Nothing) return true; 56 | var otherField = otherType.GetField("_value", BindingFlags.NonPublic | BindingFlags.Instance); 57 | return otherField.GetValue(other).Equals(_value); 58 | } 59 | 60 | /// Determines whether this instance and another specified Maybe object have the same value. 61 | public bool Equals(Maybe other) => 62 | other.Tag != MaybeType.Just || _value.Equals(other._value); 63 | 64 | public static bool operator ==(Maybe left, Maybe right) => left.Equals(right); 65 | 66 | public static bool operator !=(Maybe left, Maybe right) => !left.Equals(right); 67 | 68 | /// Serves as the default hash function. 69 | public override int GetHashCode() => 70 | Tag == MaybeType.Nothing 71 | ? ToString().GetHashCode() 72 | : _value.GetHashCode(); 73 | 74 | public override string ToString() => 75 | Tag switch { 76 | MaybeType.Just => new StringBuilder("Just(") 77 | .Append(_value) 78 | .Append(")") 79 | .ToString(), 80 | _ => "" 81 | }; 82 | 83 | #region Basic Match Methods 84 | /// Matches a value returning true and value itself via an output 85 | /// parameter. 86 | public bool MatchJust(out T value) 87 | { 88 | value = Tag == MaybeType.Just ? _value : default; 89 | return Tag == MaybeType.Just; 90 | } 91 | 92 | /// Matches an empty value returning true. 93 | public bool MatchNothing() => Tag == MaybeType.Nothing; 94 | #endregion 95 | } 96 | #endregion 97 | 98 | /// Provides static methods for manipulating Maybe. 99 | #if !CSX_TYPES_INTERNAL 100 | public 101 | #endif 102 | static class Maybe 103 | { 104 | #region Value Case Constructors 105 | /// Builds the empty case of Maybe. 106 | public static Maybe Nothing() => new Maybe(); 107 | 108 | /// Builds the case when Maybe contains a value. 109 | public static Maybe Just(T value) => new Maybe(value); 110 | #endregion 111 | 112 | #region Monad 113 | /// Injects a value into the monadic Maybe type. 114 | public static Maybe Return(T value) => Equals(value, default(T)) ? Nothing() : Just(value); 115 | 116 | /// Sequentially compose two actions, passing any value produced by the first as 117 | /// an argument to the second. 118 | public static Maybe Bind(Maybe maybe, Func> onJust) 119 | { 120 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 121 | 122 | return maybe.MatchJust(out T value) ? onJust(value) : Nothing(); 123 | } 124 | #endregion 125 | 126 | #region Functor 127 | /// Transforms a Maybe value by using a specified mapping function. 128 | public static Maybe Map(Maybe maybe, Func onJust) 129 | { 130 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 131 | 132 | return maybe.MatchJust(out T value) ? Just(onJust(value)) : Nothing(); 133 | } 134 | #endregion 135 | 136 | /// If both Maybe values contain a value, it merges them into a Maybe 137 | /// with a tupled value. 138 | public static Maybe<(T, U)> Merge(Maybe first, Maybe second) 139 | { 140 | U value2 = default; 141 | return (first.MatchJust(out T value1) && 142 | second.MatchJust(out value2)) switch { 143 | true => Just((value1, value2)), 144 | _ => Nothing<(T, U)>() 145 | }; 146 | } 147 | 148 | /// Executes the given function on a Just success or returns a Nothing. 149 | public static Maybe Try(Func func) 150 | { 151 | if (func == null) throw new ArgumentException(nameof(func)); 152 | 153 | try { 154 | return Just(func()); 155 | } 156 | catch { 157 | return Nothing(); 158 | } 159 | } 160 | 161 | /// Maps Either right value to Just, otherwise returns 162 | /// Nothing. 163 | public static Maybe FromEither(Either either) => 164 | (either.Tag == EitherType.Right) switch 165 | { 166 | true => Just(either.FromRight()), 167 | _ => Nothing() 168 | }; 169 | } 170 | 171 | /// Provides convenience extension methods for Maybe. 172 | #if !CSX_TYPES_INTERNAL 173 | public 174 | #endif 175 | static class MaybeExtensions 176 | { 177 | #region Alternative Match Methods 178 | /// Provides pattern matching using System.Func delegates. 179 | public static Unit Match(this Maybe maybe, 180 | Func onJust, Func onNothing) 181 | { 182 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 183 | if (onNothing == null) throw new ArgumentNullException(nameof(onNothing)); 184 | 185 | return maybe.MatchJust(out T value) switch { 186 | true => onJust(value), 187 | _ => onNothing() 188 | }; 189 | } 190 | 191 | /// Provides pattern matching using System.Func delegates over a Maybe 192 | /// with tupled wrapped value. 193 | public static Unit Match(this Maybe<(T, U)> maybe, 194 | Func onJust, Func onNothing) 195 | { 196 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 197 | if (onNothing == null) throw new ArgumentNullException(nameof(onNothing)); 198 | 199 | return maybe.MatchJust(out T value1, out U value2) switch { 200 | true => onJust(value1, value2), 201 | _ => onNothing() 202 | }; 203 | } 204 | 205 | /// Matches a value returning true and the tupled value itself via two output 206 | /// parameters. 207 | public static bool MatchJust(this Maybe<(T, U)> maybeTuple, 208 | out T value1, out U value2) 209 | { 210 | if (maybeTuple.MatchJust(out (T, U) value)) { 211 | value1 = value.Item1; 212 | value2 = value.Item2; 213 | return true; 214 | } 215 | value1 = default; 216 | value2 = default; 217 | return false; 218 | } 219 | #endregion 220 | 221 | #region Monad 222 | /// Equivalent to monadic Return operation. Builds a Just value in case 223 | /// value is different from its default. 224 | /// 225 | public static Maybe ToMaybe(this T value) => Maybe.Return(value); 226 | 227 | /// Invokes a function on this maybe value that itself yields a maybe. 228 | public static Maybe Bind(this Maybe maybe, Func> onJust) => 229 | Maybe.Bind(maybe, onJust); 230 | 231 | /// Transforms a maybe value by using a specified mapping function. 232 | public static Maybe Map(this Maybe maybe, Func onJust) => 233 | Maybe.Map(maybe, onJust); 234 | 235 | /// Unwraps a value applying a function o returns another value on fail. 236 | public static U Return(this Maybe maybe, Func onJust, U @default) => 237 | maybe.MatchJust(out T value) ? onJust(value) : @default; 238 | #endregion 239 | 240 | /// This is a version of map which can throw out the value. If contains a Just 241 | /// executes a mapping function over it, in case of Nothing returns @default. 242 | public static U Map(this Maybe maybe, Func onJust, U @default = default) 243 | { 244 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 245 | 246 | return maybe.MatchJust(out T value) ? onJust(value) : @default; 247 | } 248 | 249 | /// Lazy version of Map. If contains a Just executes a mapping function 250 | /// over it, in case of Nothing returns a value built by @default function. 251 | public static U Map(this Maybe maybe, Func onJust, Func @default) 252 | { 253 | if (onJust == null) throw new ArgumentNullException(nameof(onJust)); 254 | 255 | return maybe.MatchJust(out T value) ? onJust(value) : @default(); 256 | } 257 | 258 | #region LINQ Operators 259 | /// Map operation compatible with LINQ. 260 | public static Maybe Select(this Maybe maybe, 261 | Func selector) => Maybe.Map(maybe, selector); 262 | 263 | /// Bind operation compatible with LINQ. 264 | public static Maybe SelectMany(this Maybe maybe, 265 | Func> valueSelector, Func resultSelector) => 266 | maybe 267 | .Bind(sourceValue => 268 | valueSelector(sourceValue) 269 | .Map(resultValue => resultSelector(sourceValue, resultValue))); 270 | 271 | /// Returns the same Maybe value if the predicate is true, otherwise 272 | /// Nothing. 273 | public static Maybe Where(this Maybe maybe, 274 | Func predicate) 275 | { 276 | if (maybe.MatchJust(out TSource value)) { 277 | if (predicate(value)) return maybe; 278 | } 279 | return Maybe.Nothing(); 280 | } 281 | #endregion 282 | 283 | #region Do Semantic 284 | /// If contains a value executes a System.Func delegate over it. 285 | public static Unit Do(this Maybe maybe, Func func) 286 | { 287 | if (func == null) throw new ArgumentNullException(nameof(func)); 288 | 289 | return maybe.MatchJust(out T value) switch { 290 | true => func(value), 291 | _ => Unit.Default 292 | }; 293 | } 294 | 295 | /// If contans a value executes a System.Func delegate over it. 296 | public static Unit Do(this Maybe<(T, U)> maybe, Func func) 297 | { 298 | if (func == null) throw new ArgumentNullException(nameof(func)); 299 | 300 | return maybe.MatchJust(out T value1, out U value2) switch { 301 | true => func(value1, value2), 302 | _ => Unit.Default 303 | }; 304 | } 305 | #endregion 306 | 307 | /// Returns true if it is in form of Nothing. 308 | public static bool IsNothing(this Maybe maybe) => maybe.Tag == MaybeType.Nothing; 309 | 310 | /// Returns true if it is in form of Just. 311 | public static bool IsJust(this Maybe maybe) => maybe.Tag == MaybeType.Just; 312 | 313 | /// Extracts the element out of Just and returns a default value (or @default 314 | /// when given) if it is in form of Nothing. 315 | public static T FromJust(this Maybe maybe, T @default = default) => maybe.MatchJust(out T value) ? value : @default; 316 | 317 | /// Lazy version of FromJust. Extracts the element out of Just and returns 318 | /// a value built by @default function if it is in form of Nothing. 319 | public static T FromJust(this Maybe maybe, Func @default) => maybe.MatchJust(out T value) ? value : @default(); 320 | 321 | /// Extracts the element out of Just or throws an exception if it is form of 322 | /// Nothing. 323 | public static T FromJustOrFail(this Maybe maybe, Exception exceptionToThrow = null) => 324 | maybe.MatchJust(out T value) switch 325 | { 326 | true => value, 327 | _ => throw exceptionToThrow ?? new Exception("The value is empty.") 328 | }; 329 | 330 | #region Sequences 331 | /// Returns an empty sequence when given Nothing or a singleton sequence in 332 | /// case of Just. 333 | public static IEnumerable ToEnumerable(this Maybe maybe) 334 | { 335 | return _(); IEnumerable _() 336 | { 337 | if (maybe.MatchJust(out T value)) yield return value; 338 | } 339 | } 340 | 341 | /// Takes a sequence of Maybe and counts all the Nothing values. 342 | public static int Nothings(this IEnumerable> source) 343 | { 344 | if (source == null) throw new ArgumentNullException(nameof(source)); 345 | 346 | var count = 0; 347 | foreach (var maybe in source) { 348 | if (maybe.Tag == MaybeType.Just) count++; 349 | } 350 | return count; 351 | } 352 | 353 | /// Takes a sequence of Maybe and returns a sequence of all the Just 354 | /// values. 355 | public static IEnumerable Justs(this IEnumerable> source) 356 | { 357 | if (source == null) throw new ArgumentNullException(nameof(source)); 358 | 359 | return _(); IEnumerable _() 360 | { 361 | foreach (var maybe in source) { 362 | if (maybe.Tag == MaybeType.Just) yield return maybe.FromJust(); 363 | } 364 | } 365 | } 366 | 367 | /// This is a version of map which can throw out elements. In particular, the functional 368 | /// argument returns something of type Maybe<U>. If this is Nothing, no element is 369 | /// added on to the result sequence. If it is Just<U>, then U is included 370 | /// in the result sequence. 371 | public static IEnumerable Map(this IEnumerable source, Func> onElement) 372 | { 373 | if (source == null) throw new ArgumentNullException(nameof(source)); 374 | 375 | return _(); IEnumerable _() 376 | { 377 | foreach (var element in source) { 378 | if (onElement(element).MatchJust(out U value)) yield return value; 379 | } 380 | } 381 | } 382 | #endregion 383 | } 384 | } -------------------------------------------------------------------------------- /src/CSharpx/Result.cs: -------------------------------------------------------------------------------- 1 | //requires: ExceptionExtensions.cs, Unit.cs, Maybe.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | namespace CSharpx 10 | { 11 | 12 | #if !CSX_TYPES_INTERNAL 13 | public 14 | #endif 15 | enum ResultType 16 | { 17 | Success, 18 | Failure 19 | } 20 | 21 | #if !CSX_TYPES_INTERNAL 22 | public 23 | #endif 24 | struct Error : IEquatable 25 | { 26 | readonly Lazy _comparer => new Lazy( 27 | () => new ExceptionEqualityComparer()); 28 | Exception _exception; 29 | public string Message { get; private set; } 30 | public Maybe Exception => _exception.ToMaybe(); 31 | 32 | internal Error(string message, Exception exception) 33 | { 34 | if (message == null) throw new ArgumentNullException(nameof(message)); 35 | 36 | Message = message; 37 | _exception = exception; 38 | } 39 | 40 | public override bool Equals(object other) 41 | { 42 | if (other == null) return false; 43 | if (other.GetType() != typeof(Error)) return false; 44 | var otherError = (Error)other; 45 | return otherError.Message.Equals(Message) && 46 | _comparer.Value.Equals(otherError._exception, _exception); 47 | } 48 | 49 | public bool Equals(Error other) => 50 | other.Message.Equals(Message) && 51 | _comparer.Value.Equals(other._exception, _exception); 52 | 53 | public static bool operator ==(Error left, Error right) => left.Equals(right); 54 | 55 | public static bool operator !=(Error left, Error right) => !left.Equals(right); 56 | 57 | public override int GetHashCode() => 58 | _exception == null 59 | ? Message.GetHashCode() 60 | : Message.GetHashCode() ^ _exception.GetHashCode(); 61 | 62 | public override string ToString() => Exception.IsJust() 63 | ? new StringBuilder(capacity: 256) 64 | .AppendLine($"{Message}:") 65 | .AppendLine(Exception.FromJust().ToStringEx()) 66 | .ToString() 67 | : Message; 68 | 69 | sealed class ExceptionEqualityComparer : IEqualityComparer 70 | { 71 | public bool Equals(Exception first, Exception second) 72 | { 73 | if (first == null && second == null) return true; 74 | if (first == null || second == null) return false; 75 | if (first.GetType() != second.GetType()) return false; 76 | if (first.Message != second.Message) return false; 77 | if (first.InnerException != null) return Equals(first.InnerException, second.InnerException); 78 | return true; 79 | } 80 | 81 | public int GetHashCode(Exception exception) 82 | { 83 | var hash = exception.Message.GetHashCode(); 84 | if (exception.InnerException != null) hash ^= exception.InnerException.Message.GetHashCode(); 85 | return hash; 86 | } 87 | } 88 | } 89 | 90 | #if !CSX_TYPES_INTERNAL 91 | public 92 | #endif 93 | struct Result : IEquatable 94 | { 95 | #if DEBUG 96 | internal 97 | #endif 98 | readonly Error _error; 99 | 100 | internal Result(Error error) 101 | { 102 | Tag = ResultType.Failure; 103 | _error = error; 104 | } 105 | 106 | public ResultType Tag { get; private set; } 107 | 108 | public override bool Equals(object other) 109 | { 110 | if (other == null) return false; 111 | var otherType = other.GetType(); 112 | if (otherType != GetType()) return false; 113 | var otherTag = (ResultType)otherType.GetProperty( 114 | "Tag", BindingFlags.Public | BindingFlags.Instance).GetValue(other); 115 | if (otherTag != Tag) return false; 116 | if (otherTag == ResultType.Success && Tag == ResultType.Success) return true; 117 | var otherField = otherType.GetField("_error", BindingFlags.NonPublic | BindingFlags.Instance); 118 | return otherField.GetValue(other).Equals(_error); 119 | } 120 | 121 | public bool Equals(Result other) => 122 | other.Tag != ResultType.Failure || _error.Equals(other._error); 123 | 124 | public static bool operator ==(Result left, Result right) => left.Equals(right); 125 | 126 | public static bool operator !=(Result left, Result right) => !left.Equals(right); 127 | 128 | public override int GetHashCode() => 129 | Tag == ResultType.Success 130 | ? ToString().GetHashCode() 131 | : _error.GetHashCode(); 132 | 133 | public override string ToString() => 134 | Tag switch { 135 | ResultType.Success => "", 136 | _ => _error.ToString() 137 | }; 138 | 139 | #region Value Case Constructors 140 | public static Result Failure(string error) => new Result( 141 | new Error(error, null)); 142 | 143 | public static Result Failure(string error, Exception exception) => new Result( 144 | new Error(error, exception)); 145 | 146 | public static Result Success => new Result(); 147 | #endregion 148 | 149 | #region Basic Match Methods 150 | public bool MatchFailure(out Error error) 151 | { 152 | error = Tag == ResultType.Failure ? _error : default; 153 | return Tag == ResultType.Failure; 154 | } 155 | 156 | public bool MatchSuccess() => Tag == ResultType.Success; 157 | #endregion 158 | } 159 | 160 | #if !CSX_TYPES_INTERNAL 161 | public 162 | #endif 163 | static class ResultExtensions 164 | { 165 | public static Unit Match(this Result result, 166 | Func onSuccess, Func onFailure) 167 | { 168 | if (onSuccess == null) throw new ArgumentNullException(nameof(onSuccess)); 169 | if (onFailure == null) throw new ArgumentNullException(nameof(onFailure)); 170 | 171 | return result.MatchFailure(out Error error) switch { 172 | true => onFailure(error), 173 | _ => onSuccess() 174 | }; 175 | } 176 | 177 | public static Unit Match(this Result result, 178 | Func onSuccess, Func onFailure) 179 | { 180 | if (onSuccess == null) throw new ArgumentNullException(nameof(onSuccess)); 181 | if (onFailure == null) throw new ArgumentNullException(nameof(onFailure)); 182 | 183 | return result.MatchFailure(out Error error) switch { 184 | true => onFailure(error.Message), 185 | _ => onSuccess() 186 | }; 187 | } 188 | 189 | public static Unit Match(this Result result, 190 | Func onSuccess, Func, Unit> onFailure) 191 | { 192 | if (onSuccess == null) throw new ArgumentNullException(nameof(onSuccess)); 193 | if (onFailure == null) throw new ArgumentNullException(nameof(onFailure)); 194 | 195 | return result.MatchFailure(out Error error) switch { 196 | true => onFailure(error.Exception), 197 | _ => onSuccess() 198 | }; 199 | } 200 | 201 | public static Unit Match(this Result result, 202 | Func onSuccess, Func onFailure) 203 | { 204 | if (onSuccess == null) throw new ArgumentNullException(nameof(onSuccess)); 205 | if (onFailure == null) throw new ArgumentNullException(nameof(onFailure)); 206 | 207 | return result.MatchFailure(out Error error) switch { 208 | true => onFailure(error.Exception.FromJust()), 209 | _ => onSuccess() 210 | }; 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /src/CSharpx/Strings.cs: -------------------------------------------------------------------------------- 1 | //requires: CryptoRandom.cs 2 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace CSharpx 11 | { 12 | #if !CSX_TYPES_INTERNAL 13 | public 14 | #endif 15 | static class StringUtil 16 | { 17 | static readonly Random _random = new CryptoRandom(); 18 | 19 | const string _chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 20 | 21 | /// Generates a random string of given length. 22 | public static string Generate(int length) 23 | { 24 | if (length < 0) throw new ArgumentException(nameof(length)); 25 | 26 | return new string((from c in Enumerable.Repeat(_chars, length) 27 | select c[_random.Next(c.Length)]).ToArray()); 28 | } 29 | } 30 | 31 | #if !CSX_TYPES_INTERNAL 32 | public 33 | #endif 34 | static class CharExtensions 35 | { 36 | /// Replicates a character for a given number of times using a seperator. 37 | public static string Replicate(this char value, int count, string separator = "") 38 | { 39 | if (count < 0) throw new ArgumentException(nameof(count)); 40 | if (separator == null) throw new ArgumentNullException(nameof(separator)); 41 | 42 | if (separator.Length == 0) return new string(value, count); 43 | 44 | var builder = new StringBuilder((1 + separator.Length) * count); 45 | for (var i = 0; i < count; i++) { 46 | builder.Append(value); 47 | builder.Append(separator); 48 | } 49 | return builder.ToString(0, builder.Length - separator.Length); 50 | } 51 | } 52 | 53 | #if !CSX_TYPES_INTERNAL 54 | public 55 | #endif 56 | static class StringExtensions 57 | { 58 | static readonly Random _random = new CryptoRandom(); 59 | static string[] _mangleChars = 60 | {"!", "\"", "£", "$", "%", "&", "/", "(", ")", "=", "?", "^", "[", "]", "*", "@", "°", 61 | "#", "§", ",", ";", ".", ":", "-", "_"}; 62 | static Regex _stripMl = new Regex(@"<[^>]*>", RegexOptions.Compiled | RegexOptions.Multiline); 63 | 64 | /// Determines if a string is composed only by letter characters. 65 | public static bool IsAlpha(this string value) 66 | { 67 | if (value == null) throw new ArgumentNullException(nameof(value)); 68 | 69 | foreach (var @char in value.ToCharArray()) { 70 | if (!char.IsLetter(@char) || char.IsWhiteSpace(@char)) { 71 | return false; 72 | } 73 | } 74 | return true; 75 | } 76 | 77 | /// Determines if a string is composed only by alphanumeric characters. 78 | public static bool IsAlphanumeric(this string value) 79 | { 80 | if (value == null) throw new ArgumentNullException(nameof(value)); 81 | 82 | foreach (var @char in value.ToCharArray()) { 83 | if (!char.IsLetterOrDigit(@char) || char.IsWhiteSpace(@char)) { 84 | return false; 85 | } 86 | } 87 | return true; 88 | } 89 | 90 | /// Determines if a string is contains any kind of white spaces. 91 | public static bool IsWhiteSpace(this string value) 92 | { 93 | if (value == null) throw new ArgumentNullException(nameof(value)); 94 | 95 | foreach (var @char in value.ToCharArray()) { 96 | if (char.IsWhiteSpace(@char)) { 97 | return true; 98 | } 99 | } 100 | return false; 101 | } 102 | 103 | /// Returns a copy of this string with first letter converted to uppercase. 104 | public static string ToUpperFirstLetter(this string value) 105 | { 106 | if (value == null) throw new ArgumentNullException(nameof(value)); 107 | if (value.Trim().Length == 0) throw new ArgumentException(nameof(value)); 108 | 109 | return $"{char.ToUpper(value[0])}{value.Substring(1)}"; 110 | } 111 | 112 | /// Returns a copy of this string with first letter converted to lowercase. 113 | public static string ToLowerFirstLetter(this string value) 114 | { 115 | if (value == null) throw new ArgumentNullException(nameof(value)); 116 | if (value.Trim().Length == 0) throw new ArgumentException(nameof(value)); 117 | 118 | return $"{char.ToLower(value[0])}{value.Substring(1)}"; 119 | } 120 | 121 | /// Replicates a string for a given number of times using a seperator. 122 | public static string Replicate(this string value, int count, string separator = "") 123 | { 124 | if (value == null) throw new ArgumentNullException(nameof(value)); 125 | if (count < 0) throw new ArgumentException(nameof(count)); 126 | if (separator == null) throw new ArgumentNullException(nameof(separator)); 127 | 128 | var builder = new StringBuilder((value.Length + separator.Length) * count); 129 | for (var i = 0; i < count; i++) { 130 | builder.Append(value); 131 | builder.Append(separator); 132 | } 133 | return builder.ToString(0, builder.Length - separator.Length); 134 | } 135 | 136 | /// Applies a given function to nth-word of string. 137 | public static string ApplyAt(this string value, int index, Func modifier) 138 | { 139 | if (value == null) throw new ArgumentNullException(nameof(value)); 140 | if (index < 0) throw new ArgumentException(nameof(index)); 141 | 142 | var words = value.Split().ToArray(); 143 | words[index] = modifier(words[index]); 144 | return string.Join(" ", words); 145 | } 146 | 147 | /// Selects a random index of a word that optionally satisfies a function. 148 | public static int ChoiceOfIndex(this string value, Func validator = null) 149 | { 150 | if (value == null) throw new ArgumentNullException(nameof(value)); 151 | 152 | Func _nullValidator = _ => true; 153 | var _validator = validator ?? _nullValidator; 154 | 155 | var words = value.Split(); 156 | var index = _random.Next(words.Length - 1); 157 | if (_validator(words[index])) { 158 | return index; 159 | } 160 | return ChoiceOfIndex(value, validator); 161 | } 162 | 163 | /// Mangles a string with a given number of non alphanumeric character in 164 | /// random positions. 165 | public static string Mangle(this string value, int times = 1, int maxLength = 1) 166 | { 167 | if (value == null) throw new ArgumentNullException(nameof(value)); 168 | if (times < 0) throw new ArgumentException(nameof(times)); 169 | if (maxLength < 0) throw new ArgumentException(nameof(maxLength)); 170 | if (times >= value.Length) throw new ArgumentException(nameof(times)); 171 | if (times == 0 || maxLength == 0) return value; 172 | 173 | var indexes = new List(times); 174 | int uniqueNext() 175 | { 176 | var index = _random.Next(value.Length - 1); 177 | if (indexes.Contains(index)) { 178 | return uniqueNext(); 179 | } 180 | return index; 181 | }; 182 | for (var i = 0; i < times; i++) { 183 | indexes.Add(uniqueNext()); 184 | } 185 | var mutations = indexes.OrderBy(index => index); 186 | 187 | var mangled = new StringBuilder(value.Length + times * maxLength); 188 | for (var i = 0; i < value.Length; i++) { 189 | mangled.Append(value[i]); 190 | if (mutations.Contains(i)) { 191 | mangled.Append( 192 | _mangleChars[_random.Next(_mangleChars.Length - 1)] 193 | .Replicate(maxLength, string.Empty)); 194 | 195 | } 196 | } 197 | return mangled.ToString(); 198 | } 199 | 200 | /// Takes a value and a string and `intersperses' that value between its words. 201 | public static string Intersperse(this string value, params object[] values) 202 | { 203 | if (value == null) throw new ArgumentNullException(nameof(value)); 204 | if (values.Length == 0) return value; 205 | 206 | var builder = new StringBuilder(value.Length + values.Length * 8); 207 | var words = value.Split(); 208 | var count = words.Length; 209 | var last = count - 1; 210 | for (var i = 0; i < count; i++) { 211 | builder.Append(words[i]); 212 | builder.Append(' '); 213 | if (i >= values.Length) continue; 214 | var element = values[i]; 215 | builder.Append(element); 216 | builder.Append(' '); 217 | } 218 | return builder.ToString().TrimEnd(); 219 | } 220 | 221 | /// Sanitizes a string removing non alphanumeric characters and optionally normalizing 222 | /// white spaces. 223 | public static string Sanitize(this string value, bool normalizeWhiteSpace = true) 224 | { 225 | if (value == null) throw new ArgumentNullException(nameof(value)); 226 | var builder = new StringBuilder(value.Length); 227 | foreach (var @char in value) { 228 | if (char.IsLetterOrDigit(@char)) { 229 | builder.Append(@char); 230 | } 231 | else if (char.IsWhiteSpace(@char)) { 232 | if (normalizeWhiteSpace) { 233 | builder.Append(' '); 234 | } else { 235 | builder.Append(@char); 236 | } 237 | } 238 | } 239 | return builder.ToString(); 240 | } 241 | 242 | /// Normalizes any white space character to a single white space. 243 | public static string NormalizeWhiteSpace(this string value) 244 | { 245 | if (value == null) throw new ArgumentNullException(nameof(value)); 246 | var trimmed = value.Trim(); 247 | var builder = new StringBuilder(trimmed.Length); 248 | var lastIndex = trimmed.Length - 2; 249 | for (var i = 0; i < trimmed.Length; i++) { 250 | var @char = trimmed[i]; 251 | if (char.IsWhiteSpace(@char)) { 252 | if (i != lastIndex && !char.IsWhiteSpace(trimmed[i + 1])) { 253 | builder.Append(' '); 254 | } 255 | } 256 | else { 257 | builder.Append(@char); 258 | } 259 | } 260 | return builder.ToString(); 261 | } 262 | 263 | /// Removes markup from a string. 264 | public static string StripMl(this string value) 265 | { 266 | if (value == null) throw new ArgumentNullException(nameof(value)); 267 | 268 | return _stripMl.Replace(value, string.Empty); 269 | } 270 | 271 | /// Removes words of given length. 272 | public static string StripByLength(this string value, int length) 273 | { 274 | if (value == null) throw new ArgumentNullException(nameof(value)); 275 | if (length < 0) throw new ArgumentException(nameof(length)); 276 | if (length == 0) return value; 277 | 278 | var stripByLen = new Regex( 279 | string.Concat(@"\b\w{1,", length, @"}\b"), 280 | RegexOptions.Compiled | RegexOptions.Multiline); 281 | return stripByLen.Replace(value, string.Empty); 282 | } 283 | 284 | /// Reduces a sequence of strings to a sequence of parts, splitted by space, 285 | /// of each original string. 286 | public static IEnumerable FlattenOnce(this IEnumerable source) 287 | { 288 | if (source == null) throw new ArgumentNullException(nameof(source)); 289 | 290 | return _(); IEnumerable _() 291 | { 292 | foreach (var element in source) { 293 | var parts = element.Split(); 294 | foreach (var part in parts) { 295 | yield return part; 296 | } 297 | } 298 | } 299 | } 300 | } 301 | } -------------------------------------------------------------------------------- /src/CSharpx/Unit.cs: -------------------------------------------------------------------------------- 1 | //#define CSX_TYPES_INTERNAL // Uncomment or define at build time to set accessibility to internal. 2 | 3 | using System; 4 | 5 | namespace CSharpx 6 | { 7 | /// The Unit type is a type that indicates the absence of a specific value; the 8 | /// Unit type has only a single value, which acts as a placeholder when no other value 9 | /// exists or is needed. 10 | #if !CSX_TYPES_INTERNAL 11 | public 12 | #endif 13 | struct Unit : IComparable 14 | { 15 | private static readonly Unit @default = new Unit(); 16 | 17 | /// Returns the hash code for this Unit. 18 | public override int GetHashCode() => 0; 19 | 20 | /// Determines whether this instance and a specified object, which must also be a 21 | /// Unit object, have the same value. 22 | public override bool Equals(object obj) => obj == null || obj is Unit; 23 | 24 | /// Compares always to equality. 25 | public int CompareTo(object obj) => 0; 26 | 27 | /// Converts this instance to a string representation. 28 | public override string ToString() => "()"; 29 | 30 | /// Unit singleton instance. 31 | public static Unit Default { get { return @default; } } 32 | 33 | /// Returns Unit after executing a delegate. 34 | public static Unit Do(Action action) 35 | { 36 | if (action == null) throw new ArgumentNullException(nameof(action)); 37 | 38 | action(); 39 | return Default; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/CSharpx/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core -------------------------------------------------------------------------------- /tests/CSharpx.Specs/CSharpx.Specs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | netcoreapp3.1 8 | false 9 | gsscoder 10 | gsscoder 11 | CSharpx 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Fakes/Arbitrary.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.FSharp.Collections; 2 | using FsCheck; 3 | 4 | static class ArbitraryNegativeIntegers 5 | { 6 | public static Arbitrary IntegerGenerator() => Gen.Choose(-60, -30).ToArbitrary(); 7 | } 8 | 9 | static class ArbitraryIntegers 10 | { 11 | public static Arbitrary IntegerGenerator() => Gen.Choose(-30, 30).ToArbitrary(); 12 | } 13 | 14 | static class ArbitraryPositiveIntegers 15 | { 16 | public static Arbitrary IntegerGenerator() => Gen.Choose(1, 60).ToArbitrary(); 17 | } 18 | 19 | static class ArbitraryListOfIntegers 20 | { 21 | public static Arbitrary> IntegerListGenerator() => Gen.ListOf(30, 22 | Gen.Choose(-30, 30)).ToArbitrary(); 23 | } 24 | 25 | static class ArbitraryListOfStrings 26 | { 27 | public static Arbitrary StringListGenerator() => Gen.Shuffle(new [] { 28 | string.Empty, "one", "1", "two", "2", "three", "3", null, "four", "4", string.Empty, "five", 29 | "5", "six", "6", null, "seven", "7", "eight", "8", "nine", "9", null, "ten", "10", string.Empty}) 30 | .ToArbitrary(); 31 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/CharExtensionsSpecs..cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using FluentAssertions; 3 | using CSharpx; 4 | 5 | public class CharExtensionsSpecs 6 | { 7 | [Theory] 8 | [InlineData('f', 0, "", "")] 9 | [InlineData('f', 1, "", "f")] 10 | [InlineData('f', 5, " ", "f f f f f")] 11 | public void Should_replicate(char value, int count, string separator, string expected) 12 | { 13 | var outcome = value.Replicate(count, separator); 14 | 15 | outcome.Should().Be(expected); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/EitherSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | using FluentAssertions; 6 | using FsCheck; 7 | using FsCheck.Xunit; 8 | using CSharpx; 9 | 10 | public class EitherSpecs 11 | { 12 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 13 | public void Shoud_build_Left(string[] values) 14 | { 15 | values.ForEach(value => { 16 | if (value == null) return; // Skip null values 17 | 18 | var outcome = Either.Left(value); 19 | 20 | outcome.IsLeft().Should().BeTrue(); 21 | outcome.FromLeft().Should().Be(value); 22 | }); 23 | } 24 | 25 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 26 | public void Shoud_build_Right(int value) 27 | { 28 | if (value == default) return; // Skip default values 29 | 30 | var outcome = Either.Right(value); 31 | 32 | outcome.IsRight().Should().BeTrue(); 33 | outcome.FromRight().Should().Be(value); 34 | } 35 | 36 | [Fact] 37 | public void Trying_to_get_a_value_from_Left_with_FromLeftOrFail_raises_Exception_in_case_of_Right() 38 | { 39 | var sut = Either.Right(new CryptoRandom().Next()); 40 | 41 | Action action = () => sut.FromLeftOrFail(); 42 | 43 | action.Should().ThrowExactly() 44 | .WithMessage("The value is empty."); 45 | } 46 | 47 | [Fact] 48 | public void Trying_to_get_a_value_from_Right_with_FromRightOrFail_raises_Exception_in_case_of_Left() 49 | { 50 | var sut = Either.Left("bad result"); 51 | 52 | Action action = () => sut.FromRightOrFail(); 53 | 54 | action.Should().ThrowExactly() 55 | .WithMessage("The value is empty."); 56 | } 57 | 58 | [Fact] 59 | public void Shoud_partition_lefts_from_rights() 60 | { 61 | var eithers = new List>() 62 | { 63 | Either.Left("foo"), 64 | Either.Right(3), 65 | Either.Left("bar"), 66 | Either.Right(7), 67 | Either.Left("baz"), 68 | }; 69 | 70 | var outcome = eithers.Partition(); 71 | 72 | outcome.Should().NotBeNull(); 73 | outcome.Item1.Should().NotBeNullOrEmpty() 74 | .And.HaveCount(3) 75 | .And.ContainInOrder(eithers.Lefts()); 76 | outcome.Item2.Should().NotBeNullOrEmpty() 77 | .And.HaveCount(2) 78 | .And.ContainInOrder(eithers.Rights()); 79 | } 80 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/EnumerableExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.FSharp.Collections; 5 | using Xunit; 6 | using FluentAssertions; 7 | using FsCheck; 8 | using FsCheck.Xunit; 9 | using CSharpx; 10 | 11 | public class EnumerableExtensionsSpecs 12 | { 13 | #region TryHead 14 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 15 | public void Trying_to_get_the_head_element_of_a_sequence_should_return_Just( 16 | FSharpList values) 17 | { 18 | var outcome = values.TryHead(); 19 | 20 | outcome.Should().Match>(x => 21 | x.Tag == MaybeType.Just && x._value == values.ElementAt(0)); 22 | } 23 | 24 | [Fact] 25 | public void Trying_to_get_the_head_element_of_an_empty_sequence_should_return_Nothing() 26 | { 27 | var outcome = Enumerable.Empty().TryHead(); 28 | 29 | outcome.Tag.Should().Be(MaybeType.Nothing); 30 | } 31 | #endregion 32 | 33 | #region TryLast 34 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 35 | public void Trying_to_get_the_last_element_of_a_sequence_should_return_Just( 36 | FSharpList values) 37 | { 38 | var outcome = values.TryLast(); 39 | 40 | outcome.Should().Match>(x => 41 | x.Tag == MaybeType.Just && x._value == values.Last()); 42 | } 43 | 44 | [Fact] 45 | public void Trying_to_get_the_last_element_of_an_empty_sequence_should_return_Nothing() 46 | { 47 | var outcome = Enumerable.Empty().TryLast(); 48 | 49 | outcome.Tag.Should().Be(MaybeType.Nothing); 50 | } 51 | #endregion 52 | 53 | #region ToMaybe 54 | [Fact] 55 | public void An_empty_sequence_should_be_converted_to_Nothing() 56 | { 57 | var outcome = Enumerable.Empty().ToMaybe(); 58 | 59 | outcome.Should().Match>>(x => x.Tag == MaybeType.Nothing); 60 | } 61 | 62 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 63 | public void An_not_empty_sequence_should_be_converted_to_Just(FSharpList values) 64 | { 65 | var outcome = values.ToMaybe(); 66 | 67 | outcome.Tag.Should().Be(MaybeType.Just); 68 | } 69 | #endregion 70 | 71 | #region Choose 72 | [Theory] 73 | [InlineData( 74 | new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 75 | new int[] {0, 2, 4, 6, 8})] 76 | public void Should_choose_elements_to_create_a_new_sequence( 77 | IEnumerable values, IEnumerable expected) 78 | { 79 | var outcome = values.Choose(x => x % 2 == 0 80 | ? Maybe.Just(x) 81 | : Maybe.Nothing()); 82 | 83 | outcome.Should().BeEquivalentTo(expected); 84 | } 85 | #endregion 86 | 87 | #region Intersperse 88 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 89 | public void Should_intersperse_a_value_in_a_sequence(int value) 90 | { 91 | var sequence = new int[] {0, 1, 2, 3, 4}; 92 | 93 | var outcome = sequence.Intersperse(value); 94 | 95 | outcome.Should().NotBeNullOrEmpty() 96 | .And.HaveCount(sequence.Count() * 2 - 1) 97 | .And.SatisfyRespectively( 98 | item => item.Should().Be(0), 99 | item => item.Should().Be(value), 100 | item => item.Should().Be(1), 101 | item => item.Should().Be(value), 102 | item => item.Should().Be(2), 103 | item => item.Should().Be(value), 104 | item => item.Should().Be(3), 105 | item => item.Should().Be(value), 106 | item => item.Should().Be(4) 107 | ); 108 | } 109 | #endregion 110 | 111 | #region FlattenOnce 112 | [Fact] 113 | public void Should_flatten_a_sequence_by_one_level() 114 | { 115 | var sequence = new List>() 116 | { 117 | new int[] {0, 1, 2}, 118 | new int[] {3, 4, 5}, 119 | new int[] {6, 7, 8} 120 | }; 121 | 122 | var outcome = sequence.FlattenOnce(); 123 | 124 | outcome.Should().BeEquivalentTo(new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}); 125 | } 126 | #endregion 127 | 128 | #region Tail 129 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 130 | public void Should_return_the_tail_of_a_sequence(FSharpList values) 131 | { 132 | var outcome = values.Tail(); 133 | 134 | outcome.Should().HaveCount(values.Count() - 1) 135 | .And.BeEquivalentTo(values.Skip(1)); 136 | } 137 | 138 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 139 | public void Should_return_the_tail_of_a_sequence_using_TailOrEmpty(FSharpList values) 140 | { 141 | var outcome = values.TailOrEmpty(); 142 | 143 | outcome.Should().HaveCount(values.Count() - 1) 144 | .And.BeEquivalentTo(values.Skip(1)); 145 | } 146 | 147 | [Fact] 148 | public void Trying_to_get_the_tail_of_an_empty_sequence_throws_ArgumentException() 149 | { 150 | Action action = () => { foreach (var _ in Enumerable.Empty().Tail()) { } }; 151 | 152 | action.Should().ThrowExactly() 153 | .WithMessage("The input sequence has an insufficient number of elements."); 154 | } 155 | 156 | [Fact] 157 | public void Trying_to_get_the_tail_of_an_empty_sequence_returns_an_empty_sequence_using_TailOrEmpty() 158 | { 159 | var outcome = Enumerable.Empty().TailOrEmpty(); 160 | 161 | outcome.Should().HaveCount(0) 162 | .And.BeEquivalentTo(Enumerable.Empty()); 163 | } 164 | #endregion 165 | 166 | [Fact] 167 | public void Should_materialize_a_sequence() 168 | { 169 | Action action = () => NullYielder(true).Materialize(); 170 | 171 | action.Should().Throw(); 172 | 173 | IEnumerable NullYielder(bool raise) 174 | { 175 | if (raise) throw new Exception(); 176 | 177 | yield return null; 178 | } 179 | } 180 | 181 | #region ChunkBySize 182 | [Fact] 183 | public void Should_partition_a_sequence_by_chunk_size_into_arrays_without_remainder() 184 | { 185 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; 186 | 187 | var outcome = values.ChunkBySize(3); 188 | 189 | outcome.Should().NotBeNullOrEmpty() 190 | .And.HaveCount(3) 191 | .And.SatisfyRespectively( 192 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2 }), 193 | item => item.Should().BeEquivalentTo(new int[] { 3, 4, 5 }), 194 | item => item.Should().BeEquivalentTo(new int[] { 6, 7, 8 })); 195 | } 196 | 197 | [Fact] 198 | public void Should_partition_a_sequence_by_chunk_size_into_arrays_with_remainder() 199 | { 200 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 201 | 202 | var outcome = values.ChunkBySize(3); 203 | 204 | outcome.Should().NotBeNullOrEmpty() 205 | .And.HaveCount(4) 206 | .And.SatisfyRespectively( 207 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2 }), 208 | item => item.Should().BeEquivalentTo(new int[] { 3, 4, 5 }), 209 | item => item.Should().BeEquivalentTo(new int[] { 6, 7, 8 }), 210 | item => item.Should().BeEquivalentTo(new int[] { 9, 10 })); 211 | } 212 | 213 | [Fact] 214 | public void Should_partition_a_sequence_in_a_single_chunk_if_chunk_size_is_equal_than_elements_count() 215 | { 216 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 217 | 218 | var outcome = values.ChunkBySize(10); 219 | 220 | outcome.Should().NotBeNullOrEmpty() 221 | .And.HaveCount(1) 222 | .And.SatisfyRespectively( 223 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); 224 | } 225 | 226 | [Fact] 227 | public void Should_partition_a_sequence_in_a_single_chunk_if_chunk_size_is_greater_than_elements_count() 228 | { 229 | var values = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 230 | 231 | var outcome = values.ChunkBySize(11); 232 | 233 | outcome.Should().NotBeNullOrEmpty() 234 | .And.HaveCount(1) 235 | .And.SatisfyRespectively( 236 | item => item.Should().BeEquivalentTo(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })); 237 | } 238 | 239 | [Theory] 240 | [InlineData(0)] 241 | [InlineData(-1)] 242 | public void Trying_to_partition_a_sequence_by_zero_or_less_chunks_throws_ArgumentException( 243 | int value) 244 | { 245 | Action action = () => { foreach (var _ in new[] { 0, 1, 2 }.ChunkBySize(value)) {}; }; 246 | 247 | action.Should().ThrowExactly() 248 | .WithMessage("The input must be positive."); 249 | } 250 | 251 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 252 | public void Trying_to_partition_a_sequence_by_a_negative_number_of_chunks_throws_ArgumentException( 253 | FSharpList values) 254 | { 255 | Action action = () => { foreach (var _ in values.ChunkBySize(-1)) {}; }; 256 | 257 | action.Should().ThrowExactly() 258 | .WithMessage("The input must be positive."); 259 | } 260 | #endregion 261 | 262 | #region SplitAt 263 | [Fact] 264 | public void Should_split_a_sequence_before_index() 265 | { 266 | var value = new int[] { 0, 1, 3, 4, 5 }; 267 | var outcome = value.SplitAt(3); 268 | 269 | outcome.Item1.Should().NotBeNullOrEmpty().And.HaveCount(3).And.ContainInOrder(0, 1, 3); 270 | outcome.Item2.Should().NotBeNullOrEmpty().And.HaveCount(2).And.ContainInOrder(4, 5); 271 | } 272 | 273 | [Fact] 274 | public void Left_array_is_empty_when_splitting_at_index_zero() 275 | { 276 | var value = new int[] { 0, 1, 3, 4, 5 }; 277 | var outcome = value.SplitAt(0); 278 | 279 | outcome.Item1.Should().BeEmpty(); 280 | outcome.Item2.Should().NotBeNullOrEmpty().And.BeEquivalentTo(value); 281 | } 282 | 283 | [Fact] 284 | public void Right_array_is_empty_when_splitting_with_index_equal_to_sequence_elements_count() 285 | { 286 | var value = new int[] { 0, 1, 3, 4, 5 }; 287 | var outcome = value.SplitAt(value.Count()); 288 | 289 | outcome.Item1.Should().NotBeNullOrEmpty().And.BeEquivalentTo(value); 290 | outcome.Item2.Should().BeEmpty(); 291 | } 292 | 293 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 294 | public void Trying_to_split_a_sequence_with_a_negative_index_throws_ArgumentException( 295 | FSharpList values) 296 | { 297 | Action action = () => values.SplitAt(-1); 298 | 299 | action.Should().ThrowExactly() 300 | .WithMessage("The input must be non-negative."); 301 | } 302 | 303 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 304 | public void Trying_to_split_a_sequence_with_an_index_greater_than_elements_count_throws_ArgumentException( 305 | FSharpList values) 306 | { 307 | Action action = () => values.SplitAt(values.Count() + 1); 308 | 309 | action.Should().ThrowExactly() 310 | .WithMessage("The input sequence has an insufficient number of elements."); 311 | } 312 | #endregion 313 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/FSharpResultExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using FluentAssertions; 4 | using FsCheck; 5 | using FsCheck.Xunit; 6 | using Microsoft.FSharp.Core; 7 | using CSharpx; 8 | 9 | public class FSharpResultExtensionsSpecs 10 | { 11 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 12 | public void Should_match_a_result(int value) 13 | { 14 | int? expected = null; 15 | var sut = FSharpResult.NewOk(value); 16 | 17 | sut.Match( 18 | matched => expected = value, 19 | _ => { throw new InvalidOperationException(); } 20 | ); 21 | 22 | expected.Should().Be(value); 23 | } 24 | 25 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 26 | public void Should_match_an_error(int value) 27 | { 28 | string error = null; 29 | var sut = FSharpResult.NewError("bad result"); 30 | 31 | sut.Match( 32 | _ => { throw new InvalidOperationException(); }, 33 | message => { error = message; } 34 | ); 35 | 36 | error.Should().Be("bad result"); 37 | } 38 | 39 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 40 | public void Should_map_a_value(int value) 41 | { 42 | var sut = FSharpResult.NewOk(value); 43 | 44 | Func func = x => x / 0.5; 45 | var outcome = sut.Map(func); 46 | 47 | outcome.IsOk.Should().BeTrue(); 48 | outcome.ResultValue.Should().Be(func(value)); 49 | } 50 | 51 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 52 | public void Should_keep_error_when_mapping_a_value_of_a_fail(int value) 53 | { 54 | var sut = FSharpResult.NewError("bad result"); 55 | 56 | var outcome = sut.Map(x => x / 0.5); 57 | 58 | outcome.IsOk.Should().BeFalse(); 59 | outcome.ResultValue.Should().Be(default); 60 | } 61 | 62 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 63 | public void Should_bind_a_value(int value) 64 | { 65 | var sut = FSharpResult.NewOk(value); 66 | 67 | Func> func = 68 | x => FSharpResult.NewOk(x / 0.5); 69 | var outcome = sut.Bind(func); 70 | 71 | outcome.IsOk.Should().BeTrue(); 72 | outcome.ResultValue.Should().Be(func(value).ResultValue); 73 | } 74 | 75 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 76 | public void Should_keep_error_when_binding_a_value_of_fail(int value) 77 | { 78 | var sut = FSharpResult.NewError("bad result"); 79 | 80 | var outcome = sut.Bind(x => FSharpResult.NewOk(x / 0.5)); 81 | 82 | outcome.IsOk.Should().BeFalse(); 83 | outcome.ResultValue.Should().Be(default); 84 | } 85 | 86 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 87 | public void Should_return_a_value(int value) 88 | { 89 | var sut = FSharpResult.NewOk(value); 90 | 91 | var outcome = sut.ReturnOrFail(); 92 | 93 | outcome.Should().Be(value); 94 | } 95 | 96 | [Fact] 97 | public void Should_throws_exception_on_a_fail() 98 | { 99 | var sut = FSharpResult.NewError("bad result"); 100 | 101 | Action action = () => sut.ReturnOrFail(); 102 | 103 | action.Should().ThrowExactly() 104 | .WithMessage("bad result"); 105 | } 106 | 107 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 108 | public void Should_return_and_map_a_value(int value) 109 | { 110 | var sut = FSharpResult.NewOk(value); 111 | 112 | Func func = x => x / 0.5; 113 | var outcome = sut.Return(func, 0); 114 | 115 | outcome.Should().Be(func(value)); 116 | } 117 | 118 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 119 | public void Should_return_alternate_value_on_a_file(int value) 120 | { 121 | var sut = FSharpResult.NewError("bad result"); 122 | 123 | Func func = x => x / 0.5; 124 | var outcome = sut.Return(func, 0); 125 | 126 | outcome.Should().Be(0); 127 | } 128 | 129 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 130 | public void Should_build_a_Maybe_Just_from_a_success(int value) 131 | { 132 | var sut = FSharpResult.NewOk(value); 133 | 134 | var outcome = sut.ToMaybe(); 135 | 136 | outcome.IsJust().Should().BeTrue(); 137 | outcome.FromJust().Should().Be(value); 138 | } 139 | 140 | [Fact] 141 | public void Should_build_a_Maybe_Nothing_from_a_fail() 142 | { 143 | var sut = FSharpResult.NewError("bad result"); 144 | 145 | var outcome = sut.ToMaybe(); 146 | 147 | outcome.IsNothing().Should().BeTrue(); 148 | } 149 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/MaybeSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Xunit; 4 | using FluentAssertions; 5 | using FsCheck; 6 | using FsCheck.Xunit; 7 | using CSharpx; 8 | 9 | public class MaybeSpecs 10 | { 11 | static Random _random = new CryptoRandom(); 12 | 13 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 14 | public void Shoud_build_Just(int value) 15 | { 16 | if (value == default) return; // Skip default values 17 | 18 | var outcome = Maybe.Just(value); 19 | 20 | outcome.IsJust().Should().BeTrue(); 21 | outcome.FromJust().Should().Be(value); 22 | } 23 | 24 | [Fact] 25 | public void Shoud_build_Nothing() 26 | { 27 | var outcome = Maybe.Nothing(); 28 | 29 | outcome.IsNothing().Should().BeTrue(); 30 | outcome.Tag.Should().Be(MaybeType.Nothing); 31 | } 32 | 33 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 34 | public void Shoud_return_proper_maybe_with_a_value_type(int value) 35 | { 36 | var outcome = Maybe.Return(value); 37 | 38 | outcome.Should().NotBeNull(); 39 | 40 | if (value == default) { 41 | outcome.IsNothing().Should().BeTrue(); 42 | } 43 | else { 44 | outcome.IsJust().Should().BeTrue(); 45 | outcome.FromJust().Should().Be(value); 46 | } 47 | } 48 | 49 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 50 | public void Shoud_return_proper_maybe_with_a_reference_type(string[] values) 51 | { 52 | values.ForEach(value => 53 | { 54 | var outcome = Maybe.Return(value); 55 | 56 | outcome.Should().NotBeNull(); 57 | 58 | if (value == null) { 59 | outcome.IsNothing().Should().BeTrue(); 60 | } 61 | else { 62 | outcome.IsJust().Should().BeTrue(); 63 | outcome.FromJust().Should().Be(value); 64 | } 65 | }); 66 | } 67 | 68 | 69 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 70 | public void FromJust_should_unwrap_the_value_or_lazily_return_from_a_function(string[] values) 71 | { 72 | values.ForEach(value => 73 | { 74 | Func func = () => "foo"; 75 | 76 | var sut = Maybe.Return(value); 77 | 78 | var outcome = sut.FromJust(func); 79 | 80 | if (value == null) outcome.Should().NotBeNull().And.Be(func()); 81 | else outcome.Should().NotBeNull().And.Be(value); 82 | }); 83 | } 84 | 85 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 86 | public void Should_return_a_singleton_sequence_with_Just_and_an_empty_with_Nothing(string[] values) 87 | { 88 | values.ForEach(value => 89 | { 90 | var sut = Maybe.Return(value); 91 | 92 | var outcome = sut.ToEnumerable(); 93 | 94 | if (value == null) { 95 | outcome.Should().NotBeNull().And.BeEmpty(); 96 | } 97 | else { 98 | outcome.Should().NotBeNullOrEmpty().And.HaveCount(1); 99 | outcome.ElementAt(0).Should().Be(value); 100 | } 101 | }); 102 | } 103 | 104 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 105 | public void Should_return_Just_values_from_a_sequence(string[] values) 106 | { 107 | var maybes = from value in values select Maybe.Return(value); 108 | 109 | var outcome = maybes.Justs(); 110 | 111 | outcome.Should().NotBeNullOrEmpty() 112 | .And.HaveCountLessOrEqualTo(values.Count()) 113 | .And.ContainInOrder(from value in values where value != null select value); 114 | } 115 | 116 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 117 | public void Should_count_Nothing_values_of_a_sequence(string[] values) 118 | { 119 | var maybes = from value in values select Maybe.Return(values); 120 | 121 | var outcome = maybes.Nothings(); 122 | 123 | outcome.Should().BeLessOrEqualTo(values.Count()); 124 | } 125 | 126 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 127 | public void Should_throw_out_Just_values_from_a_sequence(string[] values) 128 | { 129 | Func> readInt = value => { 130 | if (int.TryParse(value, out int result)) return Maybe.Just(result); 131 | return Maybe.Nothing(); }; 132 | 133 | var expected = from value in values where int.TryParse(value, out int _) 134 | select int.Parse(value); 135 | 136 | var outcome = values.Map(readInt); 137 | 138 | outcome.Should().NotBeNullOrEmpty() 139 | .And.HaveCount(expected.Count()) 140 | .And.ContainInOrder(expected); 141 | } 142 | 143 | 144 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfIntegers) })] 145 | public void Should_match_Just_of_anonymous_tuple_type_with_lambda_function(int value) 146 | { 147 | var sut = Maybe.Just((value, value / 2)); 148 | 149 | int? outcome1 = null; 150 | int? outcome2 = null; 151 | sut.Match( 152 | (value1, value2) => Unit.Do(() => { outcome1 = value1; outcome2 = value2; }), 153 | () => Unit.Default); 154 | outcome1.Should().NotBeNull().And.Be(value); 155 | outcome2.Should().NotBeNull().And.Be(value / 2); 156 | } 157 | 158 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 159 | public void Should_match_Just_of_anonymous_tuple_type(int value) 160 | { 161 | var sut = Maybe.Just((value, value / 2)); 162 | 163 | var outcome = sut.MatchJust(out int outcome1, out int outcome2); 164 | 165 | outcome.Should().BeTrue(); 166 | outcome1.Should().Be(value); 167 | outcome2.Should().Be(value /2); 168 | } 169 | 170 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 171 | public void Should_throw_out_and_map_a_Just_value_or_lazily_build_one_in_case_of_Nothing(string[] values) 172 | { 173 | values.ForEach(value => 174 | { 175 | Func func = () => "foo"; 176 | 177 | var sut = Maybe.Return(value); 178 | 179 | var outcome = sut.Map(v => v, func); 180 | 181 | if (value == null) outcome.Should().NotBeNull().And.Be(func()); 182 | else outcome.Should().NotBeNull().And.Be(value); 183 | }); 184 | } 185 | 186 | [Theory] 187 | [InlineData(0)] 188 | [InlineData(1)] 189 | public void Should_return_a_Just_when_Try_succeed_otherwise_Nothing(int value) 190 | { 191 | var number = new CryptoRandom().Next(); 192 | var outcome = Maybe.Try(() => number / value); 193 | 194 | if (value == 0) outcome.Tag.Should().Be(MaybeType.Nothing); 195 | else outcome.Should().NotBeNull().And.Match>(x => 196 | x.Tag == MaybeType.Just && 197 | x._value == number); 198 | } 199 | 200 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 201 | public void Should_return_false_when_a_Maybe_is_compared_to_null(int value) 202 | { 203 | var sut = Maybe.Return(value); 204 | 205 | var outcome = sut.Equals(null); 206 | 207 | outcome.Should().BeFalse(); 208 | } 209 | 210 | [Fact] 211 | public void Nothing_wrapping_same_type_are_equals() 212 | { 213 | var sut1 = Maybe.Nothing(); 214 | var sut2 = Maybe.Nothing(); 215 | 216 | var outcome = sut1.Equals(sut2); 217 | 218 | outcome.Should().BeTrue(); 219 | } 220 | 221 | [Fact] 222 | public void Nothing_wrapping_same_type_compared_as_object_are_equals() 223 | { 224 | var sut1 = Maybe.Nothing(); 225 | object sut2 = Maybe.Nothing(); 226 | 227 | var outcome = sut1.Equals(sut2); 228 | 229 | outcome.Should().BeTrue(); 230 | } 231 | 232 | [Fact] 233 | public void Nothing_wrapping_different_type_are_different() 234 | { 235 | var sut1 = Maybe.Nothing(); 236 | var sut2 = Maybe.Nothing(); 237 | 238 | var outcome = sut1.Equals(sut2); 239 | 240 | outcome.Should().BeFalse(); 241 | } 242 | 243 | 244 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 245 | public void Maybe_of_different_type_are_not_equals(int value) 246 | { 247 | if (value == default) return; // Skip default values 248 | 249 | var sut1 = Maybe.Nothing(); 250 | var sut2 = Maybe.Return(value); 251 | 252 | var outcome = sut1.Equals(sut2); 253 | 254 | outcome.Should().BeFalse(); 255 | } 256 | 257 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 258 | public void Maybe_of_different_type_compared_as_object_are_not_equals(int value) 259 | { 260 | if (value == default) return; // Skip default values 261 | 262 | var sut1 = Maybe.Nothing(); 263 | object sut2 = Maybe.Return(value); 264 | 265 | var outcome = sut1.Equals(sut2); 266 | 267 | outcome.Should().BeFalse(); 268 | } 269 | 270 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 271 | public void Maybe_with_different_values_are_not_equals(int value) 272 | { 273 | if (value == default) return; // Skip default values 274 | 275 | var sut1 = Maybe.Return(value); 276 | var otherValue = _random.Next(); 277 | var sut2 = Maybe.Return(otherValue == value ? otherValue / 2 : otherValue); 278 | 279 | var outcome = sut1.Equals(sut2); 280 | 281 | outcome.Should().BeFalse(); 282 | } 283 | 284 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 285 | public void Maybe_with_identical_values_are_equals(int value) 286 | { 287 | if (value == default) return; // Skip default values 288 | 289 | var sut1 = Maybe.Return(value); 290 | var sut2 = Maybe.Return(value); 291 | 292 | var outcome = sut1.Equals(sut2); 293 | 294 | outcome.Should().BeTrue(); 295 | } 296 | 297 | [Property(Arbitrary = new[] { typeof(ArbitraryIntegers) })] 298 | public void Maybe_with_identical_values_compared_as_object_are_equals(int value) 299 | { 300 | if (value == default) return; // Skip default values 301 | 302 | var sut1 = Maybe.Return(value); 303 | object sut2 = Maybe.Return(value); 304 | 305 | var outcome = sut1.Equals(sut2); 306 | 307 | outcome.Should().BeTrue(); 308 | } 309 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/ResultSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Xunit; 4 | using FluentAssertions; 5 | using FsCheck; 6 | using FsCheck.Xunit; 7 | using CSharpx; 8 | 9 | public class ResultSpecs 10 | { 11 | static Random _random = new CryptoRandom(); 12 | 13 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 14 | public void Error_with_same_string_and_exception_are_equal(string[] values) 15 | { 16 | values.ForEach(value => 17 | { 18 | if (value == null) return; // Skip null values 19 | 20 | var outcome = new Error( 21 | $"custom message {value}", new Exception($"exception message {value}")).Equals( 22 | new Error( 23 | $"custom message {value}", new Exception($"exception message {value}"))); 24 | 25 | outcome.Should().BeTrue(); 26 | }); 27 | } 28 | 29 | [Fact] 30 | public void Shoud_build_Success() 31 | { 32 | var outcome = CSharpx.Result.Success; 33 | 34 | outcome.Tag.Should().Be(CSharpx.ResultType.Success); 35 | } 36 | 37 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 38 | public void Shoud_build_Failure_with_string(string[] values) 39 | { 40 | values.ForEach(value => 41 | { 42 | if (value == null) return; // Skip null values 43 | 44 | var outcome = CSharpx.Result.Failure(value); 45 | 46 | outcome.Tag.Should().Be(CSharpx.ResultType.Failure); 47 | outcome._error.Should().Be(new Error(value, null)); 48 | }); 49 | } 50 | 51 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 52 | public void Shoud_build_Failure_with_string_and_exception(string[] values) 53 | { 54 | values.ForEach(value => 55 | { 56 | if (value == null) return; // Skip null values 57 | 58 | var outcome = CSharpx.Result.Failure($"custom message {value}", 59 | new Exception($"exception message {value}")); 60 | 61 | outcome.Tag.Should().Be(CSharpx.ResultType.Failure); 62 | outcome._error.Should().Be( 63 | new Error($"custom message {value}", new Exception($"exception message {value}"))); 64 | }); 65 | } 66 | 67 | [Fact] 68 | public void Result_of_type_Success_are_equal() 69 | { 70 | var result1 = CSharpx.Result.Success; 71 | var result2 = CSharpx.Result.Success; 72 | 73 | var outcome = result1.Equals(result2); 74 | 75 | outcome.Should().BeTrue(); 76 | } 77 | 78 | [Fact] 79 | public void Result_of_type_Failure_with_same_error_are_equal() 80 | { 81 | var result1 = CSharpx.Result.Failure("something gone wrong", new Exception("here a trouble")); 82 | var result2 = CSharpx.Result.Failure("something gone wrong", new Exception("here a trouble")); 83 | 84 | var outcome = result1.Equals(result2); 85 | 86 | outcome.Should().BeTrue(); 87 | } 88 | 89 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 90 | public void Result_of_type_Failure_with_different_error_strings_are_not_equal(string[] values) 91 | { 92 | values.ForEach(value => 93 | { 94 | if (value == null) return; // Skip null values 95 | 96 | var result1 = CSharpx.Result.Failure( 97 | value, new Exception("here a trouble")); 98 | var result2 = CSharpx.Result.Failure( 99 | $"{value}{StringUtil.Generate(3)}", new Exception("here a trouble")); 100 | 101 | var outcome = result1.Equals(result2); 102 | 103 | outcome.Should().BeFalse(); 104 | }); 105 | } 106 | 107 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 108 | public void Result_of_type_Failure_with_different_exceptions_are_not_equal(string[] values) 109 | { 110 | values.ForEach(value => 111 | { 112 | if (value == null) return; // Skip null values 113 | 114 | var result1 = CSharpx.Result.Failure( 115 | "something gone wrong", new Exception(value)); 116 | var result2 = CSharpx.Result.Failure( 117 | "something gone wrong", new Exception($"{value}{StringUtil.Generate(3)}")); 118 | 119 | var outcome = result1.Equals(result2); 120 | 121 | outcome.Should().BeFalse(); 122 | }); 123 | } 124 | 125 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 126 | public void Result_of_type_Failure_with_different_errors_are_not_equal(string[] values) 127 | { 128 | values.ForEach(value => 129 | { 130 | if (value == null) return; // Skip null values 131 | 132 | var result1 = CSharpx.Result.Failure( 133 | value, new Exception(value)); 134 | var result2 = CSharpx.Result.Failure( 135 | $"{value}{StringUtil.Generate(3)}", new Exception($"{value}{StringUtil.Generate(3)}")); 136 | 137 | var outcome = result1.Equals(result2); 138 | 139 | outcome.Should().BeFalse(); 140 | }); 141 | } 142 | 143 | [Fact] 144 | public void Should_match_Success() 145 | { 146 | var result = CSharpx.Result.Success; 147 | 148 | var outcome = result.MatchSuccess(); 149 | 150 | outcome.Should().BeTrue(); 151 | } 152 | 153 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 154 | public void Should_match_Failure(string[] values) 155 | { 156 | values.ForEach(value => 157 | { 158 | if (value == null) return; // Skip null values 159 | 160 | var result = CSharpx.Result.Failure(value, new Exception("here a trouble")); 161 | 162 | var outcome = result.MatchFailure(out Error outcome1); 163 | 164 | outcome.Should().BeTrue(); 165 | outcome1.Should().Be(new Error(value, new Exception("here a trouble"))); 166 | }); 167 | } 168 | 169 | [Property(Arbitrary = new[] { typeof(ArbitraryListOfStrings) })] 170 | public void Should_match_Failure_with_message_only(string[] values) 171 | { 172 | values.ForEach(value => 173 | { 174 | if (value == null) return; // Skip null values 175 | 176 | var result = CSharpx.Result.Failure(value); 177 | 178 | var outcome = result.MatchFailure(out Error outcome1); 179 | 180 | outcome.Should().BeTrue(); 181 | outcome1.Should().Be(new Error(value, null)); 182 | }); 183 | } 184 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/StringExtensionsSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Xunit; 5 | using FluentAssertions; 6 | using CSharpx; 7 | 8 | public class StringExtensionsSpecs 9 | { 10 | [Theory] 11 | [InlineData("foo", true)] 12 | [InlineData("0123456789", false)] 13 | [InlineData("foo01234", false)] 14 | [InlineData("foo.bar", false)] 15 | [InlineData("foo bar", false)] 16 | public void Should_detect_letter_characters(string value, bool expected) 17 | { 18 | var outcome = value.IsAlpha(); 19 | 20 | outcome.Should().Be(expected); 21 | } 22 | 23 | [Theory] 24 | [InlineData("foo", true)] 25 | [InlineData("0123456789", true)] 26 | [InlineData("foo01234", true)] 27 | [InlineData("foo.bar", false)] 28 | [InlineData("foo bar", false)] 29 | public void Should_detect_alphanumeric_characters(string value, bool expected) 30 | { 31 | var outcome = value.IsAlphanumeric(); 32 | 33 | outcome.Should().Be(expected); 34 | } 35 | 36 | [Theory] 37 | [InlineData("foo01234", false)] 38 | [InlineData("foo.bar", false)] 39 | [InlineData("foo bar", true)] 40 | [InlineData("foo\nbar", true)] 41 | [InlineData("foo\tbar", true)] 42 | public void Should_detect_whitespace_characters(string value, bool expected) 43 | { 44 | var outcome = value.IsWhiteSpace(); 45 | 46 | outcome.Should().Be(expected); 47 | } 48 | 49 | #region Sanitize 50 | [Theory] 51 | [InlineData("foo bar@", "foo bar")] 52 | [InlineData("foo\tbar@", "foo bar")] 53 | [InlineData("foo-bar@", "foobar")] 54 | public void Should_sanitize_strings_normalizing_white_spaces(string value, string expected) 55 | { 56 | var outcome = value.Sanitize(); 57 | 58 | outcome.Should().Be(expected); 59 | } 60 | 61 | [Theory] 62 | [InlineData("foo\nbar@", "foo\nbar")] 63 | [InlineData("foo\tbar@", "foo\tbar")] 64 | public void Should_sanitize_strings_without_normalizing_white_spaces(string value, string expected) 65 | { 66 | var outcome = value.Sanitize(normalizeWhiteSpace: false); 67 | 68 | outcome.Should().Be(expected); 69 | } 70 | #endregion 71 | 72 | [Theory] 73 | [InlineData("hello this is a test", new object[] {'!', "!!", 10}, "hello ! this !! is 10 a test")] 74 | public void Should_intersperse_values(string value, object[] values, string expected) 75 | { 76 | var outcome = value.Intersperse(values); 77 | 78 | outcome.Should().Be(expected); 79 | } 80 | 81 | [Theory] 82 | [InlineData("foo", 0, "", "")] 83 | [InlineData("foo", 1, "", "foo")] 84 | [InlineData("foo", 5, " ", "foo foo foo foo foo")] 85 | public void Should_replicate(string value, int count, string separator, string expected) 86 | { 87 | var outcome = value.Replicate(count, separator); 88 | 89 | outcome.Should().Be(expected); 90 | } 91 | 92 | #region Mangle 93 | [Theory] 94 | [InlineData("foo", 0, 0)] 95 | [InlineData("foo", 1, 1)] 96 | [InlineData("fooo", 3, 2)] 97 | [InlineData("foo bar", 3, 3)] 98 | public void Should_mangle(string value, int times, int maxLength) 99 | { 100 | int expectedMangleSize = times * maxLength; 101 | 102 | var outcome = value.Mangle(times, maxLength); 103 | 104 | outcome.Length.Should().Be(value.Length + expectedMangleSize); 105 | 106 | var mangleSize = (from @char in outcome.ToCharArray() 107 | where !char.IsLetterOrDigit(@char) && !char.IsWhiteSpace(@char) 108 | select @char).Count(); 109 | 110 | mangleSize.Should().Be(expectedMangleSize); 111 | } 112 | 113 | [Fact] 114 | public void Mangle_same_string_length_throws_ArgumentException() 115 | { 116 | Action action = () => "foo".Mangle(3, 3); 117 | 118 | action.Should().ThrowExactly() 119 | .WithMessage("times"); 120 | } 121 | 122 | [Fact] 123 | public void Mangle_beyond_string_length_throws_ArgumentException() 124 | { 125 | Action action = () => "foo bar baz".Mangle(100, 3); 126 | 127 | action.Should().ThrowExactly() 128 | .WithMessage("times"); 129 | } 130 | #endregion 131 | 132 | [Theory] 133 | [InlineData("foo", "foo")] 134 | [InlineData(" foo ", "foo")] 135 | [InlineData(" foo\t bar\t\t baz\t", "foo bar baz")] 136 | public void Should_normalize_white_spaces(string value, string expected) 137 | { 138 | var outcome = value.NormalizeWhiteSpace(); 139 | 140 | outcome.Should().Be(expected);; 141 | } 142 | 143 | [Theory] 144 | [InlineData("foo bar baz", 2, "foo bar baz")] 145 | [InlineData("fooo bar baz", 3, "fooo ")] 146 | public void Should_strip_by_length(string value, int length, string expected) 147 | { 148 | var outcome = value.StripByLength(length); 149 | 150 | outcome.Should().Be(expected); 151 | } 152 | 153 | [Theory] 154 | [InlineData( 155 | new string[] {"foo bar baz", "fooo baar baaz"}, 156 | new string[] {"foo", "bar", "baz", "fooo", "baar", "baaz"})] 157 | public void Should_flatten_a_string_sequence_into_words( 158 | IEnumerable values, IEnumerable expected) 159 | { 160 | var outcome = values.FlattenOnce(); 161 | 162 | outcome.Should().BeEquivalentTo(expected); 163 | } 164 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/StringUtilSpecs.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System; 3 | using Xunit; 4 | using FluentAssertions; 5 | using FsCheck; 6 | using FsCheck.Xunit; 7 | using CSharpx; 8 | 9 | public class StringUtilsSpecs 10 | { 11 | static List _strings = new List() { StringUtil.Generate(new CryptoRandom().Next(1, 60)) }; 12 | 13 | [Property(Arbitrary = new[] { typeof(ArbitraryNegativeIntegers) })] 14 | public void Trying_to_generate_a_random_string_with_less_than_one_char_raises_ArgumentException(int value) 15 | { 16 | Action action = () => StringUtil.Generate(value); 17 | 18 | action.Should().ThrowExactly(); 19 | } 20 | 21 | [Fact] 22 | public void Should_generate_an_empty_string_when_length_is_zero() 23 | { 24 | var outcome = StringUtil.Generate(0); 25 | 26 | outcome.Should().NotBeNull().And.BeEmpty(); 27 | } 28 | 29 | [Property(Arbitrary = new[] { typeof(ArbitraryPositiveIntegers) })] 30 | public void Should_generate_a_random_string_of_given_length(int value) 31 | { 32 | var outcome = StringUtil.Generate(value); 33 | 34 | outcome.Should().NotBeNull().And.HaveLength(value); 35 | _strings.Should().NotContain(outcome); 36 | 37 | _strings.Add(outcome); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/Outcomes/UnitSpecs.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using FluentAssertions; 3 | using CSharpx; 4 | 5 | public class UnitSpecs 6 | { 7 | [Fact] 8 | public void Unit_values_should_be_equals() 9 | { 10 | var sut1 = new Unit(); 11 | var sut2 = new Unit(); 12 | 13 | var outcome = sut1.Equals(sut2); 14 | 15 | outcome.Should().BeTrue(); 16 | } 17 | 18 | [Fact] 19 | public void Unit_values_should_compare_to_equality() 20 | { 21 | var sut1 = new Unit(); 22 | var sut2 = new Unit(); 23 | 24 | var outcome = sut1.CompareTo(sut2); 25 | 26 | outcome.Should().Be(0); 27 | } 28 | 29 | [Fact] 30 | public void Do_executes_a_delegate_and_returns_Unit_value() 31 | { 32 | var evidence = 0; 33 | 34 | var outcome = Unit.Do(() => evidence++); 35 | 36 | evidence.Should().Be(1); 37 | outcome.Should().Be(Unit.Default); 38 | } 39 | } -------------------------------------------------------------------------------- /tests/CSharpx.Specs/paket.references: -------------------------------------------------------------------------------- 1 | FluentAssertions 2 | FsCheck 3 | FsCheck.Xunit 4 | Microsoft.NET.Test.Sdk 5 | xunit 6 | xunit.runner.visualstudio 7 | coverlet.collector --------------------------------------------------------------------------------