Back to catalog

dotnet-expert

.NET 8.0 development expertise for VoiceLite including WPF patterns, async/await best practices, IDisposable patterns, and NuGet package recommendations. Activates when working on C# code, WPF XAML, or .NET-specific issues.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 3
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

---
name: dotnet-expert
description: .NET 8.0 development expertise for VoiceLite including WPF patterns, async/await best practices, IDisposable patterns, and NuGet package recommendations. Activates when working on C# code, WPF XAML, or .NET-specific issues.
---

# .NET Expert

Specialized .NET 8.0 development guidance for VoiceLite's WPF desktop application.

## When This Skill Activates

- Working on `.cs` or `.xaml` files
- Mentions: "NuGet", "WPF", "XAML", "IDisposable", "async/await", "Dispatcher"
- Compiler errors: `CS####` error codes
- Performance issues: Memory leaks, GC pressure, threading
- .NET-specific questions: "How do I..." in C# context

## VoiceLite-Specific .NET Patterns

### 1. Disposal Pattern (CRITICAL for Audio Resources)

**NAudio WaveInEvent MUST be disposed** to prevent memory leaks:

```csharp
// AudioRecorder.cs - Proper disposal pattern
public class AudioRecorder : IDisposable
{
    private WaveInEvent? _waveIn;
    private bool _disposed = false;

    public void Dispose()
    {
        if (_disposed) return;

        // Stop recording first
        _waveIn?.StopRecording();

        // Dispose NAudio resources
        _waveIn?.Dispose();
        _waveIn = null;

        _disposed = true;
        GC.SuppressFinalize(this);
    }

    ~AudioRecorder()
    {
        Dispose();
    }
}

// CRITICAL: Test coverage required
// See: VoiceLite.Tests/Services/DisposalTests.cs
```

### 2. Dispatcher Pattern (UI Thread Safety)

**NEVER update UI from background thread** - always use Dispatcher:

```csharp
// MainWindow.xaml.cs - Correct UI updates
private async void OnTranscriptionCompleted(object sender, TranscriptionEventArgs e)
{
    // This event may fire from background thread!

    // ✅ CORRECT: Use Dispatcher for UI updates
    Application.Current.Dispatcher.Invoke(() =>
    {
        StatusLabel.Content = "Transcription complete";
        TranscriptionText.Text = e.Text;
    });

    // ❌ WRONG: Direct UI access from background thread
    // StatusLabel.Content = "..."; // Will throw InvalidOperationException!
}

// Async version
await Application.Current.Dispatcher.InvokeAsync(() =>
{
    // UI updates here
});
```

### 3. Async Event Handlers

**Event handlers can be `async void`** (only place this is acceptable):

```csharp
// MainWindow.xaml.cs
private async void OnAudioFileReady(object sender, AudioFileReadyEventArgs e)
{
    // async void is OK for event handlers
    try
    {
        await _whisperService.TranscribeAsync(e.FilePath);
    }
    catch (Exception ex)
    {
        ErrorLogger.Log(
quot;Transcription failed: {ex.Message}"); } } // Why async void here? // - Event handler signature requires void return // - UI remains responsive during await // - Exceptions caught locally (critical!) ``` ### 4. Process Management **Whisper.exe subprocess management**: ```csharp // PersistentWhisperService.cs - Proper process handling public async Task<string> TranscribeAsync(string audioPath) { Process? process = null; try { process = new Process { StartInfo = new ProcessStartInfo { FileName = "whisper.exe", Arguments =
quot;-m {modelPath} -f {audioPath}", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); // Calculate timeout (3x audio length) var timeout = (int)(audioLength * 3000); if (!process.WaitForExit(timeout)) { // Timeout - kill process process.Kill(); throw new TimeoutException(
quot;Whisper timeout after {timeout}ms"); } return await process.StandardOutput.ReadToEndAsync(); } finally { // ALWAYS dispose process process?.Dispose(); } } ``` ## NuGet Package Recommendations VoiceLite's tech stack and recommended versions: | Package | Version | Use Case | Notes | |---------|---------|----------|-------| | **NAudio** | 2.2.1 | Audio recording | Use WaveInEvent for recording | | **H.InputSimulator** | 1.2.1 | Text injection | Keyboard & clipboard simulation | | **Hardcodet.NotifyIcon.Wpf** | 2.0.1 | System tray icon | WPF-compatible tray support | | **System.Text.Json** | 9.0.9 | Settings serialization | Prefer over Newtonsoft.Json | | **System.Management** | 8.0.0 | System info | Used for version checking | | **Moq** | 4.20+ | Unit testing | Mock dependencies in tests | | **FluentAssertions** | 6.12+ | Test assertions | Readable test assertions | | **xUnit** | 2.6+ | Test framework | VoiceLite test framework | **Avoid**: - ❌ Newtonsoft.Json (use System.Text.Json for .NET 8.0) - ❌ Old NAudio versions (ensure 2.2.1+ for stability) ## Common .NET 8.0 Issues & Solutions ### Issue: System.Text.Json Doesn't Serialize Property **Symptom**: Property missing from JSON output **Fix**: Ensure property is public with getter/setter: ```csharp // ❌ WRONG: Private or missing setter private string Model { get; set; } public string Model { get; } // ✅ CORRECT: Public with both getter and setter public string Model { get; set; } // Alternative: Use JsonPropertyName attribute [JsonPropertyName("model")] public string Model { get; init; } // init-only is OK ``` ### Issue: WPF Dispatcher Throws on Test Thread **Symptom**: `System.InvalidOperationException: The calling thread must be STA` **Fix**: Mock Dispatcher in tests or use SynchronizationContext: ```csharp // In tests: Mock Application.Current var mockApp = new Mock<Application>(); // ... setup mock dispatcher // Or: Use SynchronizationContext SynchronizationContext.SetSynchronizationContext( new SynchronizationContext() ); ``` ### Issue: Memory Leak with Event Handlers **Symptom**: Objects not garbage collected, memory grows **Fix**: Always unsubscribe in Dispose(): ```csharp public class AudioRecorder : IDisposable { public AudioRecorder() { _waveIn.DataAvailable += OnDataAvailable; } public void Dispose() { // CRITICAL: Unsubscribe events _waveIn.DataAvailable -= OnDataAvailable; _waveIn?.Dispose(); } } ``` ### Issue: Async Deadlock in WPF **Symptom**: UI freezes on `await` call **Fix**: Use `ConfigureAwait(false)` for non-UI code: ```csharp // In service layer (non-UI) public async Task<string> TranscribeAsync(string path) { var result = await ProcessAudioAsync(path) .ConfigureAwait(false); // Don't capture UI context return result; } // In UI layer (keep UI context) private async void OnButtonClick(object sender, RoutedEventArgs e) { var result = await _service.TranscribeAsync(path); // No ConfigureAwait here - we NEED UI context for updates ResultLabel.Text = result; } ``` ## WPF-Specific Patterns ### XAML Data Binding ```xml <!-- ModernStyles.xaml pattern --> <Style x:Key="ModernButton" TargetType="Button"> <Setter Property="Background" Value="#2D2D30"/> <Setter Property="Foreground" Value="White"/> <Setter Property="BorderBrush" Value="#3E3E42"/> <Setter Property="Padding" Value="10,5"/> </Style> <!-- Usage in MainWindow.xaml --> <Button Style="{StaticResource ModernButton}" Click="OnRecordClick" Content="Record"/> ``` ### Value Converters ```csharp // Converters/RelativeTimeConverter.cs public class RelativeTimeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is DateTime dt) { var diff = DateTime.Now - dt; if (diff.TotalMinutes < 1) return "Just now"; if (diff.TotalHours < 1) return
quot;{(int)diff.TotalMinutes} mins ago"; if (diff.TotalDays < 1) return
quot;{(int)diff.TotalHours} hours ago"; return
quot;{(int)diff.TotalDays} days ago"; } return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } ``` ## .NET 8.0 Performance Tips ### 1. Use Span<T> for Buffer Operations ```csharp // ✅ GOOD: Use Span<byte> for audio buffers public void ProcessAudioBuffer(Span<byte> buffer) { // No allocations, stack-based for (int i = 0; i < buffer.Length; i++) { buffer[i] = 0; // Process in-place } } // ❌ BAD: Array allocation every time public void ProcessAudioBuffer(byte[] buffer) { var temp = new byte[buffer.Length]; // Heap allocation! } ``` ### 2. String Interpolation for Performance ```csharp // ✅ GOOD: String interpolation (compiled to string.Format) var msg =
quot;Transcribed {count} files in {elapsed}ms"; // ❌ BAD: String concatenation (multiple allocations) var msg = "Transcribed " + count + " files in " + elapsed + "ms"; ``` ### 3. Nullable Reference Types ```csharp // Enable in .csproj <Nullable>enable</Nullable> // Use in code public class AudioRecorder { private WaveInEvent? _waveIn; // Nullable private string _model = ""; // Non-nullable, initialized public string Model => _model; // Non-nullable guarantee } ``` ## Testing Patterns ### Unit Test Example ```csharp // VoiceLite.Tests/Services/WhisperServiceTests.cs public class WhisperServiceTests { [Fact] public async Task TranscribeAsync_ValidAudio_ReturnsText() { // Arrange var service = new PersistentWhisperService(); var testAudio = "test_5s.wav"; // Act var result = await service.TranscribeAsync(testAudio); // Assert result.Should().NotBeNullOrEmpty(); result.Length.Should().BeGreaterThan(0); } [Fact] public void Dispose_Called_DisposesResources() { // Arrange var recorder = new AudioRecorder(); // Act recorder.Dispose(); // Assert // Verify no exceptions thrown // Check memory released (advanced) } } ``` ## Compiler Error Quick Reference | Error Code | Meaning | Common Fix | |------------|---------|------------| | CS0103 | Name does not exist | Add `using` statement or fix typo | | CS0161 | Not all code paths return | Add `return` statement | | CS1061 | No definition for method | Check method name, add reference | | CS8602 | Null reference warning | Add null check or `!` operator | | CS8618 | Non-nullable field not initialized | Initialize in constructor or use `= null!` | ## Resources - **Official Docs**: https://learn.microsoft.com/en-us/dotnet/ - **WPF Docs**: https://learn.microsoft.com/en-us/dotnet/desktop/wpf/ - **NAudio Docs**: https://github.com/naudio/NAudio - **FluentAssertions**: https://fluentassertions.com/