A screenshot of Visual Studio showing the source generated file that supports a .resx file.

Committing generated files into source control is a shame. Besides bloating your source control database, it creates more opportunity for merge conflicts. And if the computer generated the file in the first place, why can it not regenerate it later instead of “checking it in”?

Thanks to Sam Harwell‘s work, now there is one more kind of generated file you can permanently remove from source control. The code-behind for your .resx files can be replaced with a roslyn source generator! And with this migration comes a few new features.

How to migrate

Below is a step-by-step guide. But if you want to just jump to an example migration, one of my own migrations: Migrate to resx source generator. If you have questions about it, please comment below rather than directly on the github commit (which has terrible threading UX).

Add a NuGet feed to your sources list

The source generator is not (yet) available on nuget.org, so you’ll have to add the .NET team’s feed for .NET 8 to your package sources. The feed you need is https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json. If you’re using a nuget.config file (and you hopefully are!), you can just copy and paste this line into it:

<add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />

Add a package reference to the source generator

The NuGet package you need to reference to get the new source generator is Microsoft.CodeAnalysis.ResxSourceGenerator. At the moment it is a prerelease, and I’m using version 3.3.5-beta1.23223.2.

You can add the package reference to just the individual projects that contain .resx files, or you can add it to all projects in your repo, as in my experience it is harmless to add everywhere. A snippet like this will do in most cases, which you can put in a project file or in a Directory.Build.props file at the root of your repo:

<PackageReference Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="3.3.5-beta1.23223.2" PrivateAssets="all"/>

Delete your code-behinds

Now go through and delete all the *.designer.cs files in your project(s) that are code-behinds for .resx files. Take care that there may be files matching this name pattern that may be generated by some other parent file, and you can’t (yet) delete those.

Deleting the code-behinds only cleans up half of the mess. The rest of the mess is the metadata in the project file for each of those .resx files. Go find each .resx file that appears in your project files and delete the metadata:

<EmbeddedResource Update="Strings.resx">
  <Generator>ResXFileCodeGenerator</Generator>
  <LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>

For SDK-style projects, this leaves nothing but an empty Update element, so you can delete the whole element.

Test it

Presumably you are already using your code-behind by referencing strings it declares. You can build your project to verify that everything still builds without your code-behind.

You can also explore by finding some code that references one of your string resources and execute Go to Definition on the string reference, which will open the dynamically generated file so you can see it resembles the original.

Extra features

Earlier I alluded to new features. This source generator can add value to that otherwise boring generated file through any combination of several new features. Each feature is activated via metadata on the original .resx msbuild item.

EmitFormatMethods

This one is my favorite. Activate it with an element like this added to your SDK-style project file:

<EmbeddedResource Update="*.resx" EmitFormatMethods="true" />

With this metadata added, you can replace long syntax to format your unformatted strings with a much shorter syntax:

string.Format(CultureInfo.CurrentCulture, Strings.InvalidXAddress, "sapling");
Strings.FormatInvalidXAddress("sapling");

This is more convenient, but the best part is it’s far more likely to be correct. The problem was there was no build-time enforcement that you provided the right number (or position) of formatting arguments to your string resource. And if you ever change the string and the number or relative positions of the arguments, all references to that string are broken without warning.

With the generated Format* method however you get exactly as many parameters as you have formatting arguments, so you’ll get compile breaks if you don’t specify the right number. But it gets better.

You can now name your formatting arguments and these are used as parameter names in your Format method. First, change your .resx string definition to name the formatting argument:

This {0} address does to conform to expected requirements.
This {addressType} address does to conform to expected requirements.

Now, doing this breaks any existing use of string.Format on this string, so be careful to update all uses of this string to use the Format* method instead:

Strings.FormatInvalidXAddress(addressType: "sapling");

Note you don’t have to use the named argument syntax, but I do it above to drive home the point that the parameters are now named, so as you write code that consumes the string, C# completions tell you what value is expected.

Other features

There are several other features in this source generator, but there isn’t official documentation for it yet as it’s quite new. You can find them yourself and experiment with them to decide which ones are appropriate for your project.