How to prevent an abstract class with public derived classes from being inherited in other assemblies?
Asked Answered
P

6

13

I want to write something like the following:

    internal class InternalData
    {
    }

    public class PublicData
    {
    }

    abstract internal class Base {
        internal Base() { }

        private static InternalData CreateInternalDataFromPublicData(PublicData publicData)
        {
            throw new NotImplementedException();
        }

        abstract protected void DoProcess(InternalData internalData);

        public void Process(PublicData publicData)
        {
            InternalData internalData = CreateInternalDataFromPublicData(publicData);
            DoProcess(internalData);
        }
    }

    public sealed class Derived : Base
    {
        protected override void DoProcess(InternalData internalData)
        {
            throw new NotImplementedException();
        }
    }

That is, Base contains some internal logic and is not intended to be inherited by classes outside of my assembly; and Derived is accessible from the outside. InternalData also contains some internal logic and, as it would (and should) never be used from the outside, i also want to make it internal.

Of course the code above won't compile as the Base should not be less accessible than Derived. I can set the Base to be public, that's fine, but it leads to another problem. If Base is public, then there could possibly be some ExternalDerived : Base in some other assembly. But Base.DoProcess accepts an InternalData as its argument, so that ExternalDerived cannot implement it (as it doesn't know about the InternalData). Internal parameterless Base constructor prevents creation of any ExternalDerived instances, and thus nobody will implement ExternalDerived.DoProcess and no InternalData public exposure is needed, but the compiler doesn't know it.

How can i rewrite the code above so that there will be an abstract DoProcess(InternalData) method and so that InternalData class will be internal?

Phi answered 22/2, 2012 at 10:50 Comment(0)
P
0

Set Base to be public.

public abstract class Base {...

Change Base.DoProcess:

protected virtual void DoProcess<T>(T internalData)
{
    if (!(internalData is InternalData))
    {
        throw new ArgumentOutOfRangeException("internalData");
    }
}

Change Derived.DoProcess:

protected override void DoProcess<T>(T internalData)
{
    base.DoProcess(internalData);
    // Other operations
}
Precondition answered 22/2, 2012 at 12:25 Comment(3)
Wow, that is certainly a dirty way i would never thought of! I'm accepting your answer because it seems to be the only one which actually solves the problem, though i'll better just live with the InternalData made public instead :)Phi
@Phi You still did not explain what prevents you from simply making DoProcess internal. internal DoProcess while not optimal, is certainly cleaner than this hack.Cockeyed
As i have said, i'm not going to implement this hack in my code, and i'm exposing InternalData instead. As for your question, basically the choice is between internal InternalData; internal DoProcess and public InternalData; protected DoProcess; although i'd prefer internal InternalData; protected DoProcess if there was such an option, of these two i'll prefer the latter.Phi
N
12

Since C# 7.2 there is private protected access modifier, which means "available only to derived classes in the same assembly".

In other words, it must meet conditions for both internal AND protected, unlike protected internal which applies to internal OR protected.

You can mark the base class' constructor as private protected, effectively preventing inheritance of that class through this constructor outside the assembly while still allowing inheritance within that assembly (and the assembly's friends).

So, a class like this:

public abstract BaseClass
{
    private protected BaseClass() {}
}

is effectively sealed outside the assembly, while still inheritable within the assembly.

Nb answered 11/3, 2020 at 12:49 Comment(0)
C
4

To make InternalData internal, DoProcess must be private or internal (or InternalAndProtected, but C# doesn't support this CLR feature). It can't be protected or protected internal.

internal abstract DoProcess(InternalData internalData);

I'd probably also add an internal abstract void DoNotInheritFromThisClassInAnOutsideAssembly() member. That prevents anybody outside the assembly from inheriting from your class, because they can't implement that member and they get a reasonable compiler error. But you can't make the Base class itself internal.


I'd consider refactoring the code, so that you have no common base class. Probably by using some internal interfaces and composition.

Cockeyed answered 22/2, 2012 at 11:4 Comment(4)
<Slapping my forehead for not thinking about it>. I tried to +2, but it didn't work.Scythia
internal Base() { } has IIUC the same effect internal abstract void DoNotInheritFromThisClass() has (that is, preventing anyone in other assemblies from creating instantiatable derived classes). I want DoProcess to be protected in the first place, as it is purely an implementation detail.Phi
@Phi Just make DoProcess internal instead of protected. Then you can make InternalData internal.Cockeyed
@CodeInChaos Sorry for not making this clear. I don't expect anyone to extend from my Base anyway (otherwise they fooled themselves). The main problem is that I don't want anyone to see my InternalData unless they're using reflection.Phi
W
1

It smells like you should use composition instead of inheritance. sorry, this is a very vague answer. I'm thinking more about this now..

Westfahl answered 22/2, 2012 at 10:55 Comment(5)
Your link doesn't work. Composition makes sense with the exact code sample I posted, but in my actual code inheritance makes more sense. Basically, i had a class with a lot of private methods and fields; and then i needed to extend it - so that some of private methods were made protected, which resulted in the following problem. Making use of composition here would seemingly resulted in writing a lot of code, and, after all, in my real case the Derived is actually a specific case of Base.Phi
Fixed the link, it's only a google search anyway. Without seeing all of the code it's hard to be more specific. Yes, composition does often result in writing more code, but that isn't necessarily a bad thing.Westfahl
Are the data classes immutable POCO's with no methods?Westfahl
Let's say it is e.g. an AbstractCar and Truck : AbstractCar, and there are public Run() and abstract protected RotateableWheel[] GetWheels(), and I certainly wouldn't want for anyone in other assemblies to know about RotateableWheel class (unless they're using reflection of course).Phi
Yes, the InternalData is immutable, though i don't see how it does make a difference.Phi
B
1

The base type must be accessible, because otherwise, it becomes impossible to figure out its base. Your Base derives directly from System.Object, but how does a user of Derived know that? How does it know that Base doesn't derive from another public type, and that type's members should be made available?

If you mark everything in Base internal, except for the class itself, you've already prevented other assemblies from doing anything useful with it. In other words, if you make DoProcess internal, you can then prevent InternalData from becoming public.

Yes, admittedly this allows for bugs in your own assembly, if other classes try to call DoProcess. Unfortunately, there is no "accessible from derived classes in the same assembly" access modifier, only "accessible from derived classes", "accessible from the same assembly" and "accessible from derived classes and accessible from the same assembly". (Actually, .NET does support it, but C# doesn't.)

Bloomsbury answered 22/2, 2012 at 11:19 Comment(2)
"The base type must be accessible, because otherwise, it becomes impossible to figure out its base" - I understand it, and making Base to be public is fine for me. Your explanation about lack of an appropriate access modifier in C# does make sense, and so far, it is the best answer. Still, by internal Base() { } it is possible to tell that nobody will implement DoProcess in other assemblies and this its argument doesn't needs to be public, so maybe there is some workaround.Phi
Correct, but it's a lot more difficult to figure that out, because it also relies on the fact that all classes in your own assembly that derive from Base are sealed.Bloomsbury
P
0

Set Base to be public.

public abstract class Base {...

Change Base.DoProcess:

protected virtual void DoProcess<T>(T internalData)
{
    if (!(internalData is InternalData))
    {
        throw new ArgumentOutOfRangeException("internalData");
    }
}

Change Derived.DoProcess:

protected override void DoProcess<T>(T internalData)
{
    base.DoProcess(internalData);
    // Other operations
}
Precondition answered 22/2, 2012 at 12:25 Comment(3)
Wow, that is certainly a dirty way i would never thought of! I'm accepting your answer because it seems to be the only one which actually solves the problem, though i'll better just live with the InternalData made public instead :)Phi
@Phi You still did not explain what prevents you from simply making DoProcess internal. internal DoProcess while not optimal, is certainly cleaner than this hack.Cockeyed
As i have said, i'm not going to implement this hack in my code, and i'm exposing InternalData instead. As for your question, basically the choice is between internal InternalData; internal DoProcess and public InternalData; protected DoProcess; although i'd prefer internal InternalData; protected DoProcess if there was such an option, of these two i'll prefer the latter.Phi
A
-2

It is actually quite straight forward. You just require that a deriving class implement an abstract internal method. Classes outside the library won't be able to implement the abstract method, and thus fail at compile time.

Your example, minimized to just the essentials:

abstract internal class Base {
    internal protected abstract void DoProcess();

    public void Process() {
        DoProcess();
    }
}

public sealed class Derived : Base {
    internal protected override void DoProcess() {
        throw new NotImplementedException();
    }
}
Ashmead answered 21/3, 2013 at 20:4 Comment(2)
Have you tried to compile your code? Derived classes are not allowed to be more accessible than base ones for obvious reasons. It is not possible to derive a public class from the internal one.Phi
@penartur: The example isn't good, but the trick is workable provided that all classes are either abstract or sealed.Rancell

© 2022 - 2025 — McMap. All rights reserved.