Почему мы не можем изменить модификатор доступа при переопределении методов в C #? - PullRequest
40 голосов
/ 04 июня 2011

В C # мы не можем изменить модификатор доступа при переопределении метода из базового класса.например,

Class Base
{
   **protected** string foo()
   {
       return "Base";
   }
}

Class Derived : Base
{
   **public** override string foo()
   {
       return "Derived";
   }
}

Это недопустимо в C #, это даст ошибку времени компиляции.

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

Ответы [ 8 ]

28 голосов
/ 04 июня 2011

Изменение модификатора доступа метода в производном типе не имеет смысла, поэтому оно не разрешено:

Случай 1: переопределить с более ограничительным доступом

Этот случай явно не разрешен из-зак следующей ситуации:

class Base
{
    public virtual void A() {}
}

class Derived: Base
{
    protected override void A()
}

Теперь мы можем сказать:

List<Base> list;
list.Add(new Derived());
list[0].A() //Runtime access exception

Случай 2: Переопределение с менее модифицированным модификатором доступа

Какой смысл?Скройте метод, и все готово.Очевидно, что если кто-то вызывает через базовый тип, у него не будет доступа к новому методу, определенному в производном типе, но это согласуется с тем, как этого хотел автор базового типа, поэтому у вас нет «права» изменить это.Если вам нужна специфика вызова производного класса из производного класса, то в этом случае метод new отлично работает.

РЕДАКТИРОВАТЬ: Расширяющий регистр 2

В случае 2 я пытаюсь сказать, что у вас уже есть средства для изменения доступности любого метода (виртуального или нет), если вы хотите изменить доступность.

Рассмотрим следующий код:

public class Base
{
    protected virtual string WhoAmI()
    {
        return "Base";
    }
}

public class Derived : Base
{
    public new virtual string WhoAmI()
    {
        return "Derived";
    }
}

public class AnotherDerived : Derived
{
    public override string WhoAmI()
    {
        return "AnotherDerived";
    }
}

С помощью ключевого слова new вы фактически создали новый виртуальный метод для вашего класса Derived с тем же именем и подписью.Обратите внимание, что РАЗРЕШЕНО объявлять метод new virtual, поэтому любой класс, производный от Derived, сможет переопределить его.

Недопустимо, чтобы кто-то сделал следующее:

 Base newBaseObject = new Derived();
 newBaseObject.WhoAmI() //WhoAmI is not accessible.

Но этот факт не имеет ничего общего с возможностью переопределить WhoAmI() или нет.В любом случае, такая ситуация никогда не может быть, потому что Base не объявляет public WhoAmI().

Так что в теоретическом C #, где Derived.WhoAmI() может переопределить Base.WhoAmI(), практических преимуществ в этом неттак как вы все равно никогда не сможете вызывать виртуальный метод из базового класса, поэтому опция new уже отвечает вашим требованиям.

Надеюсь, это прояснит ситуацию.

9 голосов
/ 04 июня 2011

Хорошо, я нашел небольшую заметку Эрика Липперта в аннотированном C # справочнике:

Переопределенный виртуальный метод по-прежнему считается методом класса, который его представил. Правила разрешения перегрузки в некоторых случаях предпочитают члены более производных типов ... переопределение метода не «перемещается» туда, где этот метод принадлежит этой иерархии.

Таким образом, это намеренное правило, предотвращающее проблему «хрупкого базового класса» и обеспечивающее лучшее управление версиями, т.е. меньшее количество проблем при изменении базового класса.

Но учтите, что это не имеет ничего общего с безопасностью, безопасностью типов или состоянием объекта.

3 голосов
/ 04 июня 2011

Если вы измените модификаторы видимости с более ограничивающего модификатора на менее ограничивающий модификатор, вы разрешите клиентам класса получать доступ к методам, предназначенным для внутреннего использования. По сути, вы предоставили средства для изменения состояния класса, которые могут быть небезопасными.

1 голос
/ 20 марта 2015

Уменьшение видимости невозможно, потому что если бы Base.Member было видно, а Derived.Member не было видно, это нарушило бы всю концепцию «Derived - это Base» в ООП.Однако увеличение видимости запрещено , может быть , поскольку разработчики языка считают, что изменение видимости в большинстве случаев будет ошибкой.Однако вы всегда можете использовать ключевое слово new, чтобы скрыть членов базового класса, введя члена с тем же именем, но с другим поведением.Этот новый член принадлежит интерфейсу производного типа, поэтому, конечно, вы все равно можете получить доступ к интерфейсу базового типа, приведя к этому базовому типу.В зависимости от того, как вы пишете свой подкласс, ваш new член может эффективно увеличить видимость свойства базового класса, но помните, что к свойству базового класса все еще можно получить прямой доступ (например, подкласс вашего подкласса может привести this кBase и обойти вашу собственность).

Вопрос здесь в том, как оба override и new один и тот же именованный элемент (идентификатор) в подклассе.Это, по-видимому, невозможно.По крайней мере, я могу сказать через эксперименты, что public new override string foo(){return "";} не является синтаксисом для этого.Однако вы можете получить тот же эффект, используя два подкласса:

using System;
class Base
{
    protected virtual string foo()
    {
        return "Base";
    }
    public void ExhibitSubclassDependentBehavior()
    {
        Console.WriteLine("Hi, I am {0} and {1}.", GetType(), foo());
    }
}

abstract class AbstractDerived : Base
{
    protected virtual string AbstractFoo()
    {
        return base.foo();
    }
    protected override string foo()
    {
        return AbstractFoo();
    }
}

class Derived : AbstractDerived
{
    protected override string AbstractFoo()
    {
        return "Deprived";
    }
    public new string foo()
    {
        return AbstractFoo();
    }
}

static class Program
{
    public static void Main(string[] args)
    {
        var b = new Base();
        var d = new Derived();
        Base derivedAsBase = d;
        Console.Write(nameof(b) + " -> "); b.ExhibitSubclassDependentBehavior(); // "b -> Hi, I am Base and Base."
        Console.WriteLine(nameof(d) + " -> " + d.foo()); // "d -> Deprived"
        Console.Write(nameof(derivedAsBase) + " -> "); derivedAsBase.ExhibitSubclassDependentBehavior(); // "derivedAsBase -> Hi, I am Derived and Deprived."
    }
}

Промежуточный подкласс (AbstractDerived) использует override и вводит новый член с разными именами, который подкласс и подклассыможно продолжить override член базового класса, как они считают нужным.Подкласс (Derived) использует new для представления нового API.Поскольку вы можете использовать new или override только с определенным идентификатором один раз на уровень подклассов, вам нужно два уровня подклассов, чтобы эффективно использовать оба для одного и того же идентификатора.

Таким образом, вы можете изменить видимость, переопределяя методы - это просто боль, и я не знаю синтаксиса, чтобы выполнить его с одним уровнем наследования.Однако вам, возможно, придется использовать такой прием, в зависимости от того, какие интерфейсы вы пытаетесь реализовать и как выглядит ваш базовый класс.То есть, это может или не может быть то, что вы действительно хотите сделать.Но я все еще удивляюсь, почему C # не просто поддерживает это с самого начала.Итак, этот «ответ» - это просто повторение вопроса ОП с обходным путем; -).

1 голос
/ 04 июня 2011

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

0 голосов
/ 10 мая 2018

Переопределение - это термин, который позволяет вам изменять или дополнять поведение методов в базовом классе. Переопределение дает вам возможность писать новую логику для существующего метода.

Изменение сигнатуры метода базового класса отчасти похоже на написание нового метода вместо переопределения существующего. Это противоречит цели переопределения метода. Так что, возможно, причина, по которой вы не можете изменить модификатор доступа при переопределении методов в C #.

0 голосов
/ 04 июня 2011

если у него были разные модификаторы доступа, вы больше не можете считать его одним и тем же методом. вроде наводит на мысль о проблеме с дизайном модели.

лучше спросить, зачем вам менять модификаторы доступа?

0 голосов
/ 04 июня 2011

Причины очевидны. Безопасность и целостность объектов.

В этом конкретном примере, что если внешние сущности начнут изменять свойство объекта, которое защищено в соответствии с базовым классом. Все пойдет не так. Как насчет клиентского кода, который написан для базового класса, которому должен соответствовать весь / любой производный класс.

...