C # - Могут ли публично унаследованные методы быть скрыты (напримерсделано закрытым для производного класса)

StackOverflow https://stackoverflow.com/questions/106383

  •  01-07-2019
  •  | 
  •  

Вопрос

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

например ,

public DerivedClass : BaseClass {}

Теперь я хочу разработать метод C в DerivedClass, который использует A и B.Есть ли способ, которым я могу переопределить методы A и B, чтобы они были закрытыми в DerivedClass, чтобы только метод C был доступен тому, кто хочет использовать мой DerivedClass?

Это было полезно?

Решение

Это невозможно, почему?

В C # вам навязано, что если вы наследуете общедоступные методы, вы должны сделать их общедоступными.В противном случае они ожидают, что вы вообще не будете производными от класса.

Вместо использования отношения is-a вам пришлось бы использовать отношение has-a.

Разработчики языка специально не допускают этого, чтобы вы могли более правильно использовать наследование.

Например, можно случайно перепутать класс Car с производным от класса Engine, чтобы получить его функциональность.Но двигатель - это функциональность, которая используется автомобилем.Итак, вы хотели бы использовать отношение has-a.Пользователь Автомобиля не хочет иметь доступ к интерфейсу Двигателя.И сам Автомобиль не должен путать методы работы Двигателя со своими собственными.Как и будущие производные Автомобиля.

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

Что вам следует сделать вместо этого?

Вместо этого вы должны реализовать интерфейсы.Это позволяет вам свободно использовать функциональность, используя отношение has-a.

Другие языки:

В C ++ вы просто указываете модификатор перед базовым классом private, public или protected.Это делает всех членов базы, которые были общедоступными для этого указанного уровня доступа.Мне кажется глупым, что вы не можете сделать то же самое в C #.

Реструктурированный код:

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

Я понимаю, что названия вышеперечисленных классов немного спорны :)

Другие предложения:

Другие предлагали сделать A() и B () общедоступными и создавать исключения.Но это не делает класс удобным для использования людьми, и на самом деле это не имеет смысла.

Другие советы

Когда вы, например, пытаетесь наследовать от List<object>, и вы хотите скрыть прямое Add(object _ob) Участник:

// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}

На самом деле это не самое предпочтительное решение, но оно выполняет свою работу.Intellisense по-прежнему принимает, но во время компиляции вы получаете сообщение об ошибке:

ошибка CS0619:'TestConsole.TestClass.Add(TestConsole.TestObject)' устарел:"Это не поддерживается в этом классе".

Это звучит как плохая идея. Лисков это не произвело бы впечатления.

Если вы не хотите, чтобы пользователи DerivedClass могли получать доступ к методам DeriveClass.A() и DerivedClass.B() Я бы предположил, что DerivedClass должен реализовывать некоторый открытый интерфейс IWhateverMethodCIsAbout, а потребители DerivedClass на самом деле должны разговаривать с IWhateverMethodCIsAbout и вообще ничего не знать о реализации BaseClass или DerivedClass.

Что вам нужно, так это композиция, а не наследование.

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

Теперь, если вам нужен особый тип самолета, например, такой, который имеет PairOfWings = 2, но в остальном делает все, что может самолет..Вы наследуете плоскость.Этим вы заявляете, что ваш вывод соответствует контракту базового класса и может быть заменен без мигания везде, где ожидается базовый класс.например ,LogFlight (Самолет) продолжал бы работать с экземпляром BiPlane.

Однако, если вам просто нужно поведение Fly для новой птицы, которую вы хотите создать, и вы не готовы поддерживать полный контракт базового класса, вы вместо этого создаете compose.В этом случае реорганизуйте поведение методов для повторного использования в Flight нового типа.Теперь создайте и сохраните ссылки на этот класс как в Plane, так и в Bird.Вы не наследуете, потому что Bird не поддерживает полный контракт базового класса...( например ,он не может предоставить GetPilot() ).

По той же причине, вы не можете уменьшить видимость методов базового класса при переопределении.. вы можете переопределить и сделать базовый частный метод общедоступным при выводе, но не наоборот.например ,В этом примере, если я получу тип плоскости "BadPlane", а затем переопределю и "Скрою" GetPilot() - сделаю его приватным;клиентский метод LogFlight(плоскость p) будет работать для большинства плоскостей, но сработает из-за "плохой плоскости", если для реализации LogFlight потребуется вызвать GetPilot(). Поскольку ожидается, что все производные базового класса будут "заменяемыми" везде, где ожидается параметр базового класса, это должно быть запрещено.

Единственный способ сделать это, о котором я знаю, - использовать отношение Has-A и реализовывать только те функции, которые вы хотите предоставить.

@Брайан Р.Бонди указал мне на интересную статью о сокрытии через наследование и новое ключевое слово.

http://msdn.microsoft.com/en-us/library/aa691135 (ПРОТИВ 71).aspx

Поэтому в качестве обходного пути я бы предложил:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

Таким образом, подобный код выдаст Исключение NotSupportedException:

    DerivedClass d = new DerivedClass();
    d.A();

Прятаться - довольно скользкий путь.Основными проблемами, ИМО, являются:

  • Это зависит от времени разработки тип объявления экземпляра, это означает, что если вы делаете что-то вроде BaseClass obj = new SubClass(), то вызываете obj .A(), прятаться - значит потерпеть поражение.Базовый класс.Будет выполнена функция A().

  • Скрытие может очень легко скрыть поведение (или изменения поведения) в базовом типе.Очевидно, что это вызывает меньше беспокойства, когда вы владеете обеими сторонами уравнения, или если вызов 'base.xxx' является частью вашего дочернего элемента.

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

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

Новая парадигма кодирования называется "Композиция вместо наследования". Это напрямую противоречит принципам объектно-ориентированной разработки (особенно принципу единой ответственности и принципу открытости / закрытости).

К сожалению, из-за того, что многих из нас, разработчиков, учили объектной ориентации, у нас сформировалась привычка сразу думать о наследовании, а не о композиции.У нас, как правило, есть более крупные классы, которые имеют много разных обязанностей просто потому, что они могут содержаться в одном и том же объекте "Реального мира".Это может привести к созданию иерархии классов глубиной более 5 уровней.

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

Один из способов разбить ваш код - это использовать интерфейсы, подобные упомянутым в другом ответе.В любом случае, это разумная вещь, поскольку вы хотите, чтобы внешние зависимости класса привязывались к абстракциям, а не к конкретным / производным типам.Это позволяет вам изменять реализацию без изменения интерфейса, и все это без изменения строки кода в вашем зависимом классе.

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

Возможно, тот Лучшие ресурсом по объектно-ориентированной разработке является Роберт К.Книга Мартина, Гибкая разработка программного обеспечения, принципы, шаблоны и практики.

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

Редактировать:Хорхе Феррейра прав.

Хотя ответ на вопрос "нет", есть один совет, на который я хотел бы обратить внимание для других, прибывающих сюда (учитывая, что OP как бы намекал на доступ к сборке третьими лицами).Когда другие ссылаются на сборку, Visual Studio должна учитывать следующий атрибут, чтобы он не отображался в intellisense (скрытый, но ВСЕ еще может быть вызван, так что будьте осторожны):

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

Если бы у вас не было другого выбора, вы могли бы использовать new для метода, который скрывает метод базового типа, верните => throw new NotSupportedException();, и объедините его с указанным выше атрибутом.

Другой трюк зависит от ТОГО, чтобы НЕ наследовать от базового класса, если это возможно, где база имеет соответствующий интерфейс (например, IList<T> для List<T>).Реализация интерфейсов "явно" также скроет эти методы от intellisense в типе класса.Например:

public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}

В случае var obj = new GoodForNothing(), тот самый Dispose() метод не будет доступен на obj.Тем не менее, он БУДЕТ доступен всем, кто явно выполняет приведение типов obj Для IDisposable.

Кроме того, вы также могли бы обернуть базовый тип вместо наследования от него, а затем скрыть некоторые методы:

public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top