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


  • Posted: January 24, 2011 07:02


    Hi. Great work. It's a useful project for me :) I've always searched for something like that. best regards René
  • Posted: March 10, 2011 20:44

    Greg L

    Thanks for a pretty instructional project, I really like the clean and well thought out way you structured this seemingly simple app. Little touches like using the Parallel methods, etc, are also nice to see examples of. I'm not sure if you are aware, though, but your zip of the source code includes your Subversion folders, which obviously refer to your local SVN server. You would probably be better off, AFAIK, by doing an export of your project directory to somewhere else to get a clean copy of the files used, and zipping that up to distribute. Just a suggestion. All in all, thanks again for this real-life app, it's helpful for me as I still don't know much WPF, and I'm reading through Head First Design Patterns at the moment, and recognised a little of the design patterns in there in your code. :-) It's always helpful to see real examples of theoretical concepts.
    • Posted: March 10, 2011 22:04


      Thanks I'm glad you liked it.
  • Posted: June 2, 2011 04:19


    Nice! This works really well. Note that you have to download and install "Microsoft Expression Blend 3 SDK" to compile this project successfully.
  • Posted: June 2, 2011 04:21


    Really nice use of parallel expressions with TaskFactory. Thanks - I've never used this technique before, I'm sure it will speed things up.
  • Posted: June 11, 2013 23:09


    Why don't you put all your projects in github instead? I'd like to be able to change how the code lines are counted (comments etc) and contribute back my changes.
    • Posted: June 12, 2013 08:41

      Kelly Elias

      Great question, thought of this myself, the only problem is I don't know GitHub well enough yet, and I've been swamped with work so haven't had the time to get to know it deeply enough. But yes, I'm sure this is where I'll go eventually as there are many advantages.
    • Posted: June 16, 2013 10:31

      Kelly Elias

      I've upgraded the project to .NET 4.5, modified to use Caliburn.micro as well as a bunch of other little upgrades, and it's now hosted on GitHub.