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

предположим, что у меня есть BaseClass с общедоступными методами A и B, и я создаю DerivedClass через наследование.

например

public DerivedClass : BaseClass {}

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

10 ответов


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

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

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

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

например, можно случайно путают класс автомобиля, чтобы вывести из класса двигателя, чтобы получить его функциональность. Но двигатель-это функциональность, которая используется автомобилем. Таким образом, вы хотели бы использовать отношения 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>, и вы хотите скрыть direct 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.Добавить (TestConsole.TestObject)' является устаревшим: 'это не поддерживается в этом классе.'


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

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


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

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

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

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

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


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


@Brian R. Bondy указал мне на интересную статью о скрытии через наследование и новая ключевое слово.

http://msdn.microsoft.com/en-us/library/aa691135 (VS.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();

скрытие-довольно скользкий склон. Основные вопросы, IMO, являются:

  • Это зависит от времени разработки тип объявления экземпляра, то есть, если вы делаете что-то вроде Автоматически. параметр obj = новый подкласс(), тогда позвоните в obj.A (), сокрытие побеждено. Класса baseclass.A () будет выполнено.

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

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

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

предстоящая парадигма кодирования называется "композиция над наследованием"."Это напрямую отражается на принципах объектно-ориентированного развития (особенно на принципе единой ответственности и Открытый / Закрытый Принцип).

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

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

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

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

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


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

Edit: Хорхе Феррейра прав.


хотя ответ на вопрос "нет", есть один совет, который я хочу указать для других, прибывающих сюда (учитывая, что 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. Тем не менее, он будет доступен для любой, кто явно набирает-casts obj to 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...*/
}