Os acessadores são mais do que campos. Outros já apontaram várias diferenças importantes, e vou acrescentar mais uma.
Propriedades participam de classes de interface. Por exemplo:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
Essa interface pode ser satisfeita de várias maneiras. Por exemplo:
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;
}
}
...
}
Nesta implementação, estamos protegendo a Person
classe de entrar em um estado inválido, bem como o chamador de sair nulo da propriedade não atribuída.
Mas poderíamos levar o design ainda mais longe. Por exemplo, a interface pode não lidar com o configurador. É bastante legítimo dizer que os consumidores de IPerson
interface estão interessados apenas em obter a propriedade, não em defini-la:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
A implementação anterior da Person
classe satisfaz essa interface. O fato de permitir que o chamador também defina as propriedades não faz sentido do ponto de vista dos consumidores (que consomem IPerson
). Funcionalidades adicionais da implementação concreta são levadas em consideração por, por exemplo, o construtor:
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);
}
Nesse código, o consumidor não conhece os criadores de propriedades - não é da sua conta saber sobre isso. O consumidor precisa apenas de getters, e ele recebe da interface, ou seja, do contrato.
Outra implementação completamente válida IPerson
seria uma classe de pessoa imutável e uma fábrica de pessoa correspondente:
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);
}
Neste código de amostra, o consumidor mais uma vez não tem conhecimento de preencher as propriedades. O consumidor lida apenas com getters e a implementação concreta (e a lógica de negócios por trás dele, como testar se o nome está vazio) é deixada para as classes especializadas - construtores e fábricas. Todas essas operações são totalmente impossíveis com campos.