Learn Roslyn Now: Part 6 Working with Workspaces

Special thanks to @JasonMalinowski for his help clarifying some of the subtleties of the workspace API. Until this point, we’ve simply been constructing syntax trees from strings. This approach works well when creating short samples, but often we’d like to work with entire solutions. Enter: Workspaces. Workspaces are the root node of a C# hierarchy that consists of a solution, child projects and child documents. A fundamental tenet within Roslyn is that most objects are immutable. This means we can’t hold on to a reference to a solution and expect it to be up-to-date forever. The moment a change is made, this solution will be out of date and a new, updated solution will have been created. Workspaces are our root node. Unlike solutions, projects and documents, they won’t become invalid and always contain a reference to the current, most up-to-date solution. There are four Workspace variants to consider:

Workspace

The abstract base class for all other workspaces. It’s a little disingenuous to claim that it’s a workspace variant, as you’ll never actually have an instance of it. Instead, this class serves as a sort of API around which actual workspace implementations can be created. It can be tempting to think of workspaces solely within the context of Visual Studio. After all, for most C# developers this is the only way we’ve dealt with solutions and projects. However, Workspace is meant to be agnostic as to the physical source of the files it represents. Individual implementations might store the files on the local filesystem, within a database, or even on a remote machine. One simply inherits from this class and overrides Workspace’s empty implementations as they see fit.

MSBuildWorkspace

A workspace that has been built to handle MSBuild solution (.sln) and project (.csproj, .vbproj) files. Unfortunately it cannot currently write to .sln files, which means we can’t use it to add projects or create new solutions.

The following example shows how we can iterate over all the documents in a solution:


string solutionPath = @"C:\Users\…\PathToSolution\MySolution.sln";
var msWorkspace = MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
{
Console.WriteLine(project.Name + "\t\t\t" + document.Name);
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

For more information see Learn Roslyn Now – E06 – MSBuildWorkspace.

AdhocWorkspace

A workspace that allows one to add solution and project files manually. One should note that the API for adding and removing solution items is different within AdhocWorkspace when compared to the other workspaces. Instead of calling TryApplyChanges(), methods for adding projects and documents are provided at the workspace level. This workspace is meant to be consumed by those who just need a quick and easy way to create a workspace and add projects and documents to it.


var workspace = new AdhocWorkspace();
string projName = "NewProject";
var projectId = ProjectId.CreateNewId();
var versionStamp = VersionStamp.Create();
var projectInfo = ProjectInfo.Create(projectId, versionStamp, projName, projName, LanguageNames.CSharp);
var newProject = workspace.AddProject(projectInfo);
var sourceText = SourceText.From("class A {}");
var newDocument = workspace.AddDocument(newProject.Id, "NewFile.cs", sourceText);
foreach (var project in workspace.CurrentSolution.Projects)
{
foreach (var document in project.Documents)
{
Console.WriteLine(project.Name + "\t\t\t" + document.Name);
}
}

view raw

gistfile1.cs

hosted with ❤ by GitHub

For more information see Learn Roslyn Now – E08 – AdhocWorkspace

VisualStudioWorkspace

The active workspace consumed within Visual Studio packages. As this workspace is tightly integrated with Visual Studio, it’s difficult to provide a small example on how to use this workspace. Steps:

  1. Create a new VSPackage.
  2. Add a reference to the Microsoft.VisualStudio.LanguageServices.dll. It’s now available on NuGet.
  3. Navigate to the <VSPackageName>Package.cs file (where <VSPackageName> is the name you chose for your solution.
  4. Find the Initalize() method.
  5. Place the following code within Initialize()


protected override void Initialize()
{
//Other stuff…
var componentModel = (IComponentModel)this.GetService(typeof(SComponentModel));
var workspace = componentModel.GetService<Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace>();
}
//Alternatively you can MEF import the workspace. MEF can be tricky if you're not familiar with it
//but here's how you'd import VisuaStudioWorkspace as a property.
[Import(typeof(Microsoft.VisualStudio.LanguageServices.VisualStudioWorkspace))]
public VisualStudioWorkspace myWorkspace { get; set; }

view raw

gistfile1.cs

hosted with ❤ by GitHub

When writing VSPackages, one of the most useful pieces of functionality exposed by the workspace is the WorkspaceChanged event. This event allows our VSPackage to respond to any changes made by the user or any other VSPackage. Naturally, the best way to familiarize oneself with workspaces is to use them. Roslyn’s immutability can impose a slight learning curve so we’ll be exploring how to modify documents and projects in future posts.

For more information see Learn Roslyn Now – E07 – Visual StudioWorkspace

10 thoughts on “Learn Roslyn Now: Part 6 Working with Workspaces

    1. I’m not sure what you mean. You can load solutions (.sln files) from disk via MSBuildWorkspace. Then you can traverse the projects and documents in that workspace if you’d like to. Or were you trying to do something else?

      1. You pretty much nailed it. I actually made some progress. I was having trouble getting an instance of any kind of workspace. Turns out I was missing some dependencies and the code tips weren’t telling me. I lean a bit too much on visual studio for help these days I guess. It’s been a pain trying to learn Roslyn on release day when there are 4 years of outdated and incomplete docs. Luckily I found this site, and it put me on the right path. Thanks for the quick response either way. 2:30am responses are legit.

  1. I’m in need of a little help. I have loaded a project and I’m checking the Documents property, but only the CS files are there. I can’t find any XML content files. I’m using MsBuildWorkspace to load the project. Can someone tell me where I can find the non-code files?

  2. I have some code analyzers that are configurable with a menu dialog. After the user sets some options I want all my analyzers to run again since the results will be different. I know how to read the configuration information in my dialog box from the Analyzer but I don’t know how to tell Visual Studio that it needs to rerun the analyzers. Can I raise the WorkspaceChanged event, (if so how) and will it do what I want or do I need to do something else? My code is in VB if that matters.

  3. I was able to load a MSBuild Workspace without having to install either the 2013 and 2014 MSBuild tools. Was dreading the 4 GB install.

Leave a comment