A comprehensive C# utility library providing common helpers, extensions, and WPF components for .NET applications.
HiLibrary.CSharp is organized into two main packages:
- HiLibrary - Core utilities, extensions, and helpers for any .NET application
- HiLibrary.Wpf - WPF-specific components including animations, dialogs, popups, and converters
| Framework | Version |
|---|---|
| .NET Framework | 4.5.1, 4.7.2 |
| .NET Core | 3.1 |
| .NET | 6.0, 8.0, 9.0 |
| .NET Standard | 2.0 |
# Install core library
dotnet add package Hi.Core
# Install WPF library (for WPF projects)
dotnet add package Hi.WpfPowerful command implementations for MVVM pattern with synchronous and asynchronous support.
// Simple command
public ICommand SaveCommand => new BindingCommand(() => Save());
// Command with CanExecute
public ICommand DeleteCommand => new BindingCommand(
() => Delete(),
() => SelectedItem != null
);
// Implicit conversion from Action
BindingCommand command = () => DoSomething();
// Global exception handling
BindingCommand.SetGlobalCommandExceptionCallback(ex =>
{
Logger.LogError(ex, "Command execution failed");
});// Async command
public IBindingCommandAsync LoadCommand => new BindingCommandAsync(
async () => await LoadDataAsync(),
() => !IsLoading
);
// Check if command is executing
if (LoadCommand.IsExecuting)
{
// Show loading indicator
}// Command with parameter
public ICommand SelectCommand => new BindingCommand<Item>(item => Select(item));
// Async command with parameter
public IBindingCommandAsync<int> FetchCommand => new BindingCommandAsync<int>(
async id => await FetchAsync(id)
);A flexible pub/sub event aggregator supporting synchronous and asynchronous events with channel-based messaging.
// Create event manager
IEventManager eventManager = new EventManager();
// Subscribe to events
IUnsubscrible subscription = eventManager.GetEvent<UserLoggedInEvent>()
.Subscribe(e => HandleUserLogin(e), EventThreadPolicy.Current);
// Publish events
eventManager.GetEvent<UserLoggedInEvent>().Publish(new UserLoggedInEvent(user));
// Channel-based messaging
eventManager.GetEvent<NotificationEvent>()
.Subscribe("alerts", notification => ShowAlert(notification));
eventManager.GetEvent<NotificationEvent>()
.Publish("alerts", new NotificationEvent("New message"));
// Async events
var asyncEvent = eventManager.GetAsyncEvent<DataChangedEvent>();
await asyncEvent.PublishAsync(new DataChangedEvent());
// Unsubscribe when done
subscription.Unsubscribe();Thread Policies:
EventThreadPolicy.Current- Execute on the subscriber's original threadEventThreadPolicy.PublishThread- Execute on the publisher's threadEventThreadPolicy.NewThread- Execute on a new thread pool thread
Type-safe context containers for storing and retrieving values.
// Application-wide context
IApplicationContext appContext = new ApplicationContext();
// Store values by type
appContext.SetValue(new UserSettings());
appContext.SetValue("theme", "dark");
// Retrieve values
var settings = appContext.GetValue<UserSettings>();
var theme = appContext.GetValue<string>("theme");
// Safe retrieval
if (appContext.TryGetValue<UserSettings>(out var userSettings))
{
// Use settings
}Execute actions after a specified delay with debouncing support.
// Simple deferred action
IDeferredToken token = Defer.Deferred(500) // 500ms delay
.Invoke(() => SearchAsync(query));
// Restart the timer (debouncing)
token.Restart();
// With TimeSpan
IDeferredToken token2 = Defer.Deferred(TimeSpan.FromSeconds(1))
.Invoke(async () => await SaveChangesAsync());
// With context information
Defer.Deferred(1000).Invoke(context =>
{
Console.WriteLine($"Executed on thread {context.ThreadId}");
Console.WriteLine($"Was abandoned: {context.IsAbandoned}");
});
// Execute on UI thread
Defer.Deferred(500).Invoke(
() => UpdateUI(),
invokeInCurrentThread: true
);
// Dispose to cancel
token.Dispose();A simple wrapper around SemaphoreSlim for resource management.
// Create awaiter with initial and max count
var awaiter = new Awaiter(initialCount: 3, maxCount: 5);
// Wait for resource
await awaiter.WaitAsync();
try
{
// Use resource
}
finally
{
awaiter.Release();
}
// Extension methods for locked execution
await awaiter.LockInvokeAsync(async () =>
{
await DoWorkAsync();
});
// With timeout
bool acquired = await awaiter.WaitAsync(
millisecondsTimeout: 5000,
cancellationToken: cts.Token
);// Null/empty checks
if (collection.IsNullOrEmpty()) { }
if (collection.IsNotNullOrEmpty()) { }
// Conditional filtering
var filtered = items.WhereIf(includeDeleted, x => x.IsDeleted);
// Find index
int index = items.IndexOf(x => x.Id == targetId);
// Pagination
var page = items.Paginate(pageIndex: 2, pageSize: 10);
// ForEach with action
items.ForEach(item => Process(item));
items.ForEach((item, index) => Process(item, index));
// Async ForEach
await items.ForEachAsync(async item => await ProcessAsync(item));
// Join to string
string csv = items.Join(x => x.Name, ", ");
// To read-only collections
IReadOnlyList<T> readOnlyList = items.ToReadOnlyList();
IReadOnlyDictionary<K, V> readOnlyDict = dict.ToReadOnlayDictionary();
// Chunking (for older frameworks)
var chunks = items.Chunk(100);// Await TaskCompletionSource directly
var tcs = new TaskCompletionSource<int>();
int result = await tcs;
// Await multiple TaskCompletionSources
var sources = new[] { tcs1, tcs2, tcs3 };
int[] results = await sources;
// Await TimeSpan as delay
await TimeSpan.FromSeconds(1);
// Await collection of tasks
var tasks = items.Select(x => ProcessAsync(x));
await tasks;// Various string utility methods
string formatted = text.ToTitleCase();
bool isEmpty = text.IsNullOrEmpty();// DateTime utility methods
var startOfDay = date.StartOfDay();
var endOfMonth = date.EndOfMonth();Flexible type conversion with custom converter registration.
// Convert using registered converters
int number = "123".ConvertTo<int>();
DateTime date = "2024-01-01".ConvertTo<DateTime>();
// Register custom converter
TypeConverterExtensions.ConvertRegister<string, CustomType>(
str => CustomType.Parse(str)
);
// Use custom converter
var custom = "value".ConvertTo<CustomType>();Utility methods for argument validation with detailed exception messages.
public void Process(string input, object data)
{
Thrower.IsNullOrEmpty(input, nameof(input));
Thrower.IsNullOrWhiteSpace(input, nameof(input));
Thrower.IsNull(data, nameof(data));
}Fluent API for creating WPF property animations.
// Create animation
var handler = element.BeginAnimation(UIElement.OpacityProperty)
.From(0.0)
.To(1.0)
.Duration(TimeSpan.FromMilliseconds(300))
.EasingFunction(new CubicEase())
.Delay(TimeSpan.FromMilliseconds(100))
.AutoReverse(true)
.RepeatBehavior(RepeatBehavior.Forever)
.Completed(() => Console.WriteLine("Animation completed"))
.Build();
// Start animation
handler.Begin();
// Supported property types:
// - Numeric: byte, short, int, long, float, double, decimal
// - Geometry: Point, Point3D, Vector, Vector3D, Rect, Size
// - Other: Color, Quaternion, Rotation3DShow modal and non-modal dialogs.
IDialogService dialogService = new DialogService();
// Show non-modal dialog
dialogService.Show(new MyDialogView(), new DialogParameter
{
Title = "Settings",
Width = 400,
Height = 300
});
// Show modal dialog
bool? result = dialogService.ShowDialog(new ConfirmView(), new DialogParameter
{
Title = "Confirm Action"
});Display popups for messages, confirmations, and custom content.
IPopupService popupService = new PopupService();
// Show message
await popupService.ShowAsync("Operation completed successfully", "Success");
// Show confirmation
ButtonResult result = await popupService.ConfirmAsync(
"Are you sure you want to delete this item?",
"Confirm Delete"
);
if (result == ButtonResult.OK)
{
// Delete item
}
// Show custom popup
var customResult = await popupService.PopupAsync<CustomResult>(
new CustomPopupView(),
new PopupParameter { /* options */ }
);
// Show in specific host
await popupService.ShowAsyncIn("DialogHost", "Message", "Title");Display toast-style notifications.
INotificationService notificationService = new NotificationService();
// Show notification
await notificationService.NotifyAsync(
"File saved successfully",
TimeSpan.FromSeconds(3)
);
// Show in specific host
await notificationService.NotifyAsyncIn(
"NotificationHost",
"New message received",
TimeSpan.FromSeconds(5)
);Pre-built value converters for common scenarios.
// Boolean converters
Converters.BooleanReverse // true -> false, false -> true
Converters.BooleanToVisibility // true -> Visible, false -> Collapsed
Converters.BooleanToVisibilityReverse // true -> Collapsed, false -> Visible
// Null check converters
Converters.IsNull // null -> true
Converters.IsNotNull // not null -> true
Converters.IsNullOrEmpty // null/empty -> true
Converters.IsNotNullOrEmpty // not null/empty -> true
Converters.IsNullOrWhiteSpace // null/whitespace -> true
Converters.IsNotNullOrWhiteSpace // not null/whitespace -> true
// Visibility converters
Converters.IsNullToVisibility
Converters.IsNotNullToVisibility
Converters.IsNullOrEmptyToVisibility
Converters.IsNotNullOrEmptyToVisibility
// Comparison converters (to Visibility)
Converters.EqualToVisibility
Converters.NotEqualToVisibility
Converters.GreaterThanToVisibility
Converters.GreaterThanOrEqualToVisibility
Converters.LessThanToVisibility
Converters.LessThanOrEqualToVisibility
// Enum converters
Converters.GetEnumDescription // Get [Description] attribute
Converters.GetEnumDisplayName // Get [Display] attribute
// Color converters
Converters.StringToColor // "#FF0000" -> Color
Converters.StringToBrush // "#FF0000" -> BrushUsage in XAML:
<Window xmlns:hi="clr-namespace:HiLibrary;assembly=HiLibrary.Wpf">
<Button Visibility="{Binding HasItems,
Converter={x:Static hi:Converters.BooleanToVisibility}}" />
<TextBlock Visibility="{Binding Name,
Converter={x:Static hi:Converters.IsNotNullOrEmptyToVisibility}}" />
</Window>Create background UI threads with their own Dispatcher.
// Create async
Dispatcher backgroundDispatcher = await UIDispatcher.RunNewAsync("RenderThread");
// Create sync
Dispatcher dispatcher = UIDispatcher.RunNew("BackgroundUI");
// Execute on background dispatcher
backgroundDispatcher.InvokeAsync(() =>
{
// This runs on the background UI thread
var visual = CreateVisual();
});A content control that animates content changes.
<hi:TransitioningControl Content="{Binding CurrentView}">
<!-- Content transitions automatically when changed -->
</hi:TransitioningControl>A ComboBox that automatically populates with enum values.
<hi:EnumComboBox EnumType="{x:Type local:MyEnum}"
SelectedValue="{Binding SelectedOption}" />Tools for performance monitoring and UI optimization.
// Color animation performer
var colorPerformer = new ColorPerformer();
// Transition performer
var transitionPerformer = new TransitionPerformer();public class MainViewModel : INotifyPropertyChanged
{
public IBindingCommand SaveCommand { get; }
public IBindingCommandAsync LoadCommand { get; }
public MainViewModel()
{
SaveCommand = new BindingCommand(Save, () => CanSave);
LoadCommand = new BindingCommandAsync(LoadAsync, () => !IsLoading);
}
private void Save() { /* ... */ }
private async Task LoadAsync() { /* ... */ }
}public class OrderService
{
private readonly IEventManager _eventManager;
public OrderService(IEventManager eventManager)
{
_eventManager = eventManager;
}
public async Task PlaceOrderAsync(Order order)
{
await ProcessOrderAsync(order);
// Notify other parts of the application
_eventManager.GetEvent<OrderPlacedEvent>()
.Publish(new OrderPlacedEvent(order));
}
}
public class NotificationHandler
{
public NotificationHandler(IEventManager eventManager)
{
eventManager.GetEvent<OrderPlacedEvent>()
.Subscribe(OnOrderPlaced, EventThreadPolicy.Current);
}
private void OnOrderPlaced(OrderPlacedEvent e)
{
ShowNotification($"Order {e.Order.Id} placed successfully");
}
}public class SearchViewModel
{
private IDeferredToken? _searchToken;
public string SearchText
{
get => _searchText;
set
{
_searchText = value;
DebouncedSearch();
}
}
private void DebouncedSearch()
{
_searchToken?.Restart();
_searchToken ??= Defer.Deferred(300)
.Invoke(async () => await SearchAsync(_searchText),
invokeInCurrentThread: true);
}
}Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.