Tally the Code Counting App

Tally is a C# WPF project that counts the lines in your C#.  Just point Tally at a folder and it will traverse it gathering every .PROJ file it comes across. It takes those .PROJ files and scans them to determine what files are included in your projects and then counts the lines of code in each of those.

Tally works for both C# and VB.NET projects, but there is no reason you couldn’t simply expand it to do other project types as I will show.


Before we go any further grab a copy of the code:

Download the code on GitHub

The Interface

Tally looks like this:

Tally C# code counting application

You can see it’s divided into three main parts:

  • Folder – This is on the top. It’s the folder you choose to scan to look for projects.
  • Projects – The list of projects found with the number of lines of code found in each.
  • Files – When a project is selected this part shows the files in that project and the number of lines of code.

To run Tally click on the File Menu and select Tally Folder. Then pick the folder and it will scan and return the results.

Project Layout

Tally is a simple WPF MVVM pattern, with only a single View and single ViewModel.

As you would expect the View just basically calls the ViewModel. The ViewModel calls a Processor class that does all the counting and returns the results to the ViewModel. Once the ViewModel gets the results it updates and thanks to the magic of dependency properties and observable collections the changes of the ViewModel are reflected on the View.


The code here is all dependency properties and collections, the only main part to note is how the Processor gets called to do the work:

public void Process()

Processor processor = new Processor();
CancellationToken cancelToken = new CancellationToken();
StateBag bag = new StateBag {TargetFolder = TargetFolder};

Task.Factory.StartNew((t) => processor.Process(bag), cancelToken)
.ContinueWith((t) =>
TotalLineCount = bag.TotalLineCount;
TotalFileCount = bag.TotalFileCount;
TotalFolderCount = bag.TotalFolderCount;
TotalProjectCount = bag.TotalProjectCount;

foreach (Project project in bag.Projects.OrderByDescending(w=>w.TotalLineCount))
project.Files = new ObservableCollection(project.Files.OrderByDescending(w => w.LineCount));

}, TaskScheduler.FromCurrentSynchronizationContext());

As you can see the work is offloaded to a different thread, this is just to keep the interface responsive. Tally currently doesn’t display any sort of working animation but since this is threaded it easily could for long operations.

This uses a StateBag class that is just a collection of stuff to return when the processor is done doing it’s work. That bag is then used on the ContinueWith to update the ViewModel on the UI thread. This is needed as the DependencyObjects cannot be accessed from a different thread.

Doing the Counting

The Processor class is where all the real action happens.

As we just saw above the ViewModel calls Processor.Process. The Process method saves the state object that was passed in, and then calls the ProcessFolder method giving it the target folder that was given to us by the ViewModel.

public void Process(StateBag b)
bag = b;


The ProcessFolder method scans the folder for .PROJ files and records them. Then calls the ProcessProject method for each .PROJ file it encounters.
This method uses recursion, calling its self again for each directory found in the one it was passed. Each call is done using the Parallel.ForEach construct so the work can be spread across multiple processors to speed things up.
Notice this only looks for .CSPROJ and .VBPROJ files? If you want another type of project file to be scanned you can modify this code to make it happen.

private void ProcessFolder(string path)
IEnumerable proj = Directory.EnumerateFiles(path);

foreach (string s in proj)
string ext = Path.GetExtension(s).ToUpper();

//only support c# and vb atm.
if (ext != ".CSPROJ" && ext != ".VBPROJ") continue;


IEnumerable directories = Directory.EnumerateDirectories(path);

Parallel.ForEach(directories, ProcessFolder);

The ProcessProject method scans the project file passed to it for files. Each file found is then sent to a ProcessFile method. The ProcessProject method also records the name of the application as listed in the .PROJ file.

private void ProcessProject(string projectPath)
string basePath = Path.GetDirectoryName(projectPath);

Project project = new Project {Path = projectPath};

string data = File.ReadAllText(projectPath);

//yes this works in Visual Studio 2010, they have an old year in the namespace
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
XDocument doc = XDocument.Parse(data);

XElement ele = doc.Root.Element(ns + "PropertyGroup").Element(ns + "AssemblyName");

if (ele != null)
project.Name = ele.Value;

var pages = doc.Root.Elements(ns + "ItemGroup").Elements(ns + "Page");

Parallel.ForEach(pages, (p) =>
string fullPath = Path.Combine(basePath, p.Attribute("Include").Value);
ProcessFile(fullPath, project);

var files = doc.Root.Elements(ns + "ItemGroup").Elements(ns + "Compile");

foreach (var f in files)
string fullPath = Path.Combine(basePath, f.Attribute("Include").Value);
ProcessFile(fullPath, project);

Finally the ProcessFile method goes through the file and counts the lines of code present, it also counts how many Signifigant Lines of code there are. Signifigant Lines are lines that are not curly braces.

private void ProcessFile(string path, Project project)
if (!File.Exists(path)) return;

ProjectFile f = new ProjectFile {Path = path};
f.Name = Path.GetFileNameWithoutExtension(path);
f.Extension = Path.GetExtension(path).ToUpper();


string[] lines = File.ReadAllLines(path);

foreach (string line in lines)
string l = line.Replace("t", "").Replace("n", "").Replace("r", "").Trim();

if (String.IsNullOrEmpty(l)) continue;


//Ignore brackets for signifigant count
if (l == "{" || l == "}") continue;

bag.TotalLineCount += f.LineCount;
project.TotalLineCount += f.LineCount;

That it. There really isn’t much to counting the lines of code in a project thanks to recursion and dependency properties.

Categories:   Code


Sorry, comments are closed for this item.