Internal abstract class: how to hide usage outside

2019-01-13 17:54发布

问题:

I have a common assembly/project that has an abstract base class, then several derived classes that I want to make public to other assemblies.

I don't want the abstract base class to show up in these other assemblies in Intellisense, so I thought I'd make it internal, but I get this error:

Inconsistent accessibility: base class 'Settings' is less accessible than class 'IrcSettings' ....

I don't really get this. I am forced to make the abstract Settings class public, and thus visible outside this assembly.

How can I make this class internal instead?

回答1:

As I understand, you want your abstract class to only be implemented by other classes in the same assembly (e.g. it is internal) but the derived classes could be public.

The way to do this is to make the abstract base class public, but give it an internal default constructor:

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

This will allow MyClass (and hence its members) to be visible and usable to classes outside your assembly, but classes outside your assembly cannot inherit from it (will get a compile error).

Edit: If classes which can be seen by external assemblies inherit from MyClass, you cannot prevent MyClass from also being seen - e.g., showing up in Intellisense. However, you can prevent them from being used by following the above.



回答2:

The abstract base class has to be public, as the entire inheritance heirarchy for a class has to be visible. This ensures the polymorphism works and is valid; however all the base classes' members can be internal (including the constructor), and hence not usable outside your assembly



回答3:

There really isn't much of a benefit to what you're trying to achieve but what you're actually looking to achieve is similar to this.

Have your abstract base class in 1 assembly with everything internal. In the AssemblyInfo for that assembly you need to add

[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]

Then in another assembly you have all the classes you want publicly available. Note you will still be able to access the base class from intellisense for any code inside cs_friend_assemblies_2 or whatever you name your assembly but not any where else.



回答4:

You can't simultaneously make the class available to other assemblies for inheritance but also private so it can't be visible to other consumers. You can make the class internal, and expose it to a specific assembly (if it's a friend assembly) using the [InternalsVisibleTo] attribute, but I don't think this is what you want.

If you want to keep code (other than derived classes) from being able to instantiate your base class, you could give it a protected constructor:

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

You can hide the class members from Intellisense using the EditorBrowsable attribute:

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

It should be noted that some people have reported problems with the IDE not always respecting this attribute.



回答5:

As far as I'm concerned, this is a non-problem. Observe:

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();
}

The above wouldn't work at all without polymorphism -- being able to refer to instances of different derived classes through their common interface, the abstract base class. What you want to do is take that away and cripple the usability of your class hierarchy. I don't think you should continue down this path.



回答6:

Will the other assemblies ever inherit from your abstract base class or any of the public classes that do inherit from your abstract base class?

If so, you have to make the abstract base class public. Just make methods you don't want visible outside the assembly internal.

If not, maybe interfaces can help? Define public interfaces, make your public classes implement them, and provide a factory to get instances. That way the only thing intellisense sees outside the assembly is the interface.

Does that help?



回答7:

A way to work around this limitation is to use composition instead of inheritance (there are other good reasons to do this too). For example, instead of:

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

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

Do this:

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(); }
}

You can omit the IMyBase interface entirely if the public doesn't need to know about it and your internals don't either.