Accessor Uses Expected Field
ID |
csharp.accessor_uses_expected_field |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
CSharp |
Tags |
code_smell, copy-paste, field, suspicious-assignment |
Description
Reports a property accessor that never touches the field named after the property, while it does read
or write some other field of the same type. A field corresponds to a property when the two names match
ignoring case and a leading or m, so Total corresponds to total, _total and m_total.
The whole accessor body is examined, not just a one-statement one: a guard, a conversion or a
computation is exactly where a mistyped identifier survives review. Every field and this.field
reference in the body is collected, and the accessor is reported only when the corresponding field is
absent from that set. Fields of any access level count, since a protected or internal backing field
is as easy to name wrongly as a private one.
Three situations are left alone: a property with no correspondingly named field, which is a computed or
derived value; a property whose name corresponds to more than one field, such as when both y and _y
are declared, because no convention can say which one was meant; and an accessor that reads no field of
the type at all, which has nothing to weigh against the property name.
Rationale
This is the copy-paste defect of hand-written properties. A property is duplicated, the declaration is renamed, and the identifier inside the accessor is left behind. Everything still compiles and type-checks, because the wrong field has the right type.
Nothing points at the mistake at run time either. The property returns a real value from a real field, so one field silently becomes write-only and one property silently mirrors another. It surfaces much later as two values that refuse to disagree, or as a setter whose effect never appears anywhere — and the fix is a single identifier, unrelated to the time spent finding it.
public class Rectangle
{
private int width;
private int height;
private string label;
protected int depth;
protected int span;
public int Width => width; // OK
public int Height => width; // FLAW, height is the matching field
public int Depth
{
get { if (span < 0) return 0; return span; } // FLAW, the guard does not excuse the wrong field
}
public string Label
{
get { return this.label; } // OK, this-qualified is still the matching field
set { label = value; } // OK
}
public int Area => width * height; // OK, a computed property
public int Diagonal => width; // OK, no field named after Diagonal
}
Remediation
Read the accessor against the property name and use the field that belongs to it. Where the pattern is nothing but storage, an auto-implemented property removes the backing field from the source entirely and with it any chance of naming the wrong one:
public int Height { get; set; }
Where a backing field is needed — validation in the setter, lazy initialization, a field the constructor writes directly — keep the field but make the correspondence exact, so a mismatched identifier stands out while reading.