ОБНОВЛЕНИЕ: Этот вопрос был темой моего блога в январе 2010 года. Спасибо за отличный вопрос! См:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/14/why-cant-i-access-a-protected-member-from-a-derived-class-part-six/
У кого-нибудь есть пример
проблема, которая будет вызвана
позволяя MyDerived доступ к базе
защищенные члены через переменную
типа YourDerived или Base, но делает
не существует уже при доступе к ним
через переменную типа MyDerived
или MySuperDerived?
Меня довольно смущает ваш вопрос, но я хочу дать ему шанс.
Если я правильно понимаю, ваш вопрос состоит из двух частей. Во-первых, какое смягчение атаки оправдывает ограничение на вызов защищенных методов через менее производный тип? Во-вторых, почему одно и то же обоснование не мотивирует предотвращение вызовов к защищенным методам для типов с одинаковым или большим производным?
Первая часть проста:
// Good.dll:
public abstract class BankAccount
{
abstract protected void DoTransfer(BankAccount destinationAccount, User authorizedUser, decimal amount);
}
public abstract class SecureBankAccount : BankAccount
{
protected readonly int accountNumber;
public SecureBankAccount(int accountNumber)
{
this.accountNumber = accountNumber;
}
public void Transfer(BankAccount destinationAccount, User user, decimal amount)
{
if (!Authorized(user, accountNumber)) throw something;
this.DoTransfer(destinationAccount, user, amount);
}
}
public sealed class SwissBankAccount : SecureBankAccount
{
public SwissBankAccount(int accountNumber) : base(accountNumber) {}
override protected void DoTransfer(BankAccount destinationAccount, User authorizedUser, decimal amount)
{
// Code to transfer money from a Swiss bank account here.
// This code can assume that authorizedUser is authorized.
// We are guaranteed this because SwissBankAccount is sealed, and
// all callers must go through public version of Transfer from base
// class SecureBankAccount.
}
}
// Evil.exe:
class HostileBankAccount : BankAccount
{
override protected void Transfer(BankAccount destinationAccount, User authorizedUser, decimal amount) { }
public static void Main()
{
User drEvil = new User("Dr. Evil");
BankAccount yours = new SwissBankAccount(1234567);
BankAccount mine = new SwissBankAccount(66666666);
yours.DoTransfer(mine, drEvil, 1000000.00m); // compilation error
// You don't have the right to access the protected member of
// SwissBankAccount just because you are in a
// type derived from BankAccount.
}
}
Dr. Попытка зла украсть ОДИН ... МИЛЛИОН ... ДОЛЛАРОВ ... с вашего банковского счета в швейцарском банке была сорвана компилятором C #.
Очевидно, что это глупый пример, и, очевидно, полностью доверяющий код может делать все, что захочет, вашим типам - полностью доверяющий код может запустить отладчик и изменить код при запуске. Полное доверие означает полное доверие. На самом деле не проектируйте настоящую систему безопасности таким образом!
Но я просто хочу сказать, что "атака", которая здесь сорвана, это попытка выполнить конечный прогон вокруг инвариантов, установленных SecureBankAccount, для прямого доступа к коду в SwissBankAccount.
Это ответ на ваш первый вопрос, я надеюсь. Если это не ясно, дайте мне знать.
Ваш второй вопрос: «Почему SecureBankAccount также не имеет этого ограничения?» В моем примере SecureBankAccount говорит:
this.DoTransfer(destinationAccount, user, amount);
Ясно, что «this» имеет тип SecureBankAccount или что-то более производное. Это может быть любое значение более производного типа, включая новый SwissBankAccount. Не мог ли SecureBankAccount выполнить конечную проверку инвариантов SwissBankAccount?
Да, абсолютно! И поэтому авторам SwissBankAccount требуется - понять все, что делает их базовый класс! Вы не можете просто уйти от какого-то урока волей-неволей и надеяться на лучшее! Реализация вашего базового класса позволяет вызывать набор защищенных методов, предоставляемых базовым классом. Если вы хотите извлечь из него информацию, вам необходимо прочитать документацию для этого класса или код и понять, при каких обстоятельствах будут вызываться ваши защищенные методы, и соответствующим образом написать свой код. Деривация - это способ обмена деталями реализации; если вы не понимаете деталей реализации того, из чего вы производите, не извлекайте из этого.
И кроме того, базовый класс всегда пишется перед производным классом. Базовый класс вас не меняет, и, по-видимому, вы доверяете автору этого класса, чтобы он не пытался вас украсть с помощью будущей версии. (Конечно, изменение базового класса всегда может вызвать проблемы; это еще одна версия хрупкой проблемы базового класса.)
Разница между этими двумя случаями заключается в том, что когда вы унаследованы от базового класса, у вас есть поведение один класс по вашему выбору для понимания и доверия. Это податливая работа. Авторы SwissBankAccount обязаны четко понимать, что SecureBankAccount гарантирует неизменность, прежде чем вызывать защищенный метод. Но они не должны понимать и доверять каждому возможному поведению из каждому возможному классу двоюродного брата , который просто происходит из одного и того же базового класса. Эти ребята могут быть реализованы кем угодно и делать что угодно. У вас не будет возможности понять какой-либо из их инвариантов перед вызовом, и поэтому у вас не будет возможности успешно написать работающий защищенный метод. Поэтому мы спасем вас от беспокойства и запретим этот сценарий.
И кроме того, у нас есть , чтобы позволить вам вызывать защищенные методы для получателей потенциально более производных классов. Предположим, мы этого не допустили и вывели что-то нелепое. При каких обстоятельствах может быть вызван защищенный метод когда-либо , если мы не разрешим вызывать защищенные методы для получателей потенциально производных классов? Единственный раз, когда вы можете вызвать защищенный метод в этом мире, это если вы вызываете свой собственный защищенный метод из закрытого класса! По сути, защищенные методы можно было бы почти никогда не вызывать , и вызываемая реализация всегда будет самой производной. Какой смысл "защищен" в этом случае? Ваше «защищенное» означает то же самое, что и «личное», и может быть вызвано только из закрытого класса ». Это сделало бы их менее полезными.
Итак, короткий ответ на оба ваших вопроса: «потому что, если бы мы этого не сделали, было бы невозможно использовать защищенные методы вообще». Мы ограничиваем вызовы менее производными типами, потому что если мы этого не сделаем, невозможно безопасно реализовать любой защищенный метод, который зависит от инварианта. Мы разрешаем вызовы через потенциальные подтипы, потому что если мы не разрешаем это, то мы вообще не разрешаем никаких вызовов .
Это отвечает на ваши вопросы?