Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The library splits extensions across three namespaces — callers must import th
| `ValueExtensions.cs` | `CSharpHelperExtensions.Values` | `In`, `IsBetween`, `ToJson` |
| `EnumerableExtensions.cs` | `CSharpHelperExtensions.Enumerable` | `IsNullOrEmpty<T>`, `HasAny`, `None`, `CleanNullOrEmptyItems`, `WhereNotNull`, `ContainsOnly`, `AreEqual`, `ForEach`, `Reduce`, `SelectAsync`, `WhenAllList`, `Partition`, `Batch`, `MinByOrDefault`, `MaxByOrDefault`, `ToDictionarySafe`, `AddIf`, `AddRangeIf`, `ConcatIf`, `IsSingle`, `IndexOf`, `Yield`, `WithIndex`, `JoinAsString`, `AsReadOnlyList`, `ToHashSetSafe`, `OrEmpty` |
| `StringExtensions.cs` | `CSharpHelperExtensions.Strings` | `IsNullOrEmpty`, `HasValue`, `OrEmpty`, `OrDefault`, `Truncate`, `Reverse`, `TrimToLower`, `TrimToUpper`, `ToTitleCase`, `ToSlug`, `MaskStart`, `EqualsIgnoreCase`, `ContainsIgnoreCase`, `StartsWithIgnoreCase`, `EndsWithIgnoreCase`, `EnsurePrefix`, `EnsureSuffix`, `TrimPrefix`, `TrimSuffix`, `SplitNonEmpty`, `JoinWith`, `ReplaceMany`, `RemoveWhitespace`, `CollapseWhitespace`, `RemoveDiacritics`, `IsNumeric`, `IsAlpha`, `IsAlphaNumeric`, `ToNullable<T>`, `ToIntOrNull`, `ToDecimalOrNull`, `ToDateTimeOrNull`, `ToGuidOrNull`, `ToBoolOrNull`, `Base64Encode`, `Base64Decode`, `ToBase64Url`, `FromBase64Url`, `ToUtf8Bytes`, `ToUtf8Stream` |
| `DictionaryExtensions.cs` | `CSharpHelperExtensions.Dictionaries` | `GetValueOrDefault`, `GetOrAdd`, `Merge`, `AddRange`, `RemoveWhere`, `AsReadOnly` |

`IsNullOrEmpty` exists in **both** `StringExtensions` (for `string`, namespace `CSharpHelperExtensions.Strings`) and `EnumerableExtensions` (for `IEnumerable<T>`, namespace `CSharpHelperExtensions.Enumerable`). Be careful about which namespace is imported.

Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
<PackageProjectUrl>https://github.com/rbipin/dry-extensions-csharp</PackageProjectUrl>
<RepositoryUrl>https://github.com/rbipin/dry-extensions-csharp</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReadmeFile>docs/README.md</PackageReadmeFile>
<PackageIcon>logo-nuget.png</PackageIcon>
<PackageReleaseNotes></PackageReleaseNotes>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)README.md" Pack="true" PackagePath="\" />
<None Include="$(MSBuildThisFileDirectory)docs\README.md" Pack="true" PackagePath="docs\" />
<None Include="$(MSBuildThisFileDirectory)assets\logo-nuget.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# CSharpHelperExtensions

A set of commonly used C# extension methods that reduce boilerplate across three focused namespaces: value checks, string manipulation, and collection operations.
A set of commonly used C# extension methods that reduce boilerplate across four focused namespaces: value checks, string manipulation, collection operations, and dictionary helpers.

[![.NET](https://github.com/rbipin/dry-extensions-csharp/actions/workflows/dotnet.yml/badge.svg)](https://github.com/rbipin/dry-extensions-csharp/actions/workflows/dotnet.yml)

Expand All @@ -21,6 +21,7 @@ Import the namespace for the extensions you need:
| `CSharpHelperExtensions.Values` | `In`, `IsBetween`, `ToJson` |
| `CSharpHelperExtensions.Strings` | All `string` extensions |
| `CSharpHelperExtensions.Enumerable` | All `IEnumerable<T>` and collection extensions |
| `CSharpHelperExtensions.Dictionaries` | All `IDictionary<TKey,TValue>` extensions |

## Interactive Samples

Expand All @@ -39,6 +40,7 @@ Each notebook loads the compiled DLL and imports the relevant namespace in its *
| [`sample/value-extensions.ipynb`](sample/value-extensions.ipynb) | `CSharpHelperExtensions.Values` | `In`, `IsBetween` (all four `BetweenComparison` modes), `ToJson`, and chaining examples |
| [`sample/string-extensions.ipynb`](sample/string-extensions.ipynb) | `CSharpHelperExtensions.Strings` | All 50+ string methods grouped by category: null-safety, parsing, transformation, whitespace, comparisons, prefix/suffix, encoding, and chaining pipelines |
| [`sample/enumerable-extension.ipynb`](sample/enumerable-extension.ipynb) | `CSharpHelperExtensions.Enumerable` | All collection methods: presence checks, materialization, async projection, partitioning, batching, conditional mutation, and chaining pipelines |
| [`sample/dictionary-extensions.ipynb`](sample/dictionary-extensions.ipynb) | `CSharpHelperExtensions.Dictionaries` | All dictionary methods: safe lookup, add-if-missing, merging, bulk add, in-place filtering, read-only views, and chaining pipelines |

## Usage

Expand Down Expand Up @@ -125,6 +127,30 @@ items.WithIndex(); // (Index, Item) tuples
names.JoinAsString(", "); // fluent string.Join
```

### Dictionaries

```csharp
using CSharpHelperExtensions.Dictionaries;

// Safe lookup — returns default instead of throwing on missing key or null dict
DictionaryExtensions.GetValueOrDefault(dict, "key"); // value or default(TValue)

// Add-if-missing — factory only called when key is absent
cache.GetOrAdd("user:1", key => LoadFromDb(key)); // existing or newly stored value

// Merge two dictionaries — overwrite:false keeps existing values (default)
defaults.Merge(overrides, overwrite: true); // returns same dict for chaining

// Bulk add from any IEnumerable<KeyValuePair>
inventory.AddRange(incomingItems); // returns same dict for chaining

// Filter in-place by key predicate
config.RemoveWhere(k => k.StartsWith("internal.")); // returns same dict for chaining

// Expose as a live read-only view
IReadOnlyDictionary<string, int> view = DictionaryExtensions.AsReadOnly(dict);
```

## Building and Testing

```bash
Expand Down
144 changes: 144 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# CSharpHelperExtensions

A set of commonly used C# extension methods that reduce boilerplate across four focused namespaces: value checks, string manipulation, collection operations, and dictionary helpers.

[![.NET](https://github.com/rbipin/dry-extensions-csharp/actions/workflows/dotnet.yml/badge.svg)](https://github.com/rbipin/dry-extensions-csharp/actions/workflows/dotnet.yml)

## Installation

```bash
dotnet add package CSharpHelperExtensions
```

## Namespaces

Import the namespace for the extensions you need:

| Namespace | What it covers |
|---|---|
| `CSharpHelperExtensions.Values` | `In`, `IsBetween`, `ToJson` |
| `CSharpHelperExtensions.Strings` | All `string` extensions |
| `CSharpHelperExtensions.Enumerable` | All `IEnumerable<T>` and collection extensions |
| `CSharpHelperExtensions.Dictionaries` | All `IDictionary<TKey,TValue>` extensions |

## Interactive Samples

The [`sample/`](https://github.com/rbipin/CSharpHelperExtensions/tree/main/sample) folder contains three [.NET Interactive](https://github.com/dotnet/interactive) notebooks you can run directly in VS Code (with the [Polyglot Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension) or Jupyter.

Each notebook loads the compiled DLL and imports the relevant namespace in its **Setup** cell — run that cell first, then run any section independently.

| Notebook | Namespace | What it covers |
|---|---|---|
| [`sample/value-extensions.ipynb`](https://github.com/rbipin/CSharpHelperExtensions/tree/main/sample/value-extensions.ipynb) | `CSharpHelperExtensions.Values` | `In`, `IsBetween` (all four `BetweenComparison` modes), `ToJson`, and chaining examples |
| [`sample/string-extensions.ipynb`](https://github.com/rbipin/CSharpHelperExtensions/tree/main/sample/string-extensions.ipynb) | `CSharpHelperExtensions.Strings` | All 50+ string methods grouped by category: null-safety, parsing, transformation, whitespace, comparisons, prefix/suffix, encoding, and chaining pipelines |
| [`sample/enumerable-extension.ipynb`](https://github.com/rbipin/CSharpHelperExtensions/tree/main/sample/enumerable-extension.ipynb) | `CSharpHelperExtensions.Enumerable` | All collection methods: presence checks, materialization, async projection, partitioning, batching, conditional mutation, and chaining pipelines |
| [`sample/dictionary-extensions.ipynb`](https://github.com/rbipin/CSharpHelperExtensions/tree/main/sample/dictionary-extensions.ipynb) | `CSharpHelperExtensions.Dictionaries` | All dictionary methods: safe lookup, add-if-missing, merging, bulk add, in-place filtering, read-only views, and chaining pipelines |

## Usage

### Values

```csharp
using CSharpHelperExtensions.Values;

// Membership check — like SQL IN
"admin".In("admin", "superadmin"); // true
HttpMethod.Post.In(Post, Put, Patch); // true

// Range check — inclusive by default
5.IsBetween(1, 10); // true
1.IsBetween(1, 10, BetweenComparison.ExcludeBoth); // false

// JSON serialisation via Newtonsoft.Json
new { Name = "Alice", Age = 30 }.ToJson(); // {"Name":"Alice","Age":30}
new { Name = "Alice" }.ToJson(indentation: true); // pretty-printed
```

### Strings

```csharp
using CSharpHelperExtensions.Strings;

// Null-safety
" ".IsNullOrEmpty(); // true (checks whitespace)
"hello".HasValue(); // true
((string)null).OrDefault("N/A"); // "N/A"

// Transformation
" Hello World ".TrimToLower(); // "hello world"
"café au lait".ToSlug(); // "cafe-au-lait"
"4111111111111234".MaskStart(4); // "************1234"

// Safe parsing — returns null instead of throwing
"42".ToIntOrNull(); // 42
"abc".ToIntOrNull(); // null

// Comparisons
"Hello".EqualsIgnoreCase("HELLO"); // true
"path/".EnsurePrefix("/"); // "/path/"
"report.csv".TrimSuffix(".csv"); // "report"

// Encoding
"Hello".Base64Encode(); // "SGVsbG8="
"Hello".ToBase64Url(); // URL-safe, no padding chars
```

### Enumerable

```csharp
using CSharpHelperExtensions.Enumerable;

// Null-safe presence checks
list.HasAny(); // non-null and non-empty
list.None(); // null or empty
list.OrEmpty(); // null → empty sequence

// Filtering
items.WhereNotNull(); // removes null elements
strings.CleanNullOrEmptyItems(); // removes null, empty, and whitespace strings

// Async projection with optional concurrency cap
var results = await ids.SelectAsync(FetchAsync, maxParallel: 4);

// Splitting
var (passed, failed) = scores.Partition(s => s >= 60);
var batches = items.Batch(100); // process in chunks

// Conditional building — fluent, returns same list
var tags = new List<string>()
.AddIf(isPremium, "premium")
.AddIf(isAdmin, "admin");

// Min/Max that return default instead of throwing on empty
people.MinByOrDefault(p => p.Age);
people.MaxByOrDefault(p => p.Age);

// Utilities
42.Yield(); // wrap a single value as IEnumerable<T>
items.WithIndex(); // (Index, Item) tuples
names.JoinAsString(", "); // fluent string.Join
```

### Dictionaries

```csharp
using CSharpHelperExtensions.Dictionaries;

// Safe lookup — returns default instead of throwing on missing key or null dict
DictionaryExtensions.GetValueOrDefault(dict, "key"); // value or default(TValue)

// Add-if-missing — factory only called when key is absent
cache.GetOrAdd("user:1", key => LoadFromDb(key)); // existing or newly stored value

// Merge two dictionaries — overwrite:false keeps existing values (default)
defaults.Merge(overrides, overwrite: true); // returns same dict for chaining

// Bulk add from any IEnumerable<KeyValuePair>
inventory.AddRange(incomingItems); // returns same dict for chaining

// Filter in-place by key predicate
config.RemoveWhere(k => k.StartsWith("internal.")); // returns same dict for chaining

// Expose as a live read-only view
IReadOnlyDictionary<string, int> view = DictionaryExtensions.AsReadOnly(dict);
```
Loading
Loading