
Скрытые поля C# недоступны за пределами класса – ясно как дважды два. Поэтому следующий код не должен работать.
public class Example
{
 private string _someValue;
 public void DoSomething(Example otherObject)
 {
  _someValue = otherObject._someValue; // What!? You can't access a private variable from another object! Can you?
 }
}
Но, как ни странно, он работает.
Оказывается, к переменной Private можно получить доступ из другого объекта того же типа. И, если задуматься, это вполне логично. Зачем нужны переменные Private? Для инкапсуляции и управления состоянием объекта.
Представьте, что нам нужно создать контейнер с ограниченной емкостью – коробку конфет. Мы могли бы сделать это так:
public class Chocolate
{
 public string Name { get; set; }
}
public class ChocolateBox
{
 public List<Chocolate> Chocolates { get; set; }
 public bool IsValid()
 {
   return Chocolates.Count > -1 && Chocolates.Count < 11;
 }
}
Догадайтесь, в чем проблема
Несмотря на то, что у нас есть метод IsValid, в коробку можно добавить больше, чем максимальное количество конфет (хотя, думаю, заказчик был бы не против). Чтобы решить эту проблему, мы можем инкапсулировать коробку и добавить методы для соблюдения необходимых ограничений. К тому же это позволит нам избавиться от метода IsValid.
public class Chocolate
 {
  public string Name { get; set; }
 }
public class ChocolateBox
{
 private List<Chocolate> _chocolates;
 public void AddChocolate(Chocolate chocolate)
 {
  if(_chocolates == null) _chocolates = new List<Chocolate>();
  if(_chocolates.Count > 10) throw new Exception("No more space");
  _chocolates.Add(chocolate);
 }
 // Other methods removed for brevity
}
Но здесь есть одно условие.
Разработчики, создающие другие части системы, могут не догадываться, как работает ваша «коробка». В таком случае на помощь приходит модульное или интеграционное тестирование. Чтобы избежать любых недоразумений с кодом, нужно просто сделать неявное явным.
Но если разработчик знаком с классом, ему можно доверить изменение внутреннего состояния – в том числе для других классов того же типа.
Уверен, что из этой особенности можно извлечь массу плюсов, но хочу выделить лишь один, который оказался для меня особенно полезным.
Доступ к скрытым переменным при создании объектов-значений
Объекты-значения – это один из шаблонов проблемно-ориентированного программирования, который также можно использовать за пределами этой модели. Отличительная черта таких объектов в том, что их равенство основывается не на идентификаторе, а на значении.
Простой пример – строка. Две строки с одними и теми же символами в одинаковом порядке считаются равнозначными. Аналогично можно сравнивать телефонные номера из системы CRM, если, конечно, вы не пишете ПО для оператора телефонной связи – в таком случае телефонный номер может выступать идентификатором клиента и, следовательно, не быть объектом-значением.
В большинстве случаев объекты-значения неизменяемы. В том числе это касается строк: «изменяя» строку, вы, по сути, создаете новый объект. Чем полезна такая неизменяемость? Мне приходит на ум 4 причины:
- Объекты намного легче тестировать.
- Объекты легче распараллелить.
- Можно быть уверенным, что они всегда в действительном состоянии.
- Не нужно отслеживать много идентификаторов в приложении (пожалуй, основная причина).
Пример объекта-значения
В качестве примера давайте смоделируем контейнер для жидкости или, проще говоря, чашку с максимальной емкостью в 1 литр. При этом мы можем перелить содержимое из одной чашки в другую (с точки зрения кода для этого понадобится еще один экземпляр чашки). Жидкость, превышающая максимальный объем, считается утерянной. Если же две чашки содержат одинаковый объём жидкости, они равны.
Начнем с тестирования. Чтобы чашка была неизменяемой, передадим текущий объем жидкости в конструктор. Для начала проверим, можем ли мы создать чашку, наполненную жидкостью.
[Test]
public void Constructor_ValidVolume_ObjectIsNotNull()
{
 var cup = new Cup(0.5m);
 Assert.NotNull(cup);
}
Теперь удостоверимся, что чашка не может содержать недопустимый объем – более одного или менее нуля литров.
[TestCase(-0.1)]
[TestCase(1.1)]
public void Constructor_InvalidVolumes_ThrowsException(decimal invalidVolume)
{
 ArgumentOutOfRangeException expected = new ArgumentOutOfRangeException("volume");
 ArgumentOutOfRangeException actual = null;
 try
 {
  var cup = new Cup(invalidVolume);
 }
 catch (ArgumentOutOfRangeException ex)
 {
  actual = ex;
 }
 Assert.AreEqual(expected.ParamName, actual.ParamName);
}
А теперь самое интересное. Как сделать, чтобы чашки были одинаковыми? Для этого нам нужен доступ к скрытым переменным.
[Test]
public void Equals_TwoEqualCups_AreEqual()
{
    var cup1 = new Cup(0.5m);
    var cup2 = new Cup(0.5m);
    Assert.AreEqual(cup1, cup2);
}
На досуге можете потренироваться с переливанием жидкости. Впрочем, возникает интересный вопрос: как перелить жидкость из чашки, если она неизменяема? :)
Вывод
Объекты-значения – полезный концепт проблемно-ориентированного программирования. При переопределении метода equals вы можете воспользоваться преимуществами этой особенности в C#, чтобы получить доступ к скрытым переменным однотипных классов.
Поделиться с друзьями
              Комментарии (6)
- Oxoron23.06.2016 16:42- Если не ошибаюсь, похожий пример доступа к private полям видел у Троелсена. 
 При этом есть немало других способов доступа к приватным полям: GetMemberInfo(), nested classes, ExpressionTrees, плюс совсем уж экзотика, завязанная на выравнивание полей в структурах.
 - lair23.06.2016 16:47+2- Но, как ни странно, он работает. 
 
 Странно, серьезно? А что странного в том, что вы получили доступ к private-филду, находясь внутри класса, его объявившего? - impwx23.06.2016 17:02- В некоторых языках (например Scala) есть ограничение видимости конкретным объектом, а не классом. Хотя согласен, что классовое ограничение встречается гораздо чаще. 
 
 
           
 
MonkAlex
Где связь первой и второй половиной? ЯННП Т_Т
whitedemon9
Тоже сначала не понял о чем идет речь, пока не перечитал самое начало:
Оказывается, к переменной Private можно получить доступ из другого объекта ТОГО ЖЕ типа.