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


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

например

public DerivedClass : BaseClass {}

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

10 52

10 ответов:

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

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

вместо использования отношения is-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>, и вы хотите скрыть прямой 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) 'is obsolete:' это не поддерживается в этом классе.-

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

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

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

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

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

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

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

единственный способ сделать это, что я знаю, это использовать 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();

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

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

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

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

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

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

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

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

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

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

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

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

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. Тем не менее, он будет доступен для любой, кто явно набирает-бросает 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...*/
}