Accessors - это больше, чем поля. Другие уже указали на несколько важных отличий, и я собираюсь добавить еще одно.
Свойства принимают участие в интерфейсных классах. Например:
interface IPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
Этот интерфейс может быть удовлетворен несколькими способами. Например:
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;
}
}
...
}
В этой реализации мы защищаем как класс Person
от попадания в недопустимое состояние, так и вызывающую функцию от получения нулевого значения из неназначенного свойства.
Но мы могли бы продвинуть дизайн еще дальше. Например, интерфейс может не работать с сеттером. Вполне законно сказать, что потребители интерфейса IPerson
заинтересованы только в получении свойства, а не в его настройке:
interface IPerson
{
string FirstName { get; }
string LastName { get; }
}
Предыдущая реализация класса Person
удовлетворяет этому интерфейсу. Тот факт, что это позволяет вызывающей стороне также устанавливать свойства, не имеет смысла с точки зрения потребителей (которые потребляют IPerson
). Дополнительная функциональность конкретной реализации учитывается, например, застройщиком:
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);
}
В этом коде потребитель не знает об установщиках свойств - это не его дело знать об этом. Потребителю нужны только геттеры, и он получает геттеры из интерфейса, то есть из контракта.
Другой полностью допустимой реализацией IPerson
будет класс неизменяемого человека и фабрика соответствующего человека:
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);
}
В этом примере кода потребитель еще раз не знает, как заполнить свойства. Потребитель имеет дело только с геттерами, а конкретная реализация (и бизнес-логика, лежащая в ее основе, например, проверка, если имя пусто) оставлена на усмотрение специализированных классов - сборщиков и фабрик. Все эти операции с полями совершенно невозможны.