Skip to content

Repository files navigation

ktsu.ScopedAction

A lightweight utility for executing paired actions at the start and end of code blocks.

LicenseNuGet VersionNuGet VersionNuGet DownloadsGitHub commit activityGitHub contributorsGitHub Actions Workflow Status

Introduction

ktsu.ScopedAction is a .NET utility that provides an abstract base class for executing actions at the beginning and end of code blocks. It implements the RAII (Resource Acquisition Is Initialization) pattern and leverages C#'s using statement and the IDisposable pattern to ensure that paired operations (like resource acquisition/release, state changes, or logging) are properly executed, even in the presence of exceptions.

As an abstract class, ScopedAction is designed to be inherited and extended to create specialized scoped behavior classes tailored to specific use cases.

Features

  • Abstract Base Class: Provides a foundation for creating specialized scoped action classes
  • RAII Pattern: Implements Resource Acquisition Is Initialization for deterministic resource management
  • Paired Actions: Execute actions when entering and exiting a scope
  • Exception Safety: Cleanup actions execute even if exceptions occur
  • Lightweight: Simple API with minimal overhead
  • Inheritance-Based: Designed to be extended for domain-specific implementations
  • Flexible: Works with any action delegates through protected constructor
  • Resource Management: Follows .NET's standard disposal pattern

RAII (Resource Acquisition Is Initialization)

ktsu.ScopedAction implements the RAII pattern, a programming idiom that binds the life cycle of a resource to the lifetime of an object. This ensures that:

  • Automatic Resource Management: Resources are automatically acquired when the object is constructed and released when it's destroyed
  • Exception Safety: Resources are properly released even if exceptions occur within the scope
  • Deterministic Execution: The OnClose action is guaranteed to execute when the object goes out of scope
  • Stack-Based Semantics: Leverages C#'s using statement to provide execution tied to lexical scope, mimicking C++ stack-based object destruction

The pattern is particularly useful for scenarios like:

  • File operations (open/close)
  • Database transactions (begin/commit or rollback)
  • Lock management (acquire/release)
  • Performance timing (start/stop)
  • Temporary state changes (set/restore)

Installation

Package Manager Console

Install-Package ktsu.ScopedAction

.NET CLI

dotnet add package ktsu.ScopedAction

Package Reference

<PackageReferenceInclude="ktsu.ScopedAction"Version="x.y.z" />

Usage Examples

The ScopedAction class supports three main patterns, progressing from simple to more complex scenarios:

Example 1: Using static methods without parameters

usingktsu.ScopedAction;publicclassConsoleMarkerScope():ScopedAction(Enter,Exit){// Using method groups - no lambdas needed when methods match Action signatureprivatestaticvoidEnter()=>Console.WriteLine("Entering scope");privatestaticvoidExit()=>Console.WriteLine("Exiting scope");}// Usageusing(newConsoleMarkerScope()){// Any code here...Console.WriteLine("Inside the scope");}// Output:// Entering scope// Inside the scope// Exiting scope

Example 2: Using static methods with parameters

usingktsu.ScopedAction;publicclassLoggingScope(stringoperation):ScopedAction(()=>Enter(operation),()=>Exit(operation)){// Using lambdas to capture constructor parameters for static methodsprivatestaticvoidEnter(stringoperation)=>Console.WriteLine($"Entering: {operation}");privatestaticvoidExit(stringoperation)=>Console.WriteLine($"Exiting: {operation}");}// Usageusing(newLoggingScope("my operation")){// Any code here...Console.WriteLine("Inside the scope");}// Output:// Entering: my operation// Inside the scope// Exiting: my operation

Example 3: Using instance members

usingktsu.ScopedAction;// This approach enables access to instance members in the OnClose actionpublicclassTimingScope:ScopedAction{privatereadonlyDateTimestartTime;// Instance fieldprivatereadonlystringoperation;// Instance fieldpublicTimingScope(stringoperation){this.operation=operation;this.startTime=DateTime.Now;// OnClose can reference instance method that accesses instance membersOnClose=LogExecutionTime;// No need to assign an OnOpen action - it would execute immediately anyway.// Instead, just perform the "on open" logic directly in the constructor.Console.WriteLine($"Starting: {operation}");}// Instance method with access to instance fieldsprivatevoidLogExecutionTime(){// Can directly access instance members: startTime, operationvarelapsed=DateTime.Now-startTime;Console.WriteLine($"Completed: {operation} in {elapsed.TotalMilliseconds:F2}ms");}}// Usageusing(newTimingScope("database query")){// Simulate some workThread.Sleep(100);Console.WriteLine("Executing query...");}// Output:// Starting: database query// Executing query...// Completed: database query in 100.xx ms

Choosing the Right Pattern

Example 1 (Method Groups): Use when you have simple static methods with no parameters. This is the most concise approach.

Example 2 (Lambda Capture): Use when you need to pass constructor parameters to static methods. Lambdas capture the parameters from the constructor scope.

Example 3 (Instance Members): Use when your OnClose logic needs access to instance state (fields, properties, methods). This pattern is essential for:

  • Complex resource management
  • Stateful cleanup operations
  • Scenarios where disposal behavior depends on data initialized during construction

The parameterless constructor approach gives you full access to the object's state, while the action-based constructors are limited to static methods and captured parameters.

API Reference

ScopedAction Class

An abstract base class for executing actions at scope boundaries. This class must be inherited to create concrete implementations.

Constructors

ConstructorParametersDescription
ScopedAction(Action? onOpen, Action? onClose)onOpen: Action executed on construction
onClose: Action executed on disposal
Protected constructor for derived classes that executes the onOpen action immediately and stores the onClose action for later execution during disposal
ScopedAction()NoneProtected parameterless constructor for derived classes that need custom initialization

Properties

PropertyTypeDescription
OnCloseAction?Protected property that stores the action to execute when the scoped action is disposed. Can be set by derived classes.

Methods

MethodReturn TypeDescription
Dispose()voidPublic method that implements the IDisposable interface. Executes the OnClose action if not already disposed and suppresses finalization.
Dispose(bool disposing)voidProtected virtual method for implementing the standard .NET dispose pattern. Executes the OnClose action when disposing is true and handles multiple disposal calls safely.

Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure to update tests as appropriate.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

About

A lightweight utility for executing paired actions at the start and end of code blocks using the IDisposable pattern.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages