Внутренний абстрактный класс: как скрыть использование вне сборки? - PullRequest
46 голосов
/ 07 августа 2009

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

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

Непоследовательная доступность: базовый класс «Настройки» менее доступен, чем класс «IrcSettings» ....

Я действительно не понимаю этого. Я вынужден сделать абстрактный Settings класс public, и, таким образом, видимым за пределами этой сборки.

Как я могу сделать этот класс internal вместо этого?

Ответы [ 7 ]

85 голосов
/ 07 августа 2009

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

Способ сделать это состоит в том, чтобы сделать абстрактный базовый класс общедоступным, но дать ему внутренний конструктор по умолчанию:

public abstract class MyClass
{
    internal MyClass() { }
}

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

Edit: Если классы, которые могут быть видимыми внешними сборками, наследуются от MyClass, вы не можете запретить MyClass также видеть visible - например, отображаться в Intellisense. Однако вы можете предотвратить их использование , следуя приведенным выше инструкциям.

9 голосов
/ 07 августа 2009

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

6 голосов
/ 07 августа 2009

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

Имейте свой абстрактный базовый класс в 1 сборке со всем внутренним. В AssemblyInfo для этой сборки вам нужно добавить

[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]

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

4 голосов
/ 07 августа 2009

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

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

abstract class MyBaseClass
{
    protected MyBaseClass() { ... } // only inheritors can access this...
}

Вы можете скрыть членов класса от Intellisense, используя атрибут EditorBrowsable:

abstract class MyBaseClass
{ 
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    public void SomeMethodToBeHidden() { }
}

Следует отметить, что некоторые люди сообщали о проблемах с IDE, не всегда соблюдающих этот атрибут.

1 голос
/ 07 августа 2009

Насколько я понимаю, это не проблема. Обратите внимание:

public abstract class Foo {
    public void virtual Bar() {
        // default implementation
    }
}

public class NormalFoo : Foo { }

public class SpecialFoo : Foo {
    public override void Bar() {
        // special implementation
    }
}

var foolist = new List<Foo>();

foolist.Add( new NormalFoo() );
foolist.Add( new SpecialFoo() );

foreach (var f in foolist) {
    f.Bar();
}

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

0 голосов
/ 17 ноября 2015

Способ обойти это ограничение - использовать композицию вместо наследования (есть другие веские причины , чтобы сделать это тоже). Например, вместо:

internal abstract class MyBase
{
    public virtual void F() {}
    public void G() {}
}

public class MyClass : MyBase // error; inconsistent accessibility
{
    public override void F() { base.F(); /* ... */ }
}

Сделайте это:

public interface IMyBase
{
    void F();
}

internal sealed class MyBase2 : IMyBase
{
    public void F() {}
    public void G() {}
}

public sealed class MyClass2 : IMyBase
{
    private readonly MyBase2 _decorated = new MyBase2();
    public void F() { _decorated.F(); /* ... */ }
    public void G() { _decorated.G(); }
}

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

0 голосов
/ 07 августа 2009

Будут ли другие сборки когда-либо наследоваться от вашего абстрактного базового класса или любых открытых классов, которые наследуют от вашего абстрактного базового класса?

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

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

Это помогает?

...