🚀 Roslyn UnUsed Code Remover—Clean Up Your .NET Projects with Ease

Nayana AgarwalNayana Agarwal
5 min read

Introduction


Have you ever returned to a .NET project only to find it cluttered with unused code—methods, variables, or even entire classes that serve no purpose anymore? If so, you're not alone. As projects grow and evolve, it's easy for dead code to accumulate, quietly bloating your codebase and making maintenance harder.

That's why I built Roslyn Unused Code Remover—a lightweight CLI tool that automatically identifies and removes unused code from your C# projects using the Roslyn compiler platform.


✨ What It Does

This tool leverages the power of Roslyn analyzers to detect unused code elements in .cs files and provides a quick way to clean them up. It's especially helpful for:

  • Refactoring old codebases

  • Removing noise before code reviews

  • Keeping things lean and maintainable


⚙️ How It Works

Under the hood, the tool:

  1. Parses the solution or project using Roslyn APIs.

  2. Analyzes syntax trees and semantic models to find symbols (variables,function and classes(whose all methods have 0 reference).) that are declared but never referenced.

  3. Comments or flags them for your review which you can later search in folder using "// Remove unused code" .

It's like having an assistant that knows your code well enough to do spring cleaning for you.


🛠️ Getting Started

Clone the Repo

git clone https://github.com/Nayanaska/Roslyn-Unused-Code-Remover.git
cd Roslyn-Unused-Code-Remover

Build and Run Make sure you have the .NET SDK installed. Then:

dotnet build
dotnet run

Go to Program.cs file and put path of your solution, the path of log files to check all unused code references that is found and excel file where you want to list down all unused class references under variables solutionPath, LogFilePath and unusedClassesExcelPath and run using command,

dotnet run

The tool will analyze your code and comment and flag unused code blocks.

Code explanation

📌 Main Method – The Entry Point

static async Task Main(string[] args)

This is where the program starts.

MSBuildLocator.RegisterDefaults();

Registers MSBuild so Roslyn can load projects and solutions.

string solutionPath = @"Path/to/your/solution";

Sets the path to the .sln file you want to analyze.

 static readonly string unusedClassesExcelPath = @"path\to\save\unusedclassesexcelfile.xlsx";

Sets the path to the .xlsx file you want to save the location of all unused classes.

static readonly string LogFilePath = @"path/of/log/file.txt";

Sets the path to the .txt file you want to save logs in.

đźš« Ignored Folders

static readonly List<string> IgnoredFolders = new List<string>
{
    "Folder1",
    "folder2",
    "folder3"
};

Prevents cleaning test or sample code where unused items may be expected.

using StreamWriter logWriter = new(LogFilePath, append: false);
Console.SetOut(logWriter);
Console.SetError(logWriter);

Redirects all logs and errors to a file for easy review.

using var workspace = MSBuildWorkspace.Create();
var solution = await workspace.OpenSolutionAsync(solutionPath);

Creates a Roslyn workspace and loads your solution into it.

var unusedVariables = await FindUnusedVariables(solution);
await CommentOutUnusedVariables(unusedVariables);

Detects and comments out unused variables.

var unusedFunctions = await FindUnusedFunctions(solutionPath);
await CommentOutUnusedFunctions(unusedFunctions);

Same process for methods.

var unusedClasses = await FindUnusedClasses(solutionPath);
await CommentOutUnusedClasses(unusedClasses);

And for classes.

🔍 FindUnusedVariables

var variables = root.DescendantNodes()
    .OfType<VariableDeclaratorSyntax>()
    .Select(v => new { Symbol = semanticModel.GetDeclaredSymbol(v), SyntaxNode = v });

Finds all variable declarations and fetches their symbols.

var references = await SymbolFinder.FindReferencesAsync(variable.Symbol, solution);

Checks where each variable is used.

if (!references.Any(refs => refs.Locations.Any()))

If there are no references, it’s considered unused.

đź§  FindUnusedFunctions

var methods = root.DescendantNodes().OfType<MethodDeclarationSyntax>();

Extracts all method declarations.

var symbol = semanticModel.GetDeclaredSymbol(method);

Gets the Roslyn symbol for each method.

var refs = await SymbolFinder.FindReferencesAsync(symbol, solution);

Checks if the method is ever called. If not—💥 it's unused!

✂️ CommentOutUnusedVariables

var unusedItems = unusedVariables.GroupBy(item => item.filePath);

Groups unused variables by their file.

lines[item.lineNumber - 1] = "// Remove unused code" + lines[item.lineNumber - 1];

Comments out the variable declaration directly in the source file.

đź”§ CommentOutUnusedFunctions

var methodNode = root.DescendantNodes()
    .OfType<MethodDeclarationSyntax>()
    .FirstOrDefault(n => syntaxTree.GetLineSpan(n.Span).StartLinePosition.Line == item.lineNumber - 1);

Finds the method node based on its line number.

for (int i = startLine; i <= endLine; i++)
{
    updatedLines[i] = "// " + updatedLines[i];
}

Comments out the entire method.

đź§ą FindUnusedClasses

var classSymbol = semanticModel.GetDeclaredSymbol(classDecl);

Fetches the class symbol.

var classRefs = await SymbolFinder.FindReferencesAsync(classSymbol, solution);

Checks for references to the class itself.

foreach (var member in classSymbol.GetMembers())

Also checks if any member of the class (methods, properties, etc.) is used.

🪓 CommentOutUnusedClasses

var classNode = root.DescendantNodes()
    .OfType<ClassDeclarationSyntax>()
    .FirstOrDefault(n => ...);

Finds the class node.

updatedLines.Insert(startLine, "// Remove unused code");

Adds a comment before the class.

for (int i = startLine + 1; i <= endLine; i++)
{
    updatedLines[i] = "// " + updatedLines[i];
}

Comments out the entire class block.

🛠️ BuildSolution

ProcessStartInfo psi = new("dotnet", $"build \"{solutionPath}\"")

Runs dotnet build to ensure the solution is still valid.

return process.ExitCode == 0;

Returns true if build succeeded, false otherwise.

📤 SaveFailedChangesToExcel & SaveUnusedClassesToExcel These use EPPlus to export results to Excel.

using var package = new ExcelPackage();
var worksheet = package.Workbook.Worksheets.Add("Unused Classes");

Creates an Excel file and writes data like:

File path

Line number

Code snippet

Build status

đź’ˇ Why I Built It During my development work, I often found myself manually hunting for unused code. I realized this was both time-consuming and error-prone. While tools like ReSharper offer some of this functionality, I wanted a free, scriptable, and open-source solution I could use in CI pipelines or one-off cleanup tasks.

đź§Ş Limitations & Future Plans

Doesn't yet handle dynamically referenced code or reflection-heavy patterns.

Plans include:

Optional "preview mode"

VS Code integration

CI-friendly reporting output

🙌 Contributions Welcome If you're passionate about Roslyn or static code analysis, feel free to open issues or submit PRs. I'd love to collaborate with the community on making this tool smarter and more robust.

👉 Check out the repo

🔚 Final Thoughts Code cleanliness is more than a style preference—it's a productivity booster. With Roslyn Unused Code Remover, you can keep your C# projects sharp, focused, and free of digital clutter. Give it a try and let me know how it works for you!

0
Subscribe to my newsletter

Read articles from Nayana Agarwal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nayana Agarwal
Nayana Agarwal