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 beneficial 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. Of course, I used 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 this 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 very 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 never 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-15 Fri 14:17:06 CEST