Gli accessori sono più che campi. Altri hanno già sottolineato alcune importanti differenze e ne aggiungerò un'altra.
Le proprietà prendono parte alle classi di interfaccia. Per esempio:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
Questa interfaccia può essere soddisfatta in diversi modi. Per esempio:
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 questa implementazione stiamo proteggendo sia la Person
classe da entrare in uno stato non valido, sia il chiamante da ottenere null dalla proprietà non assegnata.
Ma potremmo spingere ulteriormente il design. Ad esempio, l'interfaccia potrebbe non gestire il setter. È abbastanza legittimo affermare che i consumatori di IPerson
interfaccia sono interessati solo a ottenere la proprietà, non a impostarla:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
La precedente implementazione della Person
classe soddisfa questa interfaccia. Il fatto che consenta al chiamante di impostare anche le proprietà non ha senso dal punto di vista dei consumatori (che consumano IPerson
). Ulteriori funzionalità dell'implementazione concreta sono prese in considerazione, ad esempio, dal costruttore:
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 questo codice, il consumatore non è a conoscenza dei setter di proprietà, non è compito suo conoscerlo. Il consumatore ha solo bisogno di vincitori e ottiene vincitori dall'interfaccia, cioè dal contratto.
Un'altra implementazione completamente valida di IPerson
sarebbe una classe di persona immutabile e una fabbrica di persona corrispondente:
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 questo codice il consumatore di esempio non ha ancora una conoscenza del riempimento delle proprietà. Il consumatore si occupa solo di getter e l'implementazione concreta (e la logica aziendale dietro di essa, come test se il nome è vuoto) è lasciato alle classi specializzate - costruttori e fabbriche. Tutte queste operazioni sono assolutamente impossibili con i campi.