August 25, 2026

Library And Test Projects Generator

Here is a Linqpad file. You specify the root directory name and it will generate a directory structure with a solution file in the root directory. Under the root directory are 2 project directories, one a c# library project and the other is an NUnit test project for testing the library. The root directory is created in the Temp directory.

async void Main()
{
    IFileSystem fileSystem = new FileSystem();

    string linqPadFile = "";
    string projectName = "YYY";
    var testDirPath = Path.Combine(Path.GetTempPath(), projectName);
    var rootDir = fileSystem.Directory.CreateDirectory(testDirPath);
    Debug.Assert(rootDir.Exists, $"Directory {rootDir.FullName} not found.");

    var projFileCreator = new CSharpProjectFileCreator(fileSystem);
    var response = projFileCreator.Create(new CreateProjectRequest(rootDir.CreateSubdirectory($"{projectName}"), $"{projectName}.csproj", 
        ["Microsoft.Extensions.Logging", "System.IO.Abstractions", "Microsoft.Extensions.DependencyInjection"], $"{projectName}.cs", internalsVisibleTo: $"{projectName}.NUnit"));
    var response2 = projFileCreator.Create(new CreateProjectRequest(rootDir.CreateSubdirectory($"{projectName}.NUnit"), $"{projectName}.NUnit.csproj", 
        ["NUnit", "NUnit3TestAdapter", "Microsoft.NET.Test.Sdk", "System.IO.Abstractions", "System.IO.Abstractions.TestingHelpers", "coverlet.collector"],
        $"{projectName}.NUnit.cs", NUnitSamples.NUnitSampleTest));
    projFileCreator.CreateSolutionFile(testDirPath, projectName);


    // Output
    //Console.WriteLine($"Project File Name: {projectFileName}");
    //Console.WriteLine("");
    //Console.WriteLine("--- Project Contents ---");
    //Console.WriteLine(projectFileContents);
}

public record CreateProjectRequest(IDirectoryInfo TargetDir, string ProjectFileName, string[] NugetReferences, string ContentFileName = "", string Content = "", string internalsVisibleTo = "");
public record CreateProjectResponse(bool Successful, string msg = "");


public class CSharpProjectFileCreator
{
    private readonly IFileSystem _fileSystem;

    public CSharpProjectFileCreator(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    public CreateProjectResponse Create(CreateProjectRequest request)
    {
        bool fullySuccessful = false;
        if (!request.TargetDir.Exists)
        {
            return new CreateProjectResponse(false, $"Directory {request.TargetDir.FullName} not found.");
        }

        var projectFilePath = Path.Combine(request.TargetDir.FullName, request.ProjectFileName);
        var projectFi = _fileSystem.FileInfo.New(projectFilePath);
        var successful = GenerateProjectFile(projectFi.FullName, request.NugetReferences, request.internalsVisibleTo);
        if (!successful)
        {
            return new CreateProjectResponse(false, $"Project file {projectFi.FullName} not created.");
        }

        var contentFilePath = Path.Combine(request.TargetDir.FullName, request.ContentFileName);
        var contentFi = _fileSystem.FileInfo.New(contentFilePath);
        successful = CreateFile(contentFi.FullName, request.Content);

        if (!successful)
        {
            return new CreateProjectResponse(false, $"Project file {projectFi.FullName} created, but not the content file {contentFi.FullName} .");
        }

        return new CreateProjectResponse(fullySuccessful, $"Project file {projectFi.FullName} and Content file {contentFi.FullName} were created");
    }


    private bool GenerateProjectFile(string projectFullName, string[] nugetReferences, string internalsVisibleTo = "")
    {
        string packageReferences = "";
        if (nugetReferences.Any())
        {
            packageReferences = string.Join(Environment.NewLine,
                nugetReferences.Select(x => $"    <PackageReference Include=\"{x}\" Version=\"*\" />"));
        }

        var projectContent = projectTemplate.Replace("{packageReferences}", packageReferences);
        string internalsVisibleToSection = "";
        if (internalsVisibleTo.Length > 0)
        {
            internalsVisibleToSection = internalsVisibleTemplate.Replace("{internalsVisibleTo}", internalsVisibleTo);
        }
        projectContent = projectContent.Replace("{internalsVisibleToSection}", internalsVisibleToSection);
        //_fileSystem.File.WriteAllText(projectFullName, projContent);
        return CreateFile(projectFullName, projectContent); //_fileSystem.File.Exists(projectFullName);
    }

    private bool CreateFile(string fileFullName, string content)
    {
        _fileSystem.File.WriteAllText(fileFullName, content);
        return _fileSystem.File.Exists(fileFullName);
    }

    public bool CreateSolutionFile(string rootDirectoryPath, string projectName)
    {
        var solnFilePath = Path.Combine(rootDirectoryPath, projectName + ".slnx");
        bool successful = CreateFile(solnFilePath, projectSolutionTemplate.Replace("{projectName}", projectName));

        return successful;
    }


    private static string projectTemplate =
        """
        <Project Sdk="Microsoft.NET.Sdk">
          <PropertyGroup>
            <OutputType>Exe</OutputType>
            <TargetFramework>net10.0</TargetFramework>
            <LangVersion>latest</LangVersion>
            <Nullable>enable</Nullable>
          </PropertyGroup>

          <!-- Project references go here-->
          <ItemGroup>
          {packageReferences}
          </ItemGroup>

          {internalsVisibleToSection}
        </Project>
        """;
        
    private static string  projectSolutionTemplate = 
        """
        <Solution>
          <Configurations>
            <Platform Name="Any CPU" />
          </Configurations>
          <Folder Name="/Solution Items/">
            <File Path=".editorconfig" />
          </Folder>
          <Project Path="{projectName}/{projectName}.csproj" />
          <Project Path="{projectName}.NUnit/{projectName}.NUnit.csproj" />
        </Solution>
        """;

    private static string internalsVisibleTemplate =
        """
        <ItemGroup>
            <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
                <_Parameter1>{internalsVisibleTo}</_Parameter1>
            </AssemblyAttribute>
        </ItemGroup>
        """;

}

public class NUnitSamples
{
   public static readonly string NUnitSampleTest = // See https://rbovill.blogspot.com/2006/01/using-nunit.html
    """
    // See https://rbovill.blogspot.com/2006/01/using-nunit.html
    using NUnit.Framework;

    [TestFixture]
    public class SomeTester
    {
      // Format of a Test method, Try to put all the setup for the test in the test.
      // If necessary add private Setup/Initialise/Teardown methods to assist this 
      // rather than using the one listed above
      [Test]
      public void SomeTest1()
      {
      }
     
      // You can add test parameters to a test and use it to test multiple cases 
      [Test]
      [TestCase(5.0d, 0.0d, 3.0d)]
      [TestCase(5.0d, 1.0d, 3.0d)]
      [TestCase(5.0d, 0.0d, 5.0d)]
      public void SomeTest2(double fullLengthSecs, double startTimeSecs, int expectedValue)
      {
      }
    }   
    """;
}

C# implicit and explicit operators

What they are (quick)

  • Implicit operator: defines an automatic conversion from one type to another that the compiler applies without a cast.
  • Explicit operator: defines a conversion that requires a cast and is used when the conversion may lose information or fail.

Syntax (C#)

Implicit:

public static implicit operator TargetType(SourceType s) {
    // create and return TargetType from s
}

Explicit:

public static explicit operator TargetType(SourceType s) {
    // create and return TargetType from s
}

Simple examples

Implicit (safe, no data loss):

struct Meters {
    public double Value;
    public static implicit operator double(Meters m) => m.Value;
    public static implicit operator Meters(double d) => new Meters { Value = d };
}

Meters m = new Meters { Value = 1.5 };
double d = m;        // implicit
Meters m2 = 2.0;     // implicit

Explicit (possible loss/failure):

struct ByteSized {
    public byte Value;
    public static explicit operator ByteSized(int i) {
        if (i < 0 || i > 255) throw new OverflowException();
        return new ByteSized { Value = (byte)i };
    }
}

int x = 300;
ByteSized b = (ByteSized)x; // requires cast; may throw an exception

When to use which

  • Use implicit when the conversion is:
    • Lossless (no precision or semantic loss).
    • Safe and not surprising to callers.
    • Cheap and well-defined in both directions (often).
  • Use explicit when:
    • The conversion can lose information (precision, range).
    • It can throw or fail.
    • It’s potentially surprising or semantically significant.
    • Converting from a wide to a narrower type (e.g., double → int).

Best practices

  • Prefer explicit for any conversion that can lose data or change meaning.
  • Keep conversions simple and obvious; avoid heavy logic or external dependencies.
  • Provide symmetric conversions when sensible (if you define implicit A→B, consider B→A if safe).
  • Document conversions clearly on the type.
  • Consider factory methods (FromX, ToX) when conversion is complex or may fail with domain-specific errors.
  • Avoid implicit conversions between unrelated types to prevent hidden bugs and API surprises.

Common pitfalls

  • Implicit conversions can make code less readable and introduce subtle bugs when multiple conversion paths exist.
  • Overusing implicit can cause ambiguous overload resolution.
  • Throwing exceptions inside conversion operators is allowed but make the operator explicit if exceptions are possible.

AI Generated

C# to Javascript Conversion Using AI

I asked AI (Code Haiku 4.5) to convert a C# class to Javascript. AI seems to excel at small conversions task like this. The class takes the lines of a SRT subtitle file and converts them to a VTT file. VTT has an optional additional title and note headers so I allows these to be set in the constructor. Note that it just deals with the file as a IEnumerable. Opening the file and streaming it to an instance of this class would be the resposibility of another class, which makes this very easy to test. It is also I think, a nice example of the power of an Iterator function/method and what can be achieved with them. Note that this class does not fix the Cue timings if they are out of order, etc.. Also the class could be tidied up quite a bit: _lineNumber is not required, the ParseState could be set in the Iterator. Well here is the C# version

public class SrtToVttConvertor
{
    private int _lineNumber = 0;
    private ParseState _parseState = ParseState.Starting;
    private readonly string _title = "";
    private readonly string _note = "";

    public SrtToVttConvertor(string title = "", string note = "")
    { 
        _title = title ?? "";
        _note = note ?? "";
    }

    private enum ParseState
    {
        None = 0,
        Starting = 1,
        SearchCue = 2,
        Subtitles = 3
    }


    public IEnumerable<string> Convert(IEnumerable<string> lines)
    {
        foreach (var line in lines)
        {
            _lineNumber++;
            string temp = "";

            switch (_parseState)
            {
                case ParseState.Starting: // Output the start of the WEBVTT file
                    {
                        temp = "WEBVTT " + _title;
                        yield return temp.TrimEnd();
                        yield return "";
                        if (!string.IsNullOrWhiteSpace(_note))
                        {
                            yield return "NOTE " + _note;
                            yield return "";
                        }
                        _parseState = ParseState.SearchCue;
                        break;
                    }

                case ParseState.SearchCue: // Seach for a cue (timings) line (ignore the cue number line)
                    if (line.Contains("-->"))
                    {
                        yield return line.Replace(',', '.').Trim(); // "00:04:08,624 --> 00:04:09,833" Becomes "00:04:08.624 --> 00:04:09.833"
                        _parseState = ParseState.Subtitles;
                    }
                    break;

                case ParseState.Subtitles: // After cue line add all non-blank lines until another blank line is reached
                    if (string.IsNullOrWhiteSpace(line))
                    {
                        yield return "";
                        _parseState = ParseState.SearchCue;
                    }
                    else
                    {
                        yield return line.Trim();
                    }
                    break;
            }
        }
    }
}

I was not even sure whether Javascript had Enumerators, it does they are called generators. Here is what it came up with:

class SrtToVttConvertor {
    #lineNumber;
    #parseState;
    #title;
    #note;

    static ParseState = {
        None: 0,
        Starting: 1,
        SearchCue: 2,
        Subtitles: 3
    };

    constructor(title = "", note = "") {
        this.#lineNumber = 0;
        this.#parseState = SrtToVttConvertor.ParseState.Starting;
        this.#title = title ?? "";
        this.#note = note ?? "";
    }

    *convert(lines) {
        for (const line of lines) {
            this.#lineNumber++;
            let temp = "";

            switch (this.#parseState) {
                case SrtToVttConvertor.ParseState.Starting:
                    temp = "WEBVTT " + this.#title;
                    yield temp.trimEnd();
                    yield "";
                    if (this.#note.trim().length > 0) {
                        yield "NOTE " + this.#note;
                        yield "";
                    }
                    this.#parseState = SrtToVttConvertor.ParseState.SearchCue;
                    break;

                case SrtToVttConvertor.ParseState.SearchCue:
                    if (line.includes("-->")) {
                        yield line.replaceAll(',', '.').trim();
                        this.#parseState = SrtToVttConvertor.ParseState.Subtitles;
                    }
                    break;

                case SrtToVttConvertor.ParseState.Subtitles:
                    if (line.trim().length === 0) {
                        yield "";
                        this.#parseState = SrtToVttConvertor.ParseState.SearchCue;
                    } else {
                        yield line.trim();
                    }
                    break;
            }
        }
    }
}

// Usage:
const converter = new SrtToVttConvertor("My Video", "Subtitle note");
const vttLines = [...converter.convert(srtLines)];

August 24, 2026

Host Application Lifetime Events

Sometimes it is useful to know when the host web application has started listening for calls to web site pages and also when it is stopping listening and completely stopped listening. You can register for these events in .NET applications. In my case my web application needed to start a fileWatcher at the start and then stop it when the application had stopped.

/// <summary>
/// This class tells you when the Web Application 
/// Starts (just prior to listening for web requests) 
/// and Stops and has Stopped, so you can start things up 
/// and shut them down at the end if that kind of pattern/behaviour is required in your application.
/// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.0#ihostapplicationlifetime
/// </summary>
internal class WebApplicationLifetimeEvents : IHostedService
{
    private readonly ILogger _logger;
    private readonly IHostApplicationLifetime _appLifetime;
    private readonly IWebHostEnvironment _webhostEnvironment;


    public WebApplicationLifetimeEvents(
        ILogger<WebApplicationLifetimeEvents> logger,
        IHostApplicationLifetime appLifetime,
        IWebHostEnvironment webhostEnvironment
        )
    {
        _logger = logger;
        _appLifetime = appLifetime;
        _webhostEnvironment = webhostEnvironment;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _appLifetime.ApplicationStarted.Register(OnStarted);
        _appLifetime.ApplicationStopping.Register(OnStopping);
        _appLifetime.ApplicationStopped.Register(OnStopped);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }

    private void OnStarted()
    {
        _logger.LogInformation("OnStarted has been called.");
        // Perform post-startup activities here:
        try
        {

        }
        catch (Exception ex)
        {
            _logger.LogError($"Exception in OnStarted {ex}");
            throw;
        }
    }

    private void OnStopping()
    {
        _logger.LogInformation("OnStopping has been called.");

        // Perform on-stopping activities here
    }

    private void OnStopped()
    {
        _logger.LogInformation("OnStopped has been called.");

        // Perform post-stopped activities here
    }
}

Usage, simply inject it to your dependency injection services


public static class DependencyInjection
{
    public static IServiceCollection ConfigureServices(this IServiceCollection services)
    {
        // Add application services.
        services.AddSingleton<IRsgConfiguration, RsgConfiguration>();
        services.AddSingleton<IHostedService, WebApplicationLifetimeEvents>();
        ...

IWebHostEnvironment MapPath Extensions

Extends the IWebHostEnvironment so that it is easy to find files in the wwwroot directory or in the in the content directory. Note that you can inject IWebHostEnvironment into a controller

public static class IWebHostEnvironmentExtender
{
    /// <summary>
    /// Maps a virtual path that begins with '~' to its corresponding physical path within the web root (wwwroot) directory.
    /// </summary>
    /// <remarks>Use this method to resolve virtual paths in web applications when accessing files or
    /// directories under the web root. If the input path does not start with '~', it is returned unchanged.</remarks>
    /// <param name="whe">The <see cref="IWebHostEnvironment"/> instance that provides information about the web hosting environment,
    /// including the web root path.</param>
    /// <param name="path">The virtual path to map. If the path starts with '~', it will be replaced with the web root path.</param>
    /// <returns>The physical path corresponding to the provided virtual path if it starts with '~'; otherwise, returns the
    /// original path.</returns>
    public static string MapWwwRootPath(this IWebHostEnvironment whe, string path)
    {
        return MapToRootPath(whe.WebRootPath.AsSpan(), path.AsSpan());

    }

    /// <summary>
    /// Maps a virtual path that begins with '~' to the physical content root path of the specified web host
    /// environment.
    /// </summary>
    /// <remarks>Use this method to resolve application-relative paths in scenarios where the content root
    /// path may vary, such as in different hosting environments.</remarks>
    /// <param name="whe">The web host environment that provides the content root path used for mapping.</param>
    /// <param name="path">The virtual path to map. If the path starts with '~', it is resolved relative to the content root path.</param>
    /// <returns>The physical path corresponding to the provided virtual path, or the original path if it does not start with
    /// '~'.</returns>
    public static string MapContentRootPath(this IWebHostEnvironment whe, string path)
    {
        return MapToRootPath(whe.ContentRootPath.AsSpan(), path.AsSpan());
    }

    internal static string MapToRootPath(
        ReadOnlySpan<char> rootPath,
        ReadOnlySpan<char> path)
    {
        var pathSafe = path.Trim();
        if (pathSafe.IsEmpty)
            return path.ToString();

        var ix = pathSafe.IndexOf('~');
        var restOfPathSafe = pathSafe[1..];
        var res = (ix == 0) ? string.Concat(rootPath, restOfPathSafe) : path.ToString();

        return res;
    }

}

Example Usage


// I had an "app_data" directory under my project for data files application data like the old .NET MVC
// This code would allow me to access it:
dirPath = webHostEnv.MapContentRootPath(Path.Combine("~", "app_data", "clipboard"));
// Sometimes I may want to access static resources in the "wwwroot" directory
_mainBookmarksJsonFile = environment.MapWwwRootPath(configuration.BookmarksJsonFile);

Simple FakeLogger

Use this fake logger when required within unit tests, where you are using Microsoft Logging and directly injecting the logger

public class FakeLogger<T> : ILogger<T>
{
    private sealed class NoOpDisposable : IDisposable
    {
        public void Dispose()
        {
        }
    }

    public IDisposable BeginScope<TState>(TState state) where TState : notnull
    {
        return new NoOpDisposable();
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception?, string> formatter)
    {
        string message = "";
        if (formatter != null)
        {
            message += formatter(state, exception);
        }
        var logLeveChar = logLevel.ToString()[0];
        Console.WriteLine($"{logLeveChar} - {typeof(T)} - {message}");
    }
}

Fake Logger Factory and Fake Loggers

When using Microsoft Logging this code allows you to create a FakeLoggerFactory that creates named FakeLoggers that all log to one Combined logger. This is useful for unit testing.


// You can define other methods, fields, classes and namespaces here
// What does this do? https://www.tutorialsteacher.com/core/fundamentals-of-logging-in-dotnet-core
/// <summary>
/// FakeLoggerFactory used in Unit testing. It creates loggers that log to a <see cref="CombinedLogging"/> object
/// </summary>
/// <inheritdoc />
public sealed class FakeLoggerFactory : ILoggerFactory
{
    readonly CombinedLogging _combinedLogging = new();

    /// <summary>
    /// Creates a new <see cref="FakeLoggerFactory"/> instance.
    /// </summary>
    public FakeLoggerFactory() { }

    /// <inheritdoc />
    /// <remarks>
    /// This returns a <see cref="FakeLogger2"/> instance of the given category.
    /// </remarks>
    public ILogger CreateLogger(string categoryName)
    {
        string tmp = $"CreateLogger {categoryName}";
        Console.WriteLine(tmp);
        return new FakeLogger2(categoryName, _combinedLogging);
    }

    /// <inheritdoc />
    public void AddProvider(ILoggerProvider provider)
    {
    }

    /// <inheritdoc />
    public void Dispose()
    {
    }

    /// <summary>
    /// Enumerate the combined log entries
    /// </summary>
    /// <returns></returns>
    public IEnumerable<LogEntry> Logs()
    {
        return _combinedLogging.Logs();
    }
}

[ExcludeFromCodeCoverage]
public class FakeLogger2 : ILogger
{
    private CombinedLogging _combinedLogging;
    private readonly string _category;

    /// <summary>
    /// Create a Fake Logger
    /// </summary>
    /// <param name="category">Specifying the category of the logging</param>
    /// <param name="combinedLogging">Combines the logging from all FakeLogger2s</param>
    public FakeLogger2(string category, CombinedLogging combinedLogging)
    {
        _category = category;
        _combinedLogging = combinedLogging;
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull
    {
        return new NoOpDisposable();
    }

    private sealed class NoOpDisposable : IDisposable
    {
        public void Dispose()
        {
        }
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return true;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        _combinedLogging.Log(_category, logLevel, eventId, state, exception, formatter);
    }
}

/// <summary>
/// To record a LogEntry
/// </summary>
/// <param name="Level">The LogLevel of the entry</param>
/// <param name="Category">The category of the entry, so an ILogger<T> will
/// log to a category of T where T is the type as a string</param>
/// <param name="Text">Logged text</param>
[ExcludeFromCodeCoverage]
public sealed record LogEntry(LogLevel Level, string Category, string Text)
{
    /// <summary>
    /// For Logger logging type T which has as a fully qualified type name of X.Y.Z, 
    /// ie. ILogger<T> we usually are only interested in the last part (class name), "Z"
    /// </summary>
    /// <returns>Class name part of the Type</returns>
    public string MinimalCategory()
    {
        Debug.Assert(Category is not null, $"Field {nameof(Category)} is null");

        var span = Category.AsSpan();
        int ix = span.LastIndexOf('.'); // Look for last period/full stop character
        if (ix > 0)
        {
            span = span[ix..];
        }
        return span.ToString();
    }

    public override string ToString()
    {
        return $"{DateTime.UtcNow.ToRoundTripFormatString()} - {Level.ToString()[0..3].ToUpperInvariant()} - {MinimalCategory()} - {Text}";
    }
}

/// <summary>
/// Used to combine the logging of all FakeLogger2 loggers
/// </summary>
[ExcludeFromCodeCoverage]
public class CombinedLogging
{
    /// <summary>
    /// Store the logged lines by logging level here in logged order
    /// </summary>
    private readonly List<LogEntry> _allLogs = new();

    public CombinedLogging()
    {
    }

    public void Log<TState>(string category, LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        string message = "";
        if (formatter != null)
        {
            message += formatter(state, exception);
        }
        var logLeveChar = logLevel.ToString()[0];
        Console.WriteLine($"{logLeveChar} - {category} - {message}");
        _allLogs.Add(new LogEntry(logLevel, category, $"{message}"));
    }

    /// <summary>
    /// Retrieve All Logging
    /// </summary>
    /// <returns>All logging</returns>
    public IEnumerable<LogEntry> Logs()
    {
        foreach (var line in _allLogs)
        {
            yield return line;
        }
    }
}

August 23, 2026

Corrupted State Exceptions

Critical exceptions are exceptions that should not be caught. In C#, exceptions that indicate a catastrophic failure of the runtime or the process are generally known as Corrupted State Exceptions (CSEs). You should avoid catching these because the state of the application is no longer predictable, and attempting to execute code—especially code that allocates memory for logging—can lead to secondary crashes or hang the process.

public static class ExceptionExtender
{
    /*
    The primary exceptions you should avoid catching in a general exception handler are:
    Exception	Reason
    OutOfMemoryException	Logging/documenting the exception may itself fail due to lack of available memory.
    StackOverflowException	The stack is already exhausted; attempting to execute logging code will fail.
    ExecutionEngineException	Represents a critical CLR failure; recovery is not guaranteed. OBSOLETE now.
    FatalExecutionEngineError	Represents a critical CLR failure; recovery is not guaranteed. OBSOLETE now.
    AccessViolationException	Indicates unsafe memory access; logging may be unsafe or unreliable.
    ThreadAbortException	Thrown by the runtime to abort a thread; catching and rethrowing can interfere with CLR cleanup.
    AppDomainUnloadedException	The application domain is being unloaded; recovery is not possible.
    BadImageFormatException	Indicates a corrupted or invalid assembly; recovery is not feasible.
    
    In modern .NET (.NET Core, .NET 5+), most of these are treated as fatal and will not be caught by a standard 
    catch (Exception ex) block unless you specifically decorate your method with the 
    HandleProcessCorruptedStateExceptions attribute. For a general-purpose handler, it is safest to let these 
    propagate and allow the operating system to terminate the process.
    */


    public static bool IsCriticalException(this Exception ex)
    {
        return ex is OutOfMemoryException or
               StackOverflowException or
               // ExecutionEngineException or // OBSOLETE: ExecutionEngineException is obsolete and should not be used in 
               // new code. It was used to indicate a severe error in the execution engine of the .NET runtime, but it has
               // been deprecated and is no longer relevant in modern .NET applications.
               AccessViolationException or
               ThreadAbortException or
               AppDomainUnloadedException or
               BadImageFormatException;
    }
}

Here is an example for using it:


public T? LoadFromJsonFile(string jsonFilePath) where T : class, new()
{
    Debug.Assert(jsonFilePath != null, $"Parameter {nameof(jsonFilePath)} is null");
    T? reconstituted = null;
    try
    {
        using (var stream = _fileSystem.File.OpenRead(jsonFilePath))
        {
            reconstituted = JsonSerializer.Deserialize(stream, _serializerOptions);
        }
    }
    catch (Exception ex) when (!ex.IsCriticalException())
    {
        _logger.Log(LogLevel.Error, CallerContext.Create(), "Exception deserializing JSON from file '{JsonFilePath}' of: {Exception}",
            jsonFilePath, ex);
    }
    return reconstituted;
}

Partially AI Generated

Parsing Extensions

An idea from Nick Chapsis as shown here https://www.youtube.com/watch?v=lqbYURwM0bw, althought I added the TryParse with default value. These extensions that allow you to call the Parse function on a string or span. This will work with all the Basic types because they will have the static ISpanParsable interface defined for them.

public static class ParsableExtensions
{
	// SPAN Parsing
	
    /// <summary>
    /// Parse a span of characters for a given type <typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
    /// <param name="input">span of characters to parse</param>
    /// <param name="fp">format provider</param>
    /// <returns>The parsed value, will throw an exception if the parsing faile</returns>
    public static T Parse<T>(this ReadOnlySpan<char> input, IFormatProvider? fp = null)
      where T : ISpanParsable<T>
    {
        return T.Parse(input, fp);
    }

    /// <summary>
    /// Parse a span of characters for a given type <typeparamref name="T"/>, the parsed value is returned as an out parameter
    /// </summary>
    /// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
    /// <param name="input">span of characters to parse</param>
    /// <param name="fp">format provider</param>
    /// <returns>whether the parse succeeded or not</returns>
    public static bool TryParse<T>(this ReadOnlySpan<char> input, IFormatProvider fp, out T? value)
      where T : ISpanParsable<T>
    {
        return T.TryParse(input, fp, out value);
    }

    /// <summary>
    /// Parse a span of characters for a given type <typeparamref name="T"/>
    /// </summary>
    /// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
    /// <param name="input">span of characters to parse</param>
    /// <param name="defaultValue">The value to return if the parsing failed</param>
    /// <returns>The parsed value if the span was successfully parsed, the default value otherwise</returns>
    public static T Parse<T>(this ReadOnlySpan<char> input, T defaultValue)
      where T : ISpanParsable<T>
    {
        T result = T.TryParse(input, null, out T? value) ? value : defaultValue;
        return result;
    }

	/////////////////////////////////////////////////////////////////////////////////
	// STRING parsing

	/// <summary>
	/// Parse a string for a given type <typeparamref name="T"/>
	/// </summary>
	/// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
	/// <param name="input">string to parse</param>
	/// <param name="fp">format provider</param>
	/// <returns>The parsed value, will throw an exception if the parsing faile</returns>
	public static T Parse<T>(this string input, IFormatProvider? fp = null)
	  where T : ISpanParsable<T>
	{
		return T.Parse(input, fp);
	}

	/// <summary>
	/// Parse a string for a given type <typeparamref name="T"/>, the parsed value is returned as an out parameter
	/// </summary>
	/// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
	/// <param name="input">string to parse</param>
	/// <param name="fp">format provider</param>
	/// <returns>whether the parse succeeded or not</returns>
	public static bool TryParse<T>(this string input, IFormatProvider fp, out T? value)
	  where T : ISpanParsable<T>
	{
		return T.TryParse(input, fp, out value);
	}

	/// <summary>
	/// Parse a string for a given type <typeparamref name="T"/>
	/// </summary>
	/// <typeparam name="T">Type that supports an ISpanParsable<typeparamref name="T"/> interface</typeparam>
	/// <param name="input">string to parse</param>
	/// <param name="defaultValue">The value to return if the parsing failed</param>
	/// <returns>The parsed value if the string was successfully parsed, the default value otherwise</returns>
	public static T Parse<T>(this string input, T defaultValue)
	  where T : ISpanParsable<T>
	{
		T result = T.TryParse(input, null, out T? value) ? value : defaultValue;
		return result;
	}
}

Here are some examples on how to call them. I have used the Shouldy package to perform the assertions.

int resSpan = "  54 ".AsSpan().Parse(); // Span Parse
resSpan.ShouldEqual(54);

int resStr = " 27  ".Parse();   // String parse
resStr.ShouldEqual(27);
	
var action = () => { int res2Str = "   ".Parse(); }; // String parse that throws a format exception
action.ShouldThrow(); 

int res2Span = "   ".AsSpan().Parse(-1); // Span parse with default value
res2Span.ShouldEqual(-1);

August 21, 2026

Carriage Return and Line Feed

I always forget these so here is a note so that I can look them up easily. You can get the default newline charaters for the current system using the "Environment.NewLine".
Carriage Return (CR, '\r', ASCII 13): A Carriage return, takes the paper carriage back to the beginning of the line. In computing this control character repositions the cursor to the beginning of the line without going down to the next line.
Line Feed (LF, '\n', ASCII 10): In a typewriter, the line feed will help advance the paper by one line. In computing, this tip moves the cursor down to the next line without going back to the start of the line again.
On Windows it is Carriage return, Line feed or "\r\n"
I believe Linux and Unix and Mac OS (?) use "\n"

June 9, 2026

Windows Extended Length Path

The \\?\ (pronounced “extended-length path” or “verbatim path”) syntax is a Windows path prefix that tells the system to treat the path literally and bypass normal Windows path processing (path normalization, MAX_PATH length limit, and some path parsing rules). Key points:

Purpose

  • Allows paths longer than MAX_PATH (260 characters).
  • Prevents the system from interpreting backslashes, relative segments (., ..), or expanding device names; the path is taken verbatim.

Syntax

  • For local paths: start with \\?\ followed immediately by a drive letter and a colon, e.g.: \\?\C:\very\long\path...
  • For UNC/network paths: use the \\?\UNC\ form instead of \\server\share, e.g.: \\?\UNC\server\share\path...
  • Do not mix forward slashes; use backslashes.

Limits and behavior

  • Removes the MAX_PATH ~260-character limit for most APIs that accept Win32 file paths when used with wide-char (UTF-16) APIs.
  • Some Win32 APIs, shells, and libraries do not accept \\?\ paths; some higher-level frameworks (older .NET, Windows Explorer) may not handle them.
  • Certain parse rules are disabled: the path is not normalized (so trailing dots/spaces are preserved) and long relative components won’t be collapsed.
  • You must use absolute paths; relative paths with \\?\ are not supported.
  • For device paths (NT namespace) a different prefix \\?\GLOBALROOT\ or \\.\ may be used for special device access.

Unicode

  • Typically used with wide-character (W) Win32 APIs (UTF-16). Passing UTF-8/ANSI APIs can be problematic.

Examples

  • Long local path: \\?\C:\very\long\directory... ( > 260 chars )
  • Long UNC path: \\?\UNC\myserver\myshare\folder\file.txt

Common pitfalls

  • Some Windows APIs (including many shell APIs) reject \\?\ paths.
  • Don’t use trailing backslash immediately after \\?, e.g., \\?\C:\ is fine but be careful with extra slashes.
  • When interop with libraries/frameworks, strip or add the prefix only at the last moment when calling Win32 functions.

C# example — using the \\?\ (verbatim/extended-length) path prefix

      using System;
using System.IO;
using System.Text;

class Program
{
    // Helper: convert a normal absolute path to an extended-length path for local drive or UNC
    static string ToExtendedLengthPath(string path)
    {
        if (string.IsNullOrWhiteSpace(path))
            throw new ArgumentException(nameof(path));

        // Normalize separators
        path = Path.GetFullPath(path);

        // If UNC path (\\server\share\...), convert to \\?\UNC\server\share\...
        if (path.StartsWith(@"\\"))
        {
            return @"\\?\UNC\" + path.Substring(2);
        }

        // Otherwise (drive letter) prefix with \\?\
        return @"\\?\" + path;
    }

    static void Main()
    {
        // Example long folder path (ensure this exists or create it)
        string longFolder = @"C:\example\very\long\path\that\..."; // replace with real long path
        string extended = ToExtendedLengthPath(longFolder);

        Console.WriteLine("Extended path: " + extended);

        // Create directory using extended path
        Directory.CreateDirectory(extended);
        Console.WriteLine("Directory created.");

        // Create a file inside that directory
        string file = Path.Combine(extended, "test.txt");
        using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None))
        using (var sw = new StreamWriter(fs, Encoding.UTF8))
        {
            sw.WriteLine("Hello from extended-length path!");
        }
        Console.WriteLine("File written.");

        // Read the file back
        using (var sr = new StreamReader(new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8))
        {
            Console.WriteLine("File contents:");
            Console.WriteLine(sr.ReadToEnd());
        }
    }
}

    

Notes:

  • Use Path.GetFullPath to ensure an absolute path before adding the prefix.
  • For UNC paths (start with \\), convert to \\?\UNC... as shown.
  • Many .NET APIs work with \\?\ paths on modern runtimes, but some higher-level APIs or components (older frameworks, Windows shell) may not.

AI Generated

June 8, 2026

Datasets in Html and Javascript

This is a sample to show how to use datasets to put data into the html and how to access it

<button data-sandwich="tuna" data-topping="tomato" data-snack="cookies">Place Order</button>
Here is the Javascript to access the dataset on the button:

let btn = document.querySelector('button');

let dataset = btn.dataset; // Access "data-" attributes here
console.log("All dataset attributes: ");
console.log(dataset); 

let sandwich = btn.dataset.sandwich; // Access individually as properties of "dataset"
console.log("sandwich=" + sandwich);

let {topping, snack} = btn.dataset; // Access using tuples
console.log(sandwich, topping, snack);

let snack2 = btn.dataset["snack"]; // Access the properties dynamically
console.log("snack2=" + snack);

btn.dataset.side = 'chips'; // Add more dataset attributes
console.log(dataset); 
 
console.log("All done"); 
The code can be accessed here: https://codepen.io/capitanacerimmer/pen/JobbWYa?editors=1111
Note that the console output is not activated by the button itself

March 30, 2026

C# Compiler "CallerArgumentExpression"

Here is an example usage of the "CallerArgumentExpression". It allows you to capture a copy of some compiled code that evaluates to a value expression


void Main()
{
    var x = 34;
    var y = 23;
    Assert.That(x > y, $"{x} > {y}");
    x = 20;
    Assert.That(x > y, $"{x} > {y}");
}

// You can define other methods, fields, classes and namespaces here
public static class Assert
{
    public static void That(
         bool condition,
         string message = "",
         [CallerArgumentExpression("condition")] string expression = "",
         [CallerMemberName] string memberName = "",
         [CallerFilePath] string sourceFilePath = "",
         [CallerLineNumber] int sourceLineNumber = 0)
    {
        if (!condition)
        {
            var fullMsg = $"{sourceFilePath}:{sourceLineNumber} {memberName} - Assertion \"{expression}\" evaluated to FALSE: {message}";
            Trace.WriteLine(fullMsg);
            Console.WriteLine(fullMsg);
        }
    }
}

In this case the expression "x > y" that is passed in as the value of "bool condition" parameter is inserted by the compiler into the "string expression" parameter. In this example, the first Assert passes and outputs nothing but the second one outputs the following:

C:\...\Temp\LINQPad8\_hxysxhku\ewejlm\LINQPadQuery:7 Main - Assertion "x > y" evaluated to FALSE: 20 > 23

February 26, 2026

Functional Programming & Monads in C#

🧠 Introduction

Functional programming (FP) is a paradigm focused on pure functions, immutability, and composable transformations. Modern C# supports many FP concepts, making it possible to write expressive, predictable, and safe code without leaving the .NET ecosystem. This guide walks through the core FP ideas, monads, immutability, partial functions, and practical C# examples — all in clean Markdown for your blog.

1. Core Concepts of Functional Programming (FP)

1.1 First-Class & Higher-Order Functions

In FP:

  • Functions are treated as values: you can pass them around, store them in variables, and return them from other functions.
  • Higher-order functions take other functions as parameters or return them.

Example:

Func<int, int, int> add = (x, y) => x + y;

// Higher-order function: takes a function as input
int ApplyOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

var result = ApplyOperation(5, 3, add); // result = 8

1.2 Immutability

  • FP emphasizes immutable data, once created, values don’t change.
  • In C#, you can use readonly fields, record types, or avoid mutating collections
  • Data does not change after creation. Instead, you create new values.

Example using records:

record Person(string Name, int Age);
...
var p1 = new Person("Isabel", 30);
// Instead of mutating, create a new instance
var p2 = p1 with { Age = 31 }; // p2 is a new object

1.3 Pure Functions

  • A pure function always returns the same output for the same input and has no side effects (like modifying global state or I/O).
  • This makes code predictable and testable.

Pure:

int Square(int x) => x * x; // Pure function

Not pure:

int counter = 0;
int Increment() => ++counter; // Not pure (depends on external state)

1.4 Function Composition

Combine small functions into larger ones.

Func<int, int> doubleIt = x => x * 2;
Func<int, int> squareIt = x => x * x;

// Compose manually
Func<int, int> doubleThenSquare = x => squareIt(doubleIt(x));

var result = doubleThenSquare(3); // (3*2)^2 = 36

1.5 Declarative Style (LINQ)

  • FP favors describing what to do rather than how to do it.
  • LINQ is a great example of declarative programming in C#.
var numbers = new[] { 1, 2, 3, 4, 5 };

// Declarative: filter and transform
var evensSquared = numbers
    .Where(n => n % 2 == 0)
    .Select(n => n * n);

foreach (var n in evensSquared)
    Console.WriteLine(n); // Output: 4, 16

1.6 Lazy Evaluation

  • FP often defers computation until needed.
  • In C#, IEnumerable with yield return or LINQ queries are (mostly) lazily evaluated.
IEnumerable<int> Squares()
{
    int i = 1;
    while (true)
        yield return i * i++;
}

🔑 Summary Functional programming in C# revolves around:

  • Treating functions as values
  • Using immutability
  • Writing pure functions
  • Composing small functions
  • Favoring declarative style (LINQ)
  • Leveraging lazy evaluation

2. Monads in C#

2.1 What Is a Monad?

A monad is a design pattern from functional programming that:

  • Wraps a value in a context (e.g., "maybe this value exists", "this value is asynchronous", "this value is a sequence").
  • Provides a way to chain operations on that value without breaking the context.
  • Ensures consistent handling of side effects (nulls, errors, async, logging, etc.). Think of it as a container + rules for chaining.

A monad must support:

  • Return/Unit → wrap a value (handle the cases where the function has no valid value, eg null pointer, ...)
  • Bind → Chain operations (so it can be chained through functions) and everything works even if the value is null/none/invalid, etc. This works because we make functions act like pure functions by handling the bad cases through the Monad.

2.2 Common Monads in C#

Monad Meaning
Task<T> Asynchronous computation
IEnumerable<T> Sequence computation
Nullable<T> Optional values
Result<T> Success/failure pipeline

2.3.1 Example: Option Monad

/// <summary>
/// An option type that represents a value that may or may not be present.
/// It is used to avoid null references and provide a safer way to handle optional values.
/// </summary>
/// <typeparam name="T"></typeparam>
public class Option<T>
{
    private readonly T _value;

    /// <summary>
    /// Gets a value indicating whether the current Option has a value.
    /// </summary>
    public bool HasValue { get; }

    // Private constructor to create an Option with a value or without a value
    private Option() { HasValue = false; }

    
    // Private constructor to create an Option with a value
    private Option(T value) { _value = value; HasValue = true; }

    /// </summary>
    /// Factory methods to create an Option with a <paramref name="value"/>
    /// </summary>
    /// <param name="value">The value to wrap in an Option.</param>
    /// <returns>An Option containing the specified value.</returns>
    public static Option<T> Some(T value) => new(value);

    /// <summary>
    /// Factory methods to create an Option without a value
    /// </summary>
    /// <returns>An empty Option.</returns>
    public static Option<T> None() => new();

    /// <summary>
    /// Invokes f with the current value when present and returns the resulting Option; otherwise returns None. Like SelectMany in LINQ.
    /// </summary>
    /// <remarks>Implements monadic bind (flatMap) semantics for Option<T>. Allows for chaining operations that might fail.</remarks>
    /// <typeparam name="TResult">The type of the value contained in the returned Option.</typeparam>
    /// <param name="f">Function that maps the current value to an Option<TResult>.</param>
    /// <returns>An Option<TResult> produced by applying f to the current value if present; otherwise a None Option<TResult>.</returns>
    public Option<TResult> Bind<TResult>(Func<T, Option<TResult>> f)
        => HasValue ? f(_value) : Option<TResult>.None();

    /// <summary>
    /// Maps the current value to a new value of type TResult if present; otherwise returns None. (like Select in LINQ)
    /// </summary>
    /// <remarks>Implements monadic map semantics for Option<T>.</remarks>
    /// <typeparam name="TResult">The type of the value contained in the returned Option.</typeparam>
    /// <param name="f">Function that maps the current value to a TResult.</param>
    /// <returns>An Option<TResult> containing the mapped value if present; otherwise a None Option<TResult>.</returns>
    public Option<TResult> Map<TResult>(Func<T, TResult> f)
        => HasValue ? Option<TResult>.Some(f(_value)) : Option<TResult>.None();

    /// <summary>
    /// Extract the value with a fallback/default if the current Option is None.
    /// </summary>
    /// <param name="fallback">The value to return if the current Option is None.</param>
    /// <returns>The current value if present; otherwise the fallback value.</returns>
    public T GetValueOrDefault(T fallback)
        => HasValue ? _value : fallback;
}

2.3.2 Using this Monad

var maybeNumber = Option<int>.Some(5);

var result = maybeNumber
    .Bind(x => Option<int>.Some(x * 2))
    .Bind(x => Option<int>.Some(x + 10));

// result = Some(20)

This avoids null checks everywhere — the monad handles the "no value" case.

2.4.1 Task Monad (async) Example

Task in C# is essentially a monad:

  • Task.FromResult(value) → wraps a value.
  • await / ContinueWith → bind operations.
async Task<int> DoubleAsync(int x) => x * 2;

var result = await Task.FromResult(5)
    .ContinueWith(t => DoubleAsync(t.Result))
    .Unwrap();

Here Task ensures async chaining without manually handling threads.

2.5.1 LINQ Query Syntax (Enumerable Monad)

LINQ’s SelectMany is the bind operation for sequences.

var numbers = new[] { 1, 2, 3 };
var doubled = from n in numbers
              from m in new[] { n * 2 }
              select m;

// doubled = {2, 4, 6}

LINQ query comprehension is syntactic sugar for monadic chaining.

⚡ Summary In C#:

  • Option (or Nullable) → Maybe Monad
  • Task → Async Monad
  • IEnumerable → Sequence Monad
  • LINQ query syntax → Monadic chaining (SelectMany) 👉 The purpose: monads let you build pipelines of computation while hiding the messy details of context management (nulls, async, errors, etc.).

🧩 Scenario Imagine a WPF app where the ViewModel fetches a user profile. Sometimes the profile data might be missing (e.g., network error, no record). Instead of sprinkling if (profile != null) everywhere, we’ll use an Option Monad.

  1. Define a Simple Option<T> Monad. See the class above

3. Monads in MVVM (Practical Example)

public class ProfileViewModel : ObservableObject
{
    private Option<UserProfile> _profile = Option<UserProfile>.None();

    public string DisplayName =>
        _profile.Map(p => $"Welcome, {p.Name}!").GetValueOrDefault("Guest");

    public async Task LoadAsync()
    {
        var fetched = await FetchProfileAsync();
        _profile = fetched is null
            ? Option<UserProfile>.None()
            : Option<UserProfile>.Some(fetched);

        OnPropertyChanged(nameof(DisplayName));
    }
}

4. Partial Functions

4.1 What Is a Partial Function?

A function that is not defined for all inputs. Example:

int Divide(int x, int y) => x / y; // undefined when y ** 0

4.2 Partial Application (Different Concept)

Fixing some arguments of a function.

Func<int, int, int> add = (x, y) => x + y;
Func<int, int> addFive = y => add(5, y);

4.3 Lifting a Partial Function into a Monad

Option<int> SafeDivide(int x, int y)
{
    if (y == 0) return Option<int>.None();
    return Option<int>.Some(x / y);
}

6. Immutability in Functional Programming

6.1 Why It Matters

Immutability provides:

  • predictability
  • pure functions
  • thread safety
  • referential transparency
  • easier debugging
  • safe composition
  • undo/redo and time-travel debugging

6.2 Example: Immutable State

record AppState(int Count);

var state1 = new AppState(0);
var state2 = state1 with { Count = state1.Count + 1 };

7. Using Monads for Unit-Safe Values

7.1 Example: Length Monad

public class Length
{
    private readonly double _meters;

    private Length(double meters) => _meters = meters;

    public static Length FromMeters(double m) => new(m);
    public static Length FromKilometers(double km) => new(km * 1000);

    public Length Map(Func<double, double> f) => new(f(_meters));
    public Length Bind(Func<double, Length> f) => f(_meters);

    public double ToMeters() => _meters;
}

8. Practice Quiz (Optional)

1. What is a pure function?

Answer: A function with no side effects that always returns the same output for the same input.

2. Which LINQ method corresponds to monadic bind?

Answer: Select Many

3. What is the purpose of a monad?

Answer: To wrap values in context and allow safe, composable operations.

4. Which C# type is an example of a monad?

Answer: Task

5. How does Option help in MVVM?

Answer: Eliminates null checks by encapsulating optional values.

9. Summary

Functional programming in C# gives you:

  • safer code
  • predictable behavior
  • composable pipelines
  • easier testing
  • fewer bugs

Monads, immutability, and pure functions work together to create a clean, expressive, and maintainable architecture.

Copied and edited from a Copilot chat

February 17, 2026

From ".slnx" => ".sln" file

To create a new ".slnx" solution file from a ".sln" file. Open a command window (cmd.exe). Within that window change to the folder containing your "{YourSolutionName}.sln" file, you can do this with "cd <solution folder path>". Now, type in the following line:

"dotnet sln {YourSolutionName}.sln migrate"

This generates a new "{YourSolutionName}.slnx" file. The original "{YourSolutionName}.sln" file remains untouched. Here is an example of an slnx file:

<Solution>
  <Configurations>
    <Platform Name="Any CPU" />
    <Platform Name="x86" />
  </Configurations>
  <Project Path="MyProject.UnitTests/MyProject.UnitTests.csproj" />
  <Project Path="MyProject/MyProject.csproj" />
</Solution>

It is quite simple and can be code genereated quite easily

February 6, 2026

C# Dot Net SDK Version Macros

It is useful to be able to include or exclude code based on preprocessor macros. We can do this for the different versions of .NET

1. Use "#if" with framework version symbols to include specific code for specific .Net versions .NET SDK defines symbols like "Net5_0", "Net6_0", "Net7_0", etc. For .NET 10, the symbol will be "Net10_0". This works automatically if you're targeting .NET 10 via the SDK-style project and using the element <TargetFramework> like: "<TargetFramework>net10.0<TargetFramework>". Here is an example of this:

#if NET10_0
    // This code will be included when targeting .NET 10
    Console.WriteLine("Running on a framework other than .NET 10");
#endif
🧠 These symbols can be combined for example:

#if NET6_0 || NET7_0
    Console.WriteLine("Running on .NET 6 or 7");
#elif NET10_0
    Console.WriteLine("Running on .NET 10");
#endif
2. Invert the macro condition to exclude code using the ! sign For example, you can use "#if !NET10_0" to exclude code when the target is not .NET 10. Here is an example of this:

#if !NET10_0
    // This code will be excluded when targeting .NET 10
    Console.WriteLine("Running on a framework other than .NET 10");
#endif
3. You can also use a "_OR_GREATER" suffix, for example:

#if NET5_0_OR_GREATER
    // This code will be included when targeting .NET 5 or greater
#endif
There are also platform-specific preprocessor symbols for conditional compilation based on the operating system. These are especially useful when writing cross-platform code in .NET Core or .NET 5+. 🧭 Built-in platform symbols Here are the most common ones:
OS Target
WINDOWS Compiling for Windows
LINUX Compiling for Linux
OSX Compiling for Mac OS
ANDROID Compiling for Android
IOS Compiling for iOS
MACCATALYST Compiling for Mac Catalyst
FREEBSD Compiling for FreeBSD
BROWSER Compiling for WebAssembly (Blazor)
These are automatically defined by the SDK when targeting specific platforms.
✅ Example usage

#if WINDOWS
    Console.WriteLine("Running on Windows");
#elif LINUX
    Console.WriteLine("Running on Linux");
#elif OSX
    Console.WriteLine("Running on macOS");
#else
    Console.WriteLine("Unknown platform");
#endif

🔍 Notes

  • These symbols are only defined when the runtime identifier (RID) or target platform is specified appropriately in your ".csproj " file.
  • If you're multi-targeting or using runtime checks, you might prefer OperatingSystem.IsWindows() or similar APIs from System.Runtime.InteropServices

Note that this is heavily edited output from CoPilot.

January 30, 2026

Using System.IO.Abstractions for File System Abstraction in C#

System.IO.Abstractions is a library that helps abstract file system operations in C#. It is particularly useful when writing unit tests because it allows you to mock file system interactions. Here's how you can integrate it into your project for both regular file operations and testing scenarios.

1. Adding System.IO.Abstractions to Your Project

First, you'll need to add the System.IO.Abstractions NuGet package to your project. In your .csproj file, include the following line:

      <PackageReference Include="System.IO.Abstractions" Version="21.0.29" /> <!-- See https://github.com/TestableIO/System.IO.Abstractions -->

    

More details are here: https://github.com/TestableIO/System.IO.Abstractions

2. Registering the IFileSystem Service for Dependency Injection

Next, inject IFileSystem into your application's services. This allows you to easily work with the file system and mock it for testing. In your Startup.cs (or Program.cs), register the FileSystem implementation as a transient service:

      // Register IFileSystem to allow injection
services.AddTransient<IFileSystem, FileSystem>();

    

This ensures that the IFileSystem interface will be resolved to the concrete FileSystem class when injected.

3. Injecting and Using IFileSystem in Your Class

In your class, inject IFileSystem via the constructor. This allows you to interact with the file system in a testable way.

      public class SomeService
{
    private readonly IFileSystem _fileSystem;

    // Constructor injection of IFileSystem
    public SomeService(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    // Example method using IFileSystem to check if a file exists
    public void DoSomething(string fileName)
    {
        bool exists = _fileSystem.File.Exists(fileName);
        if (exists)
        {
            var content = _fileSystem.File.ReadLines(fileName);
            // Process the content
			...
        }
    }
}

    

In this example, DoSomething checks if a file exists using the abstracted IFileSystem and reads the file's contents if it does.

4. Unit Testing with System.IO.Abstractions.TestingHelpers

For unit testing, you'll want to mock the file system to avoid interacting with the actual file system. You can do this by adding System.IO.Abstractions.TestingHelpers to your project:

      <PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />

    

Now, you can use the MockFileSystem class to simulate a file system in your tests.

4.1. Mocking Files and Directories

Here's an extension method for setting up files (empty) and directories in your IFileSystem (mock or otherwise):

      public static IFileSystem AddFilesAndDirectories(
    this IFileSystem fileSystem, 
    IEnumerable<string> fileSystemEntries)
{
    if (fileSystem == null)
        throw new ArgumentNullException(nameof(fileSystem));

    if (fileSystemEntries == null)
        throw new ArgumentNullException(nameof(fileSystemEntries));

    foreach (var entry in fileSystemEntries.Select(x => x.Trim()))
    {
        // Check if the entry is a directory or file by checking the last character
        // if it is a directory separator assume it is a directory
        if (entry[^1] == Path.DirectorySeparatorChar || entry[^1] == Path.AltDirectorySeparatorChar)
        {
            fileSystem.AddDirectory(entry); // Add directory if the string ends with a directory separator
        }
        else // otherwise assume it is a file
        {
            fileSystem.AddFile(entry, "x"); // Add file with some content. File parent directories will also be created.
        }
    }

    return fileSystem;
}

    

This helper method adds directories and files to the MockFileSystem. It differentiates between files and directories based on the path format (whether it ends with a directory separator).

4.2. Example Unit Test

Here’s a simple unit test demonstrating how to use the MockFileSystem:

      using System.IO.Abstractions.TestingHelpers;
using Xunit;

public class SomeServiceTests
{
    [Fact]
    public void TestFileOperations()
    {
        // Setup mock file system
        var mockFileSystem = new MockFileSystem();

        var someService = new SomeService(fileSystem);
        ...
    }
}

    

You can use these methods to interact with files and directories in an abstracted way, making your code more testable and platform-independent.

6. Conclusion

System.IO.Abstractions is a library for abstracting file system operations, which simplifies unit testing and makes your code more modular and easier to maintain.

By using IFileSystem for dependency injection and MockFileSystem for unit tests, you can easily mock file system interactions without relying on the actual file system.By following the steps outlined above, you'll be able to set up file system abstractions in your project and write unit tests that are isolated from the underlying file system, improving the testability and reliability of your application.

April 16, 2025

Stream Extension Class

This extension class is useful when you want to create a stream from some bytes of directly from a string

public static class StreamExtender
{
    /// <summary>
    /// Create a stream from an array of bytes
    /// </summary>
    /// <param name="streamBytes">Bytes source foe the stream</param>
    /// <returns></returns>
    public static MemoryStream CreateStreamFromBytes(this byte[] streamBytes)
    {
        var stream = new MemoryStream();
        stream.Write(streamBytes, 0, streamBytes.Length);
        stream.Seek(0, 0);
        return stream;
    }

    /// <summary>
    /// Create a stream from a string
    /// </summary>
    /// <remarks>Useful for unit testing file storage</remarks>
    /// <param name="contents">The contents to be placed in the stream</param>
    /// <returns>A memory stream with the string as contents</returns>
    public static MemoryStream CreateStreamFromString(this string contents)
    {
        byte[] encodedStreamBytes = Encoding.UTF8.GetBytes(contents);
        var stream CreateStreamFromBytes(encodedStreamBytes);
        return stream;
    }
}

December 12, 2024

Using Patterns in if and switch statements

Using Patterns in if and switch statements See this link: https://www.thomasclaudiushuber.com/2021/02/18/c-9-0-improved-pattern-matching/ Replace the old inefficient
ChangedEvent lce = ev as ChangedEvent
if (lce != null)
{
  DoSomethingWith(lce);
}
to this much shorter and more readable:
if (ev is ChangedEvent lce)
{
  DoSomethingWith(lce);
}
And this has been refined further with much more readable conditional clauses using patterns
var developer = new Developer { YearOfBirth = 1983 };
if (developer is { YearOfBirth: >= 1980 and <= 1989 and not 1984 })
{
  // The dev is born in the eighties, but not in 1984
}
And when checking for is or is not null use this pattern
if (developer is not null)
{
  ...
}
We can also use these patterns in switch statements TODO add an example Now we have "and" and "&&" plus "or" and "||": The "and" pattern combinator is used to combine patterns. The conditional "and" operator of "&&" is a boolean operator and it is used to combine bool values in your C# code. Similar statements can be made for the pattern "or" and "not" clauses

August 22, 2024

Immediate Window in Visual Studio

Sometimes I find I want to dump an object that I see in debugging for further analysis, testing or just to get a feel for the shape of some data. To do this I stop at a location in the debugger where the data is visible grab a copy of the required variable and then switch to the VS Immediate Window.
In Immediate window I can dump a variables contents to a json file by typing in the following C#:

File.WriteAllText(@"c:\Somewhere\delme.json", Newtonsoft.Json.JsonConvert.SerializeObject(myobject, Formatting.Indented));

Alternatively use System.Text.Json

File.WriteAllText(@"c:\somewhere\delme.json", System.Text.Json.JsonSerializer.Serialize(myobject, new System.Text.Json.JsonSerializerOptions() { WriteIndented = true }));