Optimal usage of Global Usings

I have a solution with ~40 projects, built during many years with wild usings that I wanted to clean up. The global usings feature, introduced in with C# 10.0 and .NET 6 (nov21) to simplify and reduce the repetition of commonly used namespaces throughout a project.

The question I have is, how do I ‘get’ the most commonly used namespaces and how do I do that at scale.

Getting an idea of what is ‘used’

This (LINQPad script) gives you an idea of the most commonly used ones, across your solution.

var solutionPath = @"path-to-solution";
var files = Directory.EnumerateFiles(solutionPath, "*.cs", SearchOption.AllDirectories);

var usingStatements = files
	.SelectMany(file => File.ReadLines(file)
		.Where(line => line.TrimStart().StartsWith("using "))
		.Select(line => line.Trim()))
	.GroupBy(usingLine => usingLine)
	.Select(group => new { Using = group.Key, Count = group.Count() })
	.OrderByDescending(x => x.Count);

usingStatements.Dump();

You will not be surprised with the results:

Generating GlobalUsings.cs

How do I now add a GlobalUsings.cs file to every one of these 40 projects, with the relevant usings?

  1. I define a variable commonUsings which has the most reasonable candidates.
  2. I scan every project directory and tally up the usings.
  3. The intersection between #1 and #2 I write to a GlobalUsings.cs file
  4. Done
var solutionPath = @"path-to-solution";
var globalUsingsFileName = "GlobalUsings.cs";

// Define the list of namespaces to intersect with (System, System.Linq)
var commonUsings = new[] { "using System;", "using System.Linq;", "using FluentAssertions;", "using Moq;", "using Xunit;", "using System.Collections.Generic;" };

// Find all directories with a `.csproj` file, assuming each project has one.
var projectDirectories = Directory.EnumerateFiles(solutionPath, "*.csproj", SearchOption.AllDirectories)
    .Select(csprojPath => Path.GetDirectoryName(csprojPath))
    .Distinct(); // Get unique project directories

foreach (var projectDirectory in projectDirectories)
{
    // Get all C# files within the project directory
    var csFiles = Directory.EnumerateFiles(projectDirectory, "*.cs", SearchOption.AllDirectories);

    var usingStatements = csFiles
        .SelectMany(file => File.ReadLines(file)
            .Where(line => line.TrimStart().StartsWith("using "))
            .Select(line => line.Trim()))
        .GroupBy(usingLine => usingLine)
        .Select(group => new { Using = group.Key, Count = group.Count() })
        .OrderByDescending(x => x.Count)
		.Where(u => u.Count > 1);

    // Output the results for each project
    Console.WriteLine($"Project: {Path.GetFileName(projectDirectory)}");
    usingStatements.Dump();

    // Find intersection of most used usings with the specified common usings
    var intersectingUsings = usingStatements
        .Select(u => u.Using)
        .Intersect(commonUsings)
        .ToList();

    if (intersectingUsings.Any())
    {
        // Prepare the path for the GlobalUsings.cs file
        var globalUsingsFilePath = Path.Combine(projectDirectory, globalUsingsFileName);

		// Create or overwrite the GlobalUsings.cs file with the intersecting using statements
		var globalUsingsContent = intersectingUsings
			.Select(usingDirective => $"global {usingDirective}") // Convert to global using
			.Aggregate((current, next) => current + Environment.NewLine + next); // Combine into one string

		File.WriteAllText(globalUsingsFilePath, globalUsingsContent);

		Console.WriteLine($"GlobalUsings.cs file created/updated for project {Path.GetFileName(projectDirectory)} with the following global usings:");
		intersectingUsings.ForEach(Console.WriteLine);
	}
}

Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *