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"