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

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

e.g.

public DerivedClass : BaseClass {}

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


person Lyndon    schedule 19.09.2008    source источник


Ответы (10)


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

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

Вместо отношения «есть-а» вам придется использовать отношение «имеет-а».

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

Например, можно случайно перепутать класс 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() общедоступными и генерировать исключения. Но это не делает класс удобным для людей, и это не имеет смысла.

person Brian R. Bondy    schedule 19.09.2008
comment
Интерфейсы, безусловно, являются одним из рекомендуемых путей, но они не должны быть единственным шагом. Вы все равно должны знать, нарушает ли ваш код принцип единой ответственности или нет. Даже реализация интерфейсов не мешает вам писать большие и громоздкие монолитные классы. - person Jason Olson; 20.09.2008
comment
Еще одна причина не скрывать общедоступные методы заключается в том, что это может привести к нарушению контрактов интерфейса с потребителями. Например, скрытие метода Remove() из базового класса, реализующего IList, нарушит работу любого кода, ожидающего присутствия этого метода на основе интерфейса. - person Peter Gluck; 05.07.2012
comment
К сожалению, невозможность сделать это означает, что когда вы используете чужую плохо спроектированную библиотеку, невозможно изолировать от нее свою кодовую базу. Отличным примером является класс WebControl в System.Web — у него есть множество общедоступных свойств, которые злоупотребляют встроенными стилями, и, поскольку они перенесли все это в базовый класс, нет возможности создать класс CleanWebControl, который наследует его и скрывает плохой дизайн. Было бы полезно скрыть чужой плохой дизайн, если бы это было возможно, особенно когда плохой дизайн находится в самой .NET Framework. - person Sean Werkema; 01.10.2012

Когда вы, например, пытаетесь наследовать от 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)» устарела: «Это не поддерживается в этом классе».

person nono    schedule 01.07.2010
comment
Красивый! Это то, что мне нужно. У меня есть плохо структурированный плохой класс, который я не могу изменить (принадлежит кому-то другому), у которого есть члены, которые мне нужно скрыть. Это прекрасно работает. Обратите внимание, что C++ позволяет это сделать. - person Mark Lakata; 01.10.2012

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

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

person Hamish Smith    schedule 19.09.2008
comment
Я провел небольшое исследование интерфейсов и, возможно, я упустил здесь что-то важное, но если бы я определил интерфейс, я бы все равно хотел реализовать его с помощью методов A и B. A и B — вспомогательные функции для реализации более крупных функций в производных классах. - person Lyndon; 20.09.2008
comment
Линдон, надеюсь, код в принятом ответе дает понять, что DerivedClass по-прежнему является подклассом BaseClass, и вы все еще можете использовать функции A и B в DerivedClass для реализации C, но внешний мир не может взаимодействовать с DerivedClass как DerivedClass и вместо этого использует интерфейс - person Hamish Smith; 20.09.2008

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

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

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

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

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

person Gishu    schedule 20.09.2008
comment
необходимость скрывать методы и свойства базового класса возникает, когда у вас нет контроля над источником базового класса. Предположим, вам нужно переопределить весь механизм базового класса, который не удовлетворяет вашим потребностям. Вы создаете свои собственные методы, переопределяете другие, но также хотите скрыть некоторые из них, принадлежащие исходному механизму и которые могут быть использованы другими программистами. Вы можете выбрать агрегацию/упаковку базового класса, но это также не всегда приемлемо, поскольку нарушает цепочку наследования. Отметить такие методы как устаревшие - лучшее решение - person Radu Simionescu; 17.11.2015

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

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();
person Jorge Ferreira    schedule 19.09.2008
comment
Если вы используете DerivedClass d = new DerivedClass(); d.A(), вы по-прежнему сможете получить доступ к реализации базового класса A(). - person Brian R. Bondy; 20.09.2008
comment
Хорхе, я только что попробовал это, и, как заметил Брайан, A и B все равно будут разоблачены. - person Lyndon; 20.09.2008
comment
если вы приведете экземпляр DerivedClass к BaseClass, вы будете вызывать A и B из базового класса. Было бы лучше работать с виртуальными методами в базовом классе - person Radu Simionescu; 17.11.2015

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

person Nescio    schedule 19.09.2008

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

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

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

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

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

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

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

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

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

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

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

person Jason Olson    schedule 20.09.2008

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

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

person Serafina Brocious    schedule 19.09.2008

Хотя ответ на вопрос «нет», есть один совет, который я хотел бы указать для других, прибывающих сюда (учитывая, что ОП как бы намекает на доступ к сборке третьими лицами). Когда другие ссылаются на сборку, 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...*/
}
person James Wilkins    schedule 27.02.2018