BRO1303: Private field names should be camelCase¶
Private fields are named count, or _count if you configure that style. Replaces StyleCop SA1306 and
SA1309 for private fields.
Example¶
Before¶
public class Account
{
private decimal _balance;
private readonly string Owner;
public Account(string owner, decimal balance)
{
Owner = owner;
_balance = balance;
}
public string Describe() => Owner + ": " + _balance;
}
After¶
public class Account
{
private decimal balance;
private readonly string owner;
public Account(string owner, decimal balance)
{
this.owner = owner;
this.balance = balance;
}
public string Describe() => owner + ": " + balance;
}
Why¶
One naming style for private fields makes them recognizable at a glance. The default, count, is StyleCop's; many
.NET codebases (including the .NET runtime) use _count instead, which keeps fields apart from parameters without
this.. Pick one with .editorconfig.
Configuration¶
[*.cs]
# 'camelCase' (default): count. '_camelCase': _count.
stylebro_private_field_naming = _camelCase
With _camelCase, count, Count and __count all become _count. The style covers private static fields that
aren't readonly too (private static int _instances;), so it is StyleCop's SX1309 and SX1309S together.
Those static fields can have a style of their own (StyleCop's SX1309S alone: _ on static fields only):
[*.cs]
# Private static fields that aren't readonly or const. Not set: a naming rule for private fields that requires just
# 'static' decides (one with a style StyleBro doesn't write, like an 's_' prefix, leaves the names), else
# stylebro_private_field_naming's style.
stylebro_private_mutable_static_field_naming = _camelCase
private int count; stays, private static int instances; becomes _instances. Constants and static readonly
fields are BRO1306's (stylebro_private_static_field_naming).
Without stylebro_private_field_naming, the SDK's naming rules decide: a dotnet_naming_rule whose symbols cover
private fields (kind field or *, accessibility private or *, no required modifiers) with a camel-case style picks
camelCase (no prefix) or _camelCase (required_prefix = _); the lowest priority wins. With neither, the default is
camelCase. The preset doesn't set the key, so a naming rule in your .editorconfig takes effect;
stylebro-migrate writes it from your StyleCop setup. stylebro-migrate init counts your private fields and writes
the key when no enforced naming rule decides: a rule nothing enforces (IDE1006 at none or silent) leaves the style to
your code. Naming rules for public, internal and protected fields count only when they are enforced (see
BRO1306).
[*.cs]
dotnet_naming_rule.private_fields.symbols = private_fields
dotnet_naming_rule.private_fields.style = underscore_camel
dotnet_naming_rule.private_fields.severity = warning
dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
dotnet_naming_style.underscore_camel.required_prefix = _
dotnet_naming_style.underscore_camel.capitalization = camel_case
stylebro_rename_public_api = true also renames names other assemblies see (default false, see Not reported; the owner's decision in decisions.md).
How the fix works¶
The field is renamed everywhere, in every part of a partial type. Where a local or parameter with the new name would
hide the field, the reference is qualified: this.balance = balance, or Account.instances for a static field. The
other way round, a this. that only stepped around a parameter with the field's old name is dropped once the new name
no longer clashes (this.pet = pet -> _pet = pet), unless dotnet_style_qualification_for_field = true. A
field of a nested type is renamed even when an enclosing type has a member with the new name (the field hides it),
unless the nested type uses that member by name. The
new name drops leading underscores and lowers the leading capitals, like BRO1301.
Checked: private fields, instance or static, readonly or not. Constants and static readonly fields are PascalCase
(StyleCop's SA1303 and SA1311), and internal and public fields are BRO1306's.
Protected (and private protected) fields follow StyleCop's defaults: one that isn't readonly is camelCase (SA1306),
and a readonly one's casing isn't checked (StyleCop leaves it unchecked: SA1306 skips it and SA1304 defers to SA1307,
which checks public and internal fields only). A leading underscore (SA1309) is removed in the camelCase style and
kept in the _camelCase style, which doesn't add one to protected fields either.
Not reported¶
-
Where IDE1006 is suppressed: after
#pragma warning disable IDE1006(up to itsrestore), or with[SuppressMessage("Style", "IDE1006")]on the declaration, its member or type, or a global one targeting them. Code that opts out of the SDK's naming rules (a file copied from another project) keeps its names. The fix only renames when that can't change what the code does: -
Public API (unless
stylebro_rename_public_api = true): Protected fields that other assemblies see, that is public, protected or protected internal in types that are visible too. Renaming them breaks code outside the solution. Internal code is renamed, also withInternalsVisibleTo. - A name that
nameof(...)produces: the rename would change the string (Ocelot built an HTTP header name withnameof(X_RateLimit_Limit), and a test read a private field withGetField(nameof(_components))). Looked for in the member for variables and parameters, in the type for fields; the fix also looks in the rest of the solution and leaves the warning when it finds one. - The type or a base type already has a member with the new name.
- Two fields would get the same new name (
_countandCount). - The field has attributes, or the type is
[Serializable]or has a serializer attribute ([JsonObject],[DataContract],[MessagePackObject],[ProtoContract], ...). Serializers can depend on field names. - Another member's name contains the field's name, like
ShouldSerialize_countor_countSpecified: conventions that pair members by name (Json.NET, XML serialization, data binding) would break. - The old name appears in a string in the type, alone or inside a longer string: reflection (
GetField("_count")),[DebuggerDisplay("{_count}")]. - The old name appears in code excluded by
#if, which can't be renamed reliably. - The field names an anonymous type member or tuple element by inference (
new { _count }). - One-letter prefixes (
m_count,s_count): StyleCop's SA1308, ands_is the .NET runtime's style for static fields. - Names without a camelCase form: only underscores, or a keyword (
Class). - Fields in a class whose name ends in
NativeMethods(or a type nested in one), like StyleCop: they hold Win32 and COM names. - Interop names: SCREAMING_CASE constants and
static readonlyfields (S_OK), COM GUID fields (IID_...), and the fields of interop-shaped types ([StructLayout],[ComImport],[DllImport]methods); see BRO1308. _fieldandFieldin the camelCase style: in C# 14fieldinside a property accessor is the property's backing field, soget => field;would stop reading this field (CS9258)._fieldis fine in the_camelCasestyle.- A part of the type is generated, like a Razor component's code-behind class (
List.razor.cs): the markup uses the fields, and the generated half can't be renamed (eShopOnWeb's admin pages stopped compiling before).
Reported but not fixed¶
If dotnet format leaves a finding, StyleBro kept it on purpose. The fix looks at the whole solution, other
projects included, which the analyzer can't see, and leaves a change that would break something there: the name in a
string or a nameof (reflection, serialization), a member that would hide it, a use in generated code. It offers no
code action for such a finding, and stylebro-migrate format lists each one with its reason
(Findings kept on purpose); its --verify-no-changes doesn't fail on
them. Change it by hand only after checking those uses, or suppress it or record it in the baseline.
A field whose old name is a string where code reaches a member by name, anywhere in the solution, keeps its name,
and the warning stays. That's reflection from another project, like a test reading a private field
(GetField("_blockedUntil", ...)): the rename would still compile but fail at run time. Counted: an argument of
GetField/GetProperty/GetMember/GetMethod/GetEvent and their GetRuntime*/GetDeclared* forms, Type.GetType,
InvokeMember, Activator.CreateInstance, any method whose name contains Field, Property, Member, Method or Event
(Expression.Field, AccessTools.Field, EF Core's HasField, a test's GetPrivateField), [MemberData],
[DynamicData], [TestCaseSource] and [ValueSource], and any attribute's Name = "...". Any other string (an
exception's parameter name, a message, a JSON key) doesn't keep it (owner's decision, docs/decisions.md: in Orleans
common words like "options" kept 692 renames). Rename such a field by hand
and update the string, or suppress the warning for it. (Found in Polly, whose tests read two private fields this
way.) Strings in source generators' output count too: Mapperly maps private fields through
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "intValue")], and a renamed field broke its mapping.
A field that generated code uses (a source generator's output, a .g.cs file) keeps its name too: the tool would
write the old name again.
Compared with StyleCop¶
Same results as StyleCop for private and protected fields, except for the skipped cases above. SA1309 also covers constants and public fields, which are BRO1306's.