CA1051: Do not declare visible instance fields

In our codebase at work, I had to enable DotNet Code Analysis Rule CA1051 "Do not declare visible instance fields". To do that, I had to figure out a way of automatically converting thousands of instance fields to properties. This is how I did it.

DotNet Code Analysis Rule CA1051 "Do not declare visible instance fields" caters to the common misconception that visible instance fields are a bad thing to have. In fact, they are fine, as long as they are immutable, and only used by assemblies that we have control over; however, this rule prohibits all visible instance fields indiscriminately.

The only scenario under which visible instance fields are problematic is when building a library for third parties to use. In that and only that case, it is prudent to encapsulate instance fields via properties, so that our implementation can change without breaking binary compatibility with third-party code that uses it.

A Code Analysis rule that would have been beneficial under all circumstances would prohibit visible fields that are mutable. However, there exists no such Code Analysis Rule in DotNet. Instead, there are two related, but unsuitable rules:

  • CA1051 "Do not declare visible instance fields" which, as already mentioned, is unsuitable because it prohibits visible fields regardless of whether they are mutable or not, (also, it only prohibits instance fields, ignoring static fields,) and

  • CA2211 "Non-constant fields should not be visible" which is unsuitable because contrary to what its name suggests, it only checks static fields, not instance fields.

Due to the above reasons, if we want any kind of discipline in the way we declare fields, we have no option but to enable CA1051, which means that we cannot have any visible instance fields even if they are immutable.

This is a recurring phenomenon in the design of DotNet Code Analysis Rules: their intention seems to be to give us freedom to decide what coding style we want to enforce in our own code, (and they are certainly advertised as such,) but in fact they seem to be designed by Nazis who are promoting a specific coding style only, "take it or leave it".

So, I had to enable CA1051 "Do not declare visible instance fields" in our codebase at work.

To do that, I had to find a way to convert thousands of instance fields to properties.

First, I tried the way that any reasonable software engineer would think, and any decent Integrated Development Environment (IDE) would support: refactoring. Alas, neither Visual Studio 2022 nor 2026 are among the ranks of "decent" IDEs. Visual Studio is so poor that it offers no way of mass-converting fields to properties. It supports absolutely no refactorings that could be used for this purpose; it only offers two quick-fixes, which are useless:

  • Encapsulate field '{field}' (and use property).
  • Encapsulate field '{field}' (but still use field).

Neither of these two quick-fixes converts a field to a property; instead, they add a completely useless property, and they leave the field as is. Furthermore, unlike other quick-fixes, which can be applied on the entire document, or the entire project, or the entire solution, these quick fixes can be applied only on one field at a time. So, they are completely useless for my purposes.

This means that I was going to have to do it the old-fashioned way, with search and replace.

For that, it was obvious that I would have to use regular expressions.

So, the goal was to convert fields such as the following:

	public readonly Namespace.Type Name;
	protected readonly Namespace.Type Name;
	public readonly Namespace.Type<int> Name = Initializer;
	public readonly Namespace.Type<int, int> Name;

into properties, as follows:

	public Namespace.Type Name { get; }
	protected Namespace.Type Name { get; }
	public Namespace.Type<int> Name { get; } = Initializer;
	public Namespace.Type<int, int> Name;

Before even getting to that point, I had to first solve another problem:

Some colleague of mine had apparently picked up the habit of declaring multiple visible instance fields of the same type on the same line, like this:

	public readonly int X, Y, Z;

This is perfectly acceptable C#, but it was throwing a wrench in the gears for me, because this syntax is only valid when declaring fields; it is not valid when declaring properties. So, I had to find all those constructs in our code and break them up into multiple lines. I decided I would do this manually, but I needed a regular expression for finding them.

Here is what I came up with:

(\t+)(public|protected) readonly (([\w.<>?]|(, ))+) (\w+), (\w+)

Once I manually fixed all those, (luckily they were not too many,) it was time for the big search-and-replace that would convert all public readonly visible fields to properties. Here is the regular expression that I devised for that purpose:

Search for:

(?<indent>\t*)(?<access>public|protected) readonly (?<type>(?:[\w.<>?]|(?:, ))+) (?<name>\w+)(?:(?<init> = .*;)|;)

Replace with:

${indent}${access} ${type} ${name} { get; }${init}

A very useful tool for understanding regular expressions, and for working with regular expressions in general, is regex101.com. They support a number of different flavors of Regular Expressions, and I am pleased to see that they recently added a DotNet flavor.

The regular expressions I showed above make a few assumptions, such as the following:

  1. We use very consistent spacing everywhere.
  2. We use tabs for indentation.
  3. We do not use tabs anywhere else.
  4. In any place where we use a space, it is always a single space.
  5. We have a space after the coma that separates argument names in generic argument lists.

Also, the above regular expressions have a limitation: They will not find fields that contain any arrays within their type signature. For the most part this is fine, because nobody exposes public fields of array type. One very oddball case that was not handled by these regular expressions and I had to handle manually was a field of type System.Action<Type[]>. That's okay, fields of generic types that include array parameters are exceedingly rare.

Obligatory XKCD: https://xkcd.com/208/ "Regular Expressions"

Last updated on 2026-05-19 Tue 11:36:18 CEST