Accessors are more than fields. Others have already pointed out several important differences, and I'm going to add one more.
Properties take part in interface classes. For example:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
This interface can be satisfied in several ways. For example:
class Person: IPerson
{
private string _name;
public string FirstName
{
get
{
return _name ?? string.Empty;
}
set
{
if (value == null)
throw new System.ArgumentNullException("value");
_name = value;
}
}
...
}
In this implementation we are protecting both the Person
class from getting into an invalid state, as well as the caller from getting null out from the unassigned property.
But we could push the design even further. For example, interface might not deal with the setter. It is quite legitimate to say that consumers of IPerson
interface are only interested in getting the property, not in setting it:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
Previous implementation of the Person
class satisfies this interface. The fact that it lets the caller also set the properties is meaningless from the point of view of consumers (who consume IPerson
). Additional functionality of the concrete implementation is taken into consideration by, for example, builder:
class PersonBuilder: IPersonBuilder
{
IPerson BuildPerson(IContext context)
{
Person person = new Person();
person.FirstName = context.GetFirstName();
person.LastName = context.GetLastName();
return person;
}
}
...
void Consumer(IPersonBuilder builder, IContext context)
{
IPerson person = builder.BuildPerson(context);
Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
In this code, consumer doesn't know about property setters - it is not his business to know about it. Consumer only needs getters, and he gets getters from the interface, i.e. from the contract.
Another completely valid implementation of IPerson
would be an immutable person class and a corresponding person factory:
class Person: IPerson
{
public Person(string firstName, string lastName)
{
if (string.IsNullOrEmpty(firstName) || string.IsNullOrEmpty(lastName))
throw new System.ArgumentException();
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstName { get; private set; }
public string LastName { get; private set; }
}
...
class PersonFactory: IPersonFactory
{
public IPerson CreatePerson(string firstName, string lastName)
{
return new Person(firstName, lastName);
}
}
...
void Consumer(IPersonFactory factory)
{
IPerson person = factory.CreatePerson("John", "Doe");
Console.WriteLine("{0} {1}", person.FirstName, person.LastName);
}
In this code sample consumer once again has no knowledge of filling the properties. Consumer only deals with getters and concrete implementation (and business logic behind it, like testing if name is empty) is left to the specialized classes - builders and factories. All these operations are utterly impossible with fields.