diff --git a/getting-started.md b/getting-started.md new file mode 100644 index 0000000..57d51c9 --- /dev/null +++ b/getting-started.md @@ -0,0 +1,208 @@ +# Getting Started + +This guide will help you get up and running with the Try Pattern library in just a few minutes. + +## Installation + +### NuGet Package + +Install the Try Pattern library via NuGet Package Manager: + +```bash +dotnet add package Wolfgang.TryPattern +``` + +Or using the Package Manager Console: + +```powershell +Install-Package Wolfgang.TryPattern +``` + +### Manual Installation + +Alternatively, you can clone the repository and reference the project directly: + +```bash +git clone https://github.com/Chris-Wolfgang/Try-Pattern.git +``` + +## Basic Usage + +### 1. Using Try with Async Operations + +The most common use case is wrapping async operations that may fail: + +```csharp +using Wolfgang.TryPattern; + +// Reading a file that might not exist +var result = await Try.RunAsync(() => File.ReadAllTextAsync("data.txt")); + +if (result.Succeeded) +{ + Console.WriteLine($"File content: {result.Value}"); +} +else +{ + Console.WriteLine($"Failed to read file: {result.ErrorMessage}"); +} +``` + +### 2. Using Try with Synchronous Operations + +You can also use the Try Pattern with synchronous operations: + +```csharp +using Wolfgang.TryPattern; + +// Parsing a JSON file +var result = Try.Run(() => JsonSerializer.Deserialize(jsonString)); + +if (result.Succeeded) +{ + ProcessData(result.Value); +} +else +{ + LogError(result.ErrorMessage); +} +``` + +### 3. Using Result for Custom Methods + +Use `Result` and `Result` as return types for your own methods: + +```csharp +using Wolfgang.TryPattern; + +public Result ValidateEmail(string email) +{ + if (string.IsNullOrWhiteSpace(email)) + { + return Result.Failure("Email cannot be empty"); + } + + if (!email.Contains("@")) + { + return Result.Failure("Email must contain @"); + } + + return Result.Success(); +} + +public Result GetUser(int userId) +{ + var user = database.FindUser(userId); + + if (user == null) + { + return Result.Failure("User not found"); + } + + return Result.Success(user); +} +``` + +## Common Patterns + +### Pattern 1: File Operations + +```csharp +var result = await Try.RunAsync(() => File.ReadAllTextAsync(filePath)); +if (!result.Succeeded) +{ + // Handle failure - file doesn't exist, no permissions, etc. + return; +} + +// Process the file content +var content = result.Value; +``` + +### Pattern 2: HTTP Requests + +```csharp +public async Task> FetchDataAsync(string url) +{ + using var response = await httpClient.GetAsync(url); + + if (!response.IsSuccessStatusCode) + { + return Result.Failure($"HTTP {response.StatusCode}"); + } + + var content = await response.Content.ReadAsStringAsync(); + return Result.Success(content); +} +``` + +### Pattern 3: Database Operations + +```csharp +public async Task SaveUserAsync(User user) +{ + if (user == null) + { + throw new ArgumentNullException(nameof(user)); // Programming error + } + + var saveResult = await Try.RunAsync(() => database.SaveAsync(user)); + + if (!saveResult.Succeeded) + { + return Result.Failure("Failed to save user to database"); + } + + return Result.Success(); +} +``` + +## Examples + +The repository includes several example projects demonstrating the Try Pattern in different scenarios: + +- **C# .NET 8 Example**: Modern C# with latest features +- **C# .NET 4.6.2 Example**: .NET Framework support +- **F# Examples**: Functional programming approach (.NET 4.6.2 and .NET 8) +- **VB.NET Examples**: Visual Basic support (.NET 4.6.2 and .NET 8) + +The library itself supports .NET Framework 4.6.2, .NET Standard 2.0, .NET 8.0, and .NET 10.0. + +You can find these examples in the `examples` folder of the repository. + +## API Reference + +### Try Class + +- `Try.Run(Func func)`: Execute a synchronous function +- `Try.RunAsync(Func> func)`: Execute an async function +- `Try.Run(Action action)`: Execute a synchronous action +- `Try.RunAsync(Func func)`: Execute an async action + +### Result Class + +- `Result.Success()`: Create a successful result +- `Result.Failure(string message)`: Create a failed result +- `Succeeded`: Boolean indicating success +- `ErrorMessage`: Error message when failed + +### Result Class + +- `Result.Success(T value)`: Create a successful result with a value +- `Result.Failure(string message)`: Create a failed result +- `Succeeded`: Boolean indicating success +- `Value`: The returned value when successful +- `ErrorMessage`: Error message when failed + +## Next Steps + +- Read the [Introduction](introduction.md) for a deeper understanding of the Try Pattern +- Check out the examples in the `examples` folder +- Review the [readme.md](readme.md) for more detailed examples +- Explore the source code in the `src/Wolfgang.TryPattern` folder + +## Need Help? + +- Open an issue on [GitHub](https://github.com/Chris-Wolfgang/Try-Pattern/issues) +- Check the [Contributing Guide](CONTRIBUTING.md) if you want to contribute +- Review the [Code of Conduct](CODE_OF_CONDUCT.md) diff --git a/introduction.md b/introduction.md new file mode 100644 index 0000000..9e6c7f7 --- /dev/null +++ b/introduction.md @@ -0,0 +1,85 @@ +# Introduction + +Welcome to the Try Pattern library! This library provides a simple and effective way to implement the Try Pattern in your C# applications. + +## What is the Try Pattern? + +The Try Pattern is a design pattern where code execution returns success/failure status without exposing detailed exception information to the caller. This is particularly useful when: + +- The caller only needs to know if an operation succeeded or failed +- The specific reason for failure is not relevant to the caller +- You want to avoid using exceptions for control flow + +## Why Use the Try Pattern? + +### Benefits + +1. **Simplified Error Handling**: No need to catch and handle specific exceptions +2. **Cleaner Code**: Reduces try-catch blocks throughout your codebase +3. **Better Performance**: Avoids the overhead of exception creation for expected failures +4. **Clearer Intent**: Makes it explicit that an operation may fail + +### Real-World Examples + +You're already familiar with the Try Pattern from .NET's built-in types: +- `int.TryParse(string, out int)` +- `DateTime.TryParse(string, out DateTime)` +- `Dictionary.TryGetValue(TKey, out TValue)` + +## How This Library Helps + +This library extends the Try Pattern to any operation by providing: + +1. **`Try` Class**: Static helper methods to execute operations and handle exceptions +2. **`Result` Type**: Represents the outcome of an operation without a return value +3. **`Result` Type**: Represents the outcome of an operation that returns a value + +### Compatibility + +The library supports .NET Framework 4.6.2 through .NET 10.0, including: +- .NET Framework 4.6.2 and later +- .NET Standard 2.0 +- .NET 8.0 +- .NET 10.0 + +## Quick Example + +```csharp +// Without Try Pattern - traditional approach +try +{ + var content = await File.ReadAllTextAsync("config.json"); + ProcessConfig(content); +} +catch (Exception ex) +{ + Console.WriteLine("Error reading config: " + ex.Message); +} + +// With Try Pattern - simplified approach +var result = await Try.RunAsync(() => File.ReadAllTextAsync("config.json")); +if (result.Succeeded) +{ + ProcessConfig(result.Value); +} +else +{ + Console.WriteLine("Error reading config: " + result.ErrorMessage); +} +``` + +## When to Use the Try Pattern + +**Use the Try Pattern when:** +- Operations may fail in expected ways (file not found, network timeout, etc.) +- The caller only needs success/failure information +- You want to avoid exception-based control flow + +**Don't use the Try Pattern when:** +- You need specific exception details for different error handling +- The failure is truly exceptional and should propagate up the call stack +- You're dealing with programming errors (null arguments, invalid state) + +## Next Steps + +Ready to get started? Check out the [Getting Started](getting-started.md) guide to learn how to install and use the library in your projects. diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..915a833 --- /dev/null +++ b/readme.md @@ -0,0 +1,170 @@ +# Try Pattern + +## Definition + +The `Try Pattern` is a design pattern in which code is executed but the caller only cares if the code +succeeded or failed and the exact reason for failure is not important. For example, a user may need +to update a record in a database via a web page. If the update fails, they only need to know that it +failed, not the specific reason for the failure. Connection timeout, login failed, user doesn't have +access, etc., are irrelevant to the user. They just need to know if the update succeeded or failed. + +A variation of this, is calling a method that returns a value (i.e. a function). In this case if the +call succeeds, the caller wants the return value from the method. However, if the call fails, the +caller does not care about the specific reason for the failure. This can be seen in many of the +built-in types in C#, such as `int.TryParse` or `DateTime.TryParse`. In these methods, if the operation +succeeds, it returns true and the parsed value is returned via an out parameter. If the method fails +it returns false and the default value is returned via the output parameter. + + +## Library Description + +This library makes it easy to implement the Try Pattern in your own code. It has a +`static` `class` called [`Try`](src/Wolfgang.TryPattern/Try.cs) that provides a set +of `static` helper methods that implement the Try Pattern for both +actions (methods that do not return a value) and functions (methods that return a value). +These helper methods, execute the [`Action`](https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-10.0) +or [`Func`](https://learn.microsoft.com/en-us/dotnet/api/system.func-1?view=net-10.0) +passed in, handle any exceptions, and returns a [`Result`](src/Wolfgang.TryPattern/Result.cs) +for actions, or [`Result`](src/Wolfgang.TryPattern/Result.cs) for functions. + +## Compatibility + +This library supports the following .NET platforms: +- .NET Framework 4.6.2 and later +- .NET Standard 2.0 +- .NET 8.0 +- .NET 10.0 + +## Usage +```csharp + + // Try reading a file async + var result = await Try.RunAsync(() => File.ReadAllTextAsync(@".\sample.txt")); + + // If file read failed, print the error message + if (result.Succeeded) + { + Console.WriteLine("File contents:"); + Console.WriteLine(result.Value); + } + else + { + Console.WriteLine("Error reading file: " + result.ErrorMessage); + } +``` + +## Other Uses for `Result` and `Result` + +The `Result` and `Result` types can be used outside of the Try Pattern +The classes work well as return types for methods that need to indicate success or failure +For example consider the following example of a method that validates user input: +```csharp +public Result ValidateUserInput(string input) +{ + if (string.IsNullOrWhiteSpace(input)) + { + return Result.Failure("Input cannot be empty."); + } + if (input.Length < 5) + { + return Result.Failure("Input must be at least 5 characters long."); + } + return Result.Success(); +} +``` + +A more complex example is a method that processes an order: +```csharp + +public class OrderProcessingService +{ + + private readonly HttpClient httpClient; + private readonly string endpoint; + + public OrderProcessingService(HttpClient httpClient, string endpoint) + { + if (httpClient == null) + { + throw new ArgumentNullException(nameof(httpClient)); + } + if (endpoint == null) + { + throw new ArgumentNullException(nameof(endpoint)); + } + this.httpClient = httpClient; + this.endpoint = endpoint; + } + + public async Task CreateOrderAsync(Order order) + { + + if (order == null) + { + // Throwing exception here because this is a programming error + throw new ArgumentNullException(nameof(order)); + } + + var payload = JsonSerializer.Serialize(order); + using var content = new StringContent(payload, Encoding.UTF8, "application/json"); + using var response = await httpClient.PostAsync(endpoint, content).ConfigureAwait(false); + + + if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500) + { + var reason = string.IsNullOrWhiteSpace(response.ReasonPhrase) ? "Client Error" : response.ReasonPhrase; + return Result.Failure($"Failed to create order: {(int)response.StatusCode} ({reason})."); + } + else if ((int)response.StatusCode >= 500 && (int)response.StatusCode < 600) + { + var reason = string.IsNullOrWhiteSpace(response.ReasonPhrase) ? "Server Error" : response.ReasonPhrase; + return Result.Failure($"Failed to create order: {(int)response.StatusCode} ({reason})."); + } + + // Success case for 2xx status codes + return Result.Success(); + } + + + public async Task> GetOrderAsync(string orderId) + { + + if (string.IsNullOrWhiteSpace(orderId)) + { + // Throwing exception here because this is a programming error + throw new ArgumentNullException(nameof(orderId)); + } + using var response = await httpClient.GetAsync($"{endpoint}/{orderId}").ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var confirmation = JsonSerializer.Deserialize(content); + return Result.Success(confirmation); + } + return Result.Failure($"Failed to get order confirmation. Status code: {response.StatusCode}"); + } +} +``` + + + + +## A Note on Using Exceptions for Control Flow + +Exceptions are generally intended for exceptional circumstances and not for regular control flow. +They are expensive to create and handle, and using them for control flow can lead to code that is harder to follow and understand + +If you have a function that searches for a specific value, it is better to return some value that indicates not found +such as `null`, `false`, or an empty collection, in the case where more than one result could be returned, rather than throwing an exception when the value is not found. + +When creating code that uses the Try Pattern, consider whether exceptions are the best way to indicate failure. +In the case where a user searches for specific data, it is very common that the value +they are searching for does not exist. As such, don't throw an exception when this happens but rather return a value that indicates not found. + +However, failing to connect to a database or a webservice are both +situations where exceptions are appropriate as these are not common occurrences and indicate something unexpected happened. + +Reading a config file or some required other application file that should always be present +is another good example of when to use exceptions for control flow. However, +an application that reads from a user specified file can expect that the file may not exist +and so in that case throwing an exception is probably not the best approach. diff --git a/setup.md b/setup.md new file mode 100644 index 0000000..acad7bc --- /dev/null +++ b/setup.md @@ -0,0 +1,199 @@ +# Setup Guide + +This guide provides instructions for setting up the Try Pattern library for development and usage. + +## For Library Users + +### Prerequisites + +- .NET SDK (the library supports .NET Framework 4.6.2, .NET Standard 2.0, .NET 8.0, and .NET 10.0) +- Visual Studio 2022, VS Code, or Rider (recommended) + +### Quick Setup + +1. **Install via NuGet** + ```bash + dotnet add package Wolfgang.TryPattern + ``` + +2. **Add using statement** + ```csharp + using Wolfgang.TryPattern; + ``` + +3. **Start using the library** + ```csharp + var result = await Try.RunAsync(() => SomeOperationAsync()); + ``` + +## For Contributors + +### Prerequisites + +- .NET 8.0 SDK or later (for building the library which targets .NET 4.6.2 through .NET 10.0) +- Git +- Visual Studio 2022, VS Code, or Rider + +### Clone the Repository + +```bash +git clone https://github.com/Chris-Wolfgang/Try-Pattern.git +cd Try-Pattern +``` + +### Build the Solution + +```bash +dotnet restore +dotnet build +``` + +### Run Tests + +```bash +dotnet test +``` + +### Project Structure + +``` +Try-Pattern/ +├── src/ +│ └── Wolfgang.TryPattern/ # Main library project +├── tests/ +│ └── Wolfgang.TryPattern.Tests/ # Unit tests +├── examples/ +│ ├── CSharp.DotNet8.Example/ # C# .NET 8 examples +│ ├── CSharp.DotNet462.Example/ # C# .NET Framework examples +│ ├── FSharp.DotNet8.Example/ # F# .NET 8 examples +│ ├── FSharp.DotNet462.Example/ # F# .NET Framework examples +│ ├── VB.DotNet8.Example/ # VB.NET .NET 8 examples +│ └── VB.DotNet462.Example/ # VB.NET .NET Framework examples +├── benchmarks/ # Performance benchmarks +└── docs/ # Documentation files +``` + +## Development Workflow + +### 1. Create a Feature Branch + +```bash +git checkout -b feature/your-feature-name +``` + +### 2. Make Changes + +Edit the code, add tests, and ensure all tests pass. + +### 3. Run Quality Checks + +```bash +# Run tests +dotnet test + +# Build the solution +dotnet build + +# Run benchmarks (optional) +cd benchmarks +dotnet run -c Release +``` + +### 4. Commit and Push + +```bash +git add . +git commit -m "Add: your feature description" +git push origin feature/your-feature-name +``` + +### 5. Create Pull Request + +Open a pull request on GitHub and wait for review. + +## Building Documentation + +The project uses DocFX for documentation generation. + +```bash +cd docfx_project +docfx build +docfx serve +``` + +Visit `http://localhost:8080` to view the documentation. + +## Running Examples + +Navigate to any example project and run: + +```bash +cd examples/CSharp.DotNet8.Example +dotnet run +``` + +## Troubleshooting + +### Build Errors + +**Issue**: Missing .NET SDK version +``` +Solution: Install the required .NET SDK from https://dotnet.microsoft.com/download +``` + +**Issue**: NuGet package restore fails +``` +Solution: Clear NuGet cache and restore +dotnet nuget locals all --clear +dotnet restore +``` + +### Test Failures + +**Issue**: Tests fail on first run +``` +Solution: Ensure all dependencies are restored +dotnet restore +dotnet build +dotnet test +``` + +## IDE Setup + +### Visual Studio 2022 + +1. Open `TryPattern.sln` +2. Restore NuGet packages (automatic) +3. Build solution (Ctrl+Shift+B) +4. Run tests (Ctrl+R, A) + +### VS Code + +1. Open folder in VS Code +2. Install C# extension +3. Use integrated terminal for commands: + ```bash + dotnet build + dotnet test + ``` + +### Rider + +1. Open `TryPattern.sln` +2. Restore NuGet packages (automatic) +3. Build solution +4. Run tests from Test Explorer + +## Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Support + +- **Issues**: Report bugs or request features on [GitHub Issues](https://github.com/Chris-Wolfgang/Try-Pattern/issues) +- **Discussions**: Ask questions in [GitHub Discussions](https://github.com/Chris-Wolfgang/Try-Pattern/discussions) +- **Documentation**: Check the [docs](docs/) folder for detailed documentation