Showing posts with label c#. Show all posts
Showing posts with label c#. Show all posts

October 2, 2026

PadLeft and PadRight

Just a simple example to show what PadLet and PadRight do:


void Main()
{  
	int[] numbers = { 1, 12, 123, 1234, 12345, 123456, 1234567, 12345678, 123456789, 1234567890 };

    DisplayPad(numbers, '-', 9);
}

public static void DisplayPad(IEnumerable numbers, char padChar = ' ', int padSize = 8)
{
    foreach (var number in numbers)
    {
        var none = number.ToString();
        var left = none.PadLeft(padSize, padChar);
        var right = none.PadRight(padSize, padChar);
        Console.WriteLine($"|{left}|{none}|{right}|");
    }
}
Here is what the output looks like:
|--------1|1|1--------|
|-------12|12|12-------|
|------123|123|123------|
|-----1234|1234|1234-----|
|----12345|12345|12345----|
|---123456|123456|123456---|
|--1234567|1234567|1234567--|
|-12345678|12345678|12345678-|
|123456789|123456789|123456789|
|1234567890|1234567890|1234567890|

Linq Aggregate Functionality

Here is a link to a explanation of the linq aggregate method on Det Net Tutorials: LINQ Aggregate Method in C# The most powerful form allows you to set a seed value to start the Aggregate computation and a result selector to perform a final calculation at the end calculation. I like the way the examples are written in the examples, it makes it so much easier to see what is being done.


List ints = [4, 6, 9, 1, 3, 5];
List decimals = [4.2m, 6.3m, 9.7m, 1.8m, 3.4m, 5.1m];

// Aggregate to (total revenue, count), then project to average
decimal averageDecimals = decimals.Aggregate(
    seed: (Total: 0m, Count: 0),
    func: (acc, s) => (acc.Total + s, acc.Count + 1),
    resultSelector: acc => acc.Count > 0 ? acc.Total / acc.Count : 0m);

decimal averageInts = ints.Aggregate(
        seed: (Total: 0m, Count: 0),
        func: (acc, s) => (acc.Total + (decimal)s, acc.Count + 1),
        resultSelector: acc => acc.Count > 0m ? acc.Total / (decimal)acc.Count : 0m);

Console.WriteLine($"Average Decimals: ${averageDecimals:F2}");
Console.WriteLine($"Average Ints: ${averageInts:F2}");

I know their are already methods for calculating an average, it is just a good example to see how the Aggregate method is used

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

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>();
        ...

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);

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

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

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 }));

April 11, 2024

Using Caller Context with Microsoft Logging

Here are the CallerContext and ILoggerExtension class. They allow the compiler special Caller member attributes to be logged during logging. I believe this to be of enormous benefit when trying to fix bugs in the field.

using System.Runtime.CompilerServices;

/// <summary>
/// A class to record the context of a method call.
/// Records the member name, file and line number of where the method call was made.
/// </summary>
public record CallerContext
{
    public CallerContext(
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        MemberName = memberName;
        FilePath = sourceFilePath;
        LineNumber = sourceLineNumber;
    }
    
    public static CallerContext Create(
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        return new CallerContext(memberName, sourceFilePath, sourceLineNumber);
    }

    /// <summary>
    /// Member where the call was made
    /// </summary>
    public string MemberName { get; init; }
    /// <summary>
    /// File where the call was made
    /// </summary>
    public string FilePath { get; init; }
    /// <summary>
    /// Line number in the file where the call was made
    /// </summary>
    public int LineNumber { get; init; }
}

/// <summary>
/// ILogger extensions
/// </summary>
public static class ILoggerExt
{
    /// <summary>
    /// Log something but with extra Caller context information <seealso cref="CallerContext"/>
    /// </summary>
    /// <param name="logger">ILogger being invoked</param>
    /// <param name="logLevel">Level of the logging</param>
    /// <param name="context">The caller context <see cref="CallerContext"/></param>
    /// <param name="message">The message to log</param>
    /// <param name="args">The message parameters to log.</param>
    public static void Log(this ILogger logger,
        LogLevel logLevel,
        CallerContext context,
        string message,
        params object[] args)
    {
        Debug.Assert(logger != null, "trying to use a null logger to log something");
        var enhancedMessage = "{@CallerContext} "  + (message ?? "");

        Debug.Assert(args != null, "args cannot be null");

        object[] newArgs = new object[args.Length + 1];
        newArgs[0] = context; // Prepend the context argument
        // Drawback: Could this cause a slowdown in logging over a long period ?
        Array.Copy(args, sourceIndex: 0, newArgs, destinationIndex:1, args.Length); // add the other arguments

        logger.Log(logLevel, enhancedMessage, newArgs.ToArray());
    }
}

Example Usage

ILogger logger = ...
logger.Log(new CallerContext(), LogLevel.Info, "Message with {@parameters} goes here", parameters);
// OR
logger.Log(CallerContext.Create(), LogLevel.Info, "Message with {@parameters} goes here", parameters); // I prefer this one

October 14, 2023

Simple Line Editor 2

Here is the new LineEditor, it can work on a file or a string. This timing using the power of LINQ.

// Here is the definition of a line edit
public interface ILineEdit
{
    string ApplyEdit(string input);
}

/// <summary>
/// Perform a bunch of edits on a text file or string on a line by line basis. 
/// Only search and replace type edits are available.
/// </summary>
/// <summary>
/// Perform a bunch of edits on a IEnumerable<string>. 
/// </summary>
public class TextLineEditor
{
    private List<ILineEdit> _pendingEdits = new List<ILineEdit>();

    public TextLineEditor()
    {
    }

    public IEnumerable<string> ApplyEdits(IEnumerable<string> inputs)
    {
        foreach (var line in inputs)
        {
            string tmp = line;
            foreach (ILineEdit edit in _pendingEdits)
            {
                tmp = edit.ApplyEdit(tmp);
            }
            yield return tmp;
        }
    }

    public void AddEdit(ILineEdit edit)
    {
        Trace.Assert(edit != null);
        _pendingEdits.Add(edit);
    }
}

We can use this as the basis of a line by line text file ediitor:

/// <summary>
/// Perform a bunch of edits on a text file 
/// Only search and replace type edits are available.
/// </summary>
public class FileTextLineEditor : TextLineEditor
{
    public void ApplyEditsToFile(string inputPath, bool keepOriginal = true)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(inputPath));
        var inputFileInfo = new FileInfo(inputPath);
        Debug.Assert(inputFileInfo.Exists);

        var outputPath = inputPath + ".new";
        var outputFileInfo = new FileInfo(outputPath);
        Debug.Assert(!outputFileInfo.Exists);

        File.WriteAllLines(outputFileInfo.FullName, ApplyEdits(File.ReadLines(inputFileInfo.FullName)));
        
        inputFileInfo.MoveTo(inputPath + ".bak");
        outputFileInfo.Refresh();
        outputFileInfo.MoveTo(inputPath);
        if (!keepOriginal)
        {
            File.Delete(inputPath + ".bak");
        }
    }
}
Here is a LineEdit example. Note that it can do deletes by replacing with a blank string.

public class SearchAndReplace : ILineEdit
{
    public enum RegExUsage
    {
        None = 0,
        Use
    }

    public SearchAndReplace()
    {
    }

    public SearchAndReplace(string searchFor, string replaceWith, bool extendedChars = false, RegExUsage regExUse = RegExUsage.None)
    {
        if (string.IsNullOrEmpty(searchFor))
            throw new ArgumentException("Cannot be null or an empty string", "searchFor");
        if (replaceWith == null)
            throw new ArgumentException("Cannot be null", "replaceWith");
        this.SearchFor = searchFor;
        this.ReplaceWith = replaceWith;
        this.ExtendedChars = extendedChars;
    }

    public bool IsRegEx { get; set; } = false;
    public string SearchFor { get; set; } = "";
    public string ReplaceWith { get; set; } = "";
    public bool ExtendedChars { get; set; } = false;

    public string ApplyEdit(string input)
    {
        if (ExtendedChars)
        {
            SearchFor = SearchFor.Replace("\\t","\t").Replace("\\n","\n").Replace("\\r","\r");
        }
        return (IsRegEx) ? SearchReplaceInString(input) : SearchReplaceInStringRegEx(input);
    }

    private string SearchReplaceInString(string input)
    {
        string output = input.Replace(SearchFor, ReplaceWith);
        return output;
    }

    private string SearchReplaceInStringRegEx(string input)
    {
        string output = Regex.Replace(input, SearchFor, ReplaceWith);
        return output;
    }
}

Here is an example usage that attempts to replace usage of Moq in a CS file to usage of NSubstitute:
void Main()
{
    string inputFile = @"a:/path/SomeFileName.cs";

    FileTextLineEditor resEditor = new();
    resEditor.AddEdits(this.Edits());
    resEditor.ApplyEditsToFile(inputFile, keepOriginal: false);
}

internal IEnumerable<ILineEdit> Edits()
{
    yield return new SearchAndReplace("using Moq", "using NSubstitute");
    yield return new SearchAndReplace("MockBehavior.Strict", "");
    yield return new SearchAndReplace("MockBehavior.Loose", "");
    yield return new SearchAndReplace(".Object", "");
    yield return new SearchAndReplace("It.IsAny", "Arg.Any");
    yield return new SearchAndReplace("It.Is", "Arg.Is");
    yield return new SearchAndReplace(@"(new\sMock\<([A-Za-z\<\>\-_]+)\>)", @"Substitute.For<$2>", false, SearchAndReplace.RegExUsage.Use);
    yield return new SearchAndReplace(@"(Mock\<([A-Za-z\<\>\-_]+)\>\s([A-Za-z\<\>\-_]+)\s\=\snew)", @"var $3 = Substitute.For<$2>", false, SearchAndReplace.RegExUsage.Use);
    // yield return new SearchAndReplace(@" = new()", @" = Substitute.For(??)");
    //yield return new SearchAndReplace(@"(^\sMock\<([A-Za-z\<\>\-_]+)", "var", false, SearchAndReplace.RegExUsage.Use);
    yield return new SearchAndReplace(@"(Setup\([a-z]+\s\=\>\s[a-z]+\.)", "", false, SearchAndReplace.RegExUsage.Use);
    yield return new SearchAndReplace(@"(SetupGet\([a-z]+\s\=\>\s[a-z]+\.)", "", false, SearchAndReplace.RegExUsage.Use);
    yield return new SearchAndReplace(@"(Verify\([a-z]+\s\=\>\s[a-z]+)", "Received(?)", false, SearchAndReplace.RegExUsage.Use);
    yield return new SearchAndReplace("Times.Once", "1");
    yield return new SearchAndReplace("Times.Never", "0");
    yield return new SearchAndReplace(@"Returns\(\(\)\s\=\>\s", "Returns(");
} 

August 15, 2023

Simple Line Editor

Here is the LineEditor, it can work on a file or a string:

// Here is the definition of a line edit
public interface ILineEdit
{
    string ApplyEdit(string input);
}

/// <summary>
/// Perform a bunch of edits on a text file or string on a line by line basis. 
/// Only search and replace type edits are available.
/// </summary>
public class TextLineEditor
{
    private List<ILineEdit> _pendingEdits = new List<ILineEdit>();

    public TextLineEditor()
    {
    }

    public void ApplyEditsToFile(string inputPath, string outputPath)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(inputPath));
        Debug.Assert(!string.IsNullOrWhiteSpace(outputPath));
        Debug.Assert(!outputPath.Equals(inputPath)); 
        var inputFileInfo = new FileInfo(inputPath);
        var outputFileInfo = new FileInfo(outputPath);
        Debug.Assert(inputFileInfo.Exists);
        Debug.Assert(!outputFileInfo.Exists);

        using (TextWriter writer = new StreamWriter(outputFileInfo.FullName))
        {
            using (TextReader reader = new StreamReader(inputFileInfo.FullName))
            {
                ApplyEdits(writer, reader);
            }
        }
    }

    public string ApplyEditsToString(string text)
    {
        Trace.Assert(text != null);
        StringBuilder sb = new StringBuilder();
        using (TextWriter writer = new StringWriter(sb))
        {
            using (TextReader reader = new StringReader(text))
            {
                ApplyEdits(writer, reader);
            }
        }
        return sb.ToString();
    }

    private void ApplyEdits(TextWriter writer, TextReader reader)
    {
        string line = null;
        while ((line = reader.ReadLine()) != null)
        {
            foreach (ILineEdit edit in _pendingEdits)
            {
                line = edit.ApplyEdit(line);
            }
            if (line != null)
                writer.WriteLine(line);
        }
    }

    public void AddEdit(ILineEdit edit)
    {
        Trace.Assert(edit != null);
        _pendingEdits.Add(edit);
    }
}

This TextLineEditor could be replaced to work with IEnumerab<string> for line input and returning a IEnumerable<string> as an output. Here is a LineEdit example. Note that it can do deletes by replacing with a blank string.

public class SearchAndReplace : ILineEdit
{
    public enum RegExUsage
    {
        None = 0,
        Use
    }

    public SearchAndReplace()
    {
    }

    public SearchAndReplace(string searchFor, string replaceWith, bool extendedChars = false, RegExUsage regExUse = RegExUsage.None)
    {
        if (string.IsNullOrEmpty(searchFor))
            throw new ArgumentException("Cannot be null or an empty string", "searchFor");
        if (replaceWith == null)
            throw new ArgumentException("Cannot be null", "replaceWith");
        this.SearchFor = searchFor;
        this.ReplaceWith = replaceWith;
        this.ExtendedChars = extendedChars;
    }

    public bool IsRegEx { get; set; } = false;
    public string SearchFor { get; set; } = "";
    public string ReplaceWith { get; set; } = "";
    public bool ExtendedChars { get; set; } = false;

    public string ApplyEdit(string input)
    {
        if (ExtendedChars)
        {
            SearchFor = SearchFor.Replace("\\t","\t").Replace("\\n","\n").Replace("\\r","\r");
        }
        return (IsRegEx) ? SearchReplaceInString(input) : SearchReplaceInStringRegEx(input);
    }

    private string SearchReplaceInString(string input)
    {
        string output = input.Replace(SearchFor, ReplaceWith);
        return output;
    }

    private string SearchReplaceInStringRegEx(string input)
    {
        string output = Regex.Replace(input, SearchFor, ReplaceWith);
        return output;
    }
}

Here is an example usage:
{
	var inputResFilePath = ...
	var outputResNewCsvFilePath ...
	var outputResCsvFilePath = ...

	var inputCnbFilePath = ...
	var outputCnbNewCsvFilePath ...
	var outputCnbCsvFilePath = ...


	var replaceCommentChars = new SearchAndReplace("#", "*");
    {
        var replaceTabsWithGT = new SearchAndReplace("\t", " > ");

        TextLineEditor resEditor = new();
        resEditor.AddEdit(replaceTabsWithGT);
        resEditor.AddEdit(replaceCommentChars);
        
        resEditor.ApplyEditsToFile(inputResFilePath, outputResNewCsvFilePath);

        File.Delete(outputResCsvFilePath);
        File.Move(outputResNewCsvFilePath, outputResCsvFilePath);
    }
    {
        var replaceExclamationChars = new SearchAndReplace("!", "*");
        var replaceTabWithHyphen = new SearchAndReplace("\t", " - ");

        TextLineEditor cnbEditor = new();
        cnbEditor.AddEdit(replaceTabWithHyphen);
        cnbEditor.AddEdit(replaceCommentChars);
        cnbEditor.AddEdit(replaceExclamationChars);

        cnbEditor.ApplyEditsToFile(inputCnbFilePath, outputCnbNewCsvFilePath);

        File.Delete(outputCnbCsvFilePath);
        File.Move(outputCnbNewCsvFilePath, outputCnbCsvFilePath);
    }
}

February 13, 2023

File Renamer

Rename a file when you have a path. This was written as a LinqPad script

void Main()
{
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"c:/some/Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"C:File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"/some/Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"/some\Directory/File.pdf"));
    Console.WriteLine(FileRenamer. CalcNewFilePath (@"e:\zdump\some\Directory\File.pdf"));
}

public static class FileRenamer
{
    const string Suffix = @"-xx";

    /// <summary>
    /// Calculate the file path of the file, changing the file name
    /// in the process
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public static string CalcNewFilePath(string filePath)
    {
        if (string.IsNullOrWhiteSpace(filePath))
            return "";
        var ext = Path.GetExtension(filePath);
        var bareFileName = Path.GetFileNameWithoutExtension(filePath);
       
        var path = GetFilePathStem(filePath);

    var fileNameTemplate = $"{path}{bareFileName}{Suffix}{ext}";
    return fileNameTemplate;
}

/// <summary>
/// Get the stem of the file path, everything before the actual filename.
/// It assumes that the last part of the path after the 
/// final Directory Seperator character is the filename
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
public static string GetFilePathStem(string filePath)
{
    var path = "";
    var endofPath1 = filePath.LastIndexOf(Path.DirectorySeparatorChar);
    var endofPath2 = Path.AltDirectorySeparatorChar != 
      Path.DirectorySeparatorChar ? filePath.LastIndexOf(Path.AltDirectorySeparatorChar) : -1;
    var endofPath = Math.Max(endofPath1, endofPath2);
    if (endofPath > 0)
    {
        path = filePath.Substring(0, endofPath + 1);
    }
    else // When the root does not use a directory separator char eg. "c:filename.pdf"
    {
        var root = Path.GetPathRoot(filePath);
        path = root;
    }
    return path;
}


 

July 12, 2022

xUnit vs MSTest vs NUnit

There is a good comparison of the 3 unit test libraries:

https://xunit.net/docs/comparisons