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

July 12, 2024

Time Providers

In .Net 8.0 there is now a means to inject a time provider. Here is a link https://blog.nimblepros.com/blogs/finally-an-abstraction-for-time-in-net/


1. It can be passed via dependency injection:
ServiceCollection services = new ServiceCollection();
...
services.AddSingleton<TimeProvider>(TimeProvider.System);

2. Pass it in to your constructor
private readonly TimeProvider _timeProvider;
public MyConstructor(..., TimeProvider timeProvider)
{
    _timeProvider = timeProvider;
}

3. Use it somewhere
void SomeMethod()
{
…
   var nowUtc = _timeProvider.GetUtcNow();
…
}

4. Use the FakeTimeProvider in unit tests
using Microsoft.Extensions.Time.Testing; // Get from Package from NuGet (same name as namespace)
...
FakeTimeProvider fakeTimeProvider = new();
fakeTimeProvider.SetUtcNow(new DateTimeOffset(2025, 3, 4, 13, 22, 42, new TimeSpan(0)));
...
fakeTimeProvider.Advance(new TimeSpan(entry.TimeMs * TimeSpan.TicksPerMillisecond));

April 11, 2024

Using Caller Context with Microsoft Logging

Here is the CallerContext and ILoggerExtension class

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

    /// <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>
/// To get the code context using caller member attributes
/// </summary>
public static class Code
{
    public static CallerContext Context(
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        return new CallerContext(memberName, sourceFilePath, sourceLineNumber);
    }
}

/// <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
        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(Code.Context(), LogLevel.Info, "Message with {@parameters} goes here", parameters);

October 16, 2023

Bit Operations

An extension class that allows various bitwise operations on an integer to be performed when dealing with a [Flags] Enum type.

// Bit wise operations on an integer as an extensions class
public static class IntBitwiseOperationsExtender
{
    // Returns bits set in lhs or rhs or both
    public static int BitwiseUnion(this int lhs, int rhs)
    {
        int bitWiseOr = lhs | rhs;
        return bitWiseOr;
    }

    // Return bits set common to both lhs and rhs
    public static int BitwiseIntersection(this int lhs, int rhs)
    {
        return lhs & rhs;
    }

    // Returns bits set in lhs or rhs but not in both
    public static int BitwiseExclusiveOr(this int lhs, int rhs)
    {
        int exclusiveOr = BitwiseUnion(lhs, rhs) - BitwiseIntersection(lhs, rhs);
        return exclusiveOr;
    }

    // Return lhs bits inverted, 0s becomes 1s and vice versa
    public static int BitwiseInvert(this int lhs)
    {
        int bitWiseOr = ~lhs;
        return bitWiseOr;
    }

    // Return lhs bits set minus any that are also set in the rhs
    public static int BitwiseRemove(this int lhs, int rhs)
    {
        int common = BitwiseIntersection(lhs, rhs); // Find the bits common to both sides
        int res = (int)lhs - (int)common;
        return res;
    }

    // Return a value that has all the bits set either in the lhs part or in the rhs part or both
    public static int BitwiseOr(this int lhs, int rhs)
    {
        return BitwiseUnion(lhs, rhs); // Same as a Bitwise Union
    }

    // Return true if lhs contains all the bits set within rhs
    public static bool BitwiseContains(this int lhs, int rhs)
    {
        int common = lhs & rhs;
        return (common == rhs);
    }

    // Return true if lhs contains one of the bits set within rhs
    public static bool BitwiseContainsOneOf(this int lhs, int rhs)
    {
        int common = lhs & rhs;
        return common > 0;
    }
}