Скрыть и найти

Еще один интересный вопрос со StackOverflow. Это место просто кладезь с темами для статей. Рассмотрим следующий пример:

 class B
{
  public int X() { return 123; }
}
class D : B
{
  new protected int X() { return 456; }
}
class E : D
{
  public int Y() { return X(); } // возвратит 456
}
class P
{
  public static void Main()
  {
     D d = new D();
     Console.WriteLine(d.X());
  }
}

Существует два возможных сценария. Мы можем рассматривать X как B.X и компилировать этот код успешно, либо мы можем рассматривать X, как D.X и выдать ошибку «вы не можете обратиться к защищенному методу класса D из класса Program».

[Дополнение: По просьбе, которая прозвучала в комментариях, я более подробно объясню эту часть. Спасибо за отличные вопросы.]

Мы поступаем первым способом. Для вычисления набора возможных методов при поиске имен спецификация говорит следующее: «набор состоит из всех доступных членов с именем N в T, включая унаследованные члены». Но D.X недоступен снаружи класса D, поскольку является защищенным членом. Поэтому D.X не включается в этот набор.

Затем в спецификации сказано: «члены, скрытые другими членами удаляются из этого набора». Скрывается ли B.X чем-либо? Кажется, что он D.X его скрывает. Давайте проверим. В спецификации сказано «Объявление нового члена скрывает унаследованный член только в области видимости нового члена». Объявление D.X скрывает B.X внутри своей области видимости: в теле класса D и его наследниках. Поскольку P не является ни тем, ни другим, D.X не скрывает B.X, поэтому B.X является видимым и именно он будет выбран алгоритмом разрешения.

Внутри E, D.X является доступным и скрывает B.X, поэтому D.X включается в набор для перегрузки, а B.X – нет.

Какова причина такого поведения? Не противоречит ли это поведение нашему правилу, которое гласит, что методы в более производных классах являются более предпочтительными, нежели методы в базовых классах?

Нет, не противоречит. Нужно помнить о том, что это правило призвано смягчить проблему хрупких базовых классов (brittle base class problem). Что оно и делает! Давайте рассмотрим следующий сценарий:

FooCorp создает Foo.DLL:

 public class Foo
{ 
  public object Blah() { ... } 
} 

BarCorp создает Bar.DLL:

 public class Bar : Foo
{ 
// Разная ерунда, которая не имеет никакого отношения к Blah
}

ABCCorp создает ABC.EXE:

 public class ABC
{ 
static void Main() 
  { 
    Console.WriteLine((new Bar()).Blah());  
  } 
}

Теперь BarCorp говорит: «А знаете что, наш внутренний код, благодаря знаниям всех производных классов, может гарантировать, что метод Blah будет всегда возвращать строку. Давайте воспользуемся преимуществом этих знаний в нашем внутреннем коде»:

 public class Bar : Foo
{ 
  internal new string Blah() 
  { 
    object r = base.Blah(); 
    Debug.Assert(r is string); 
    return (string)r; 
  } 
}

ABCCorp получит новую версию Bar.DLL с огромным количеством исправлений. Должен ли перестать компилироваться их код только потому у них есть вызов метода Blah, внутреннего метода класса Bar? Конечно же, нет. Это было бы просто ужасно. Это изменение является внутренней деталью реализации, которая не должна быть видима снаружи Bar.DLL. Тот факт, что скрытые методы игнорируются вне области своей видимости, означает, что они могут безопасно использоваться для деталей внутренней реализации, не нарушая работы внешних пользователей.

(Эрик в Осло на NDC; это сообщение записано заранее)

Оригинал статьи