Why Would You Make a Method Internal in a Public Class and Vice Versa?
Not everything should be public these days.

When developing or maintaining a class library, you might want want to restrict access to certain classes or methods and make them only available within the assembly. .NET provides the internal access modified exactly for this purpose. It’s pretty straightforward to use. However, we can achieve some interesting and sometimes unexpected effects if we mix internal and public modifiers.

Public Class with an Internal Method

The basic use case for this situation is rather obvious: You want to make a class available outside the assembly but keep a method hidden from the outside world. But there’s also a trickier case where internal can act as a workaround for protected.

Let’s say we are still working on that burger kiosk project. We’ve got an IngredientList class, a base Burger class, and a Cheeseburger class that inherits from Burger. The Burger class has a protected method, ValidateIngredients, so every type of burger can check if there are enough buns to make it.

public class IngredientList { }

public abstract class Burger
{
	protected bool ValidateIngredients(IngredientList ingredients);
}

public class Cheeseburger : Burger { }

But now we want to make the IngredientList class internal. This way, the consumers of our assembly will only deal with burgers. Suddenly, we get this error:

Inconsistent accessibility: parameter type ‘IngredientList’ is less accessible than method ‘Burger.ValidateIngredients(IngredientList)’

Let’s see. The Burger class is public, which means anyone can subclass it and get access to the protected ValidateIngredients. But this method has an internal class IngredientList as a parameter, making the method signature invalid. And that’s where we get a conflict.

How to solve it? Of course, we can refactor our small sample classes. However, that can be not easy in a real-life scenario, and sometimes we have to stick to the existing class hierarchy. The use of internal instead of protected can be a workaround for this situation:

public abstract class Burger
{
    internal bool ValidateIngredients(IngredientList ingredients);
}

Now, you can’t access ValidateIngredients outside of the assembly, even from the inherited classes, and the conflict is solved. Of course, we should mention that internal is not the same as protected, and now every class inside the assembly can call ValidateIngredients, not just Burger subclasses. So that’s the trade-off you have to consider.

What About protected internal?

You might think protected internal is the good fix, but it’s actually not. That modifier makes the method protected outside the assembly (still causing the same error with a less accessible IngredientList) and public inside the assembly, so it doesn’t really solve the problem in this case.

Internal Class with a Public Method

Does that make sense at all? Why would you ever make a method public in an internal class? Since the class itself isn’t visible outside the assembly, who’s even calling that method? The short answer: nobody, unless you’re doing something special.

Let’s say we have this setup:

internal class IngredientList
{
	public static bool AreCarrotsHealthy() => true;
}
// This won't work outside the assembly
var carrotsQuestionOutcome = IngredientList.AreCarrotsHealthy();

There’s still a workaround for this combination of public and internal if we can use interfaces. As long as an internal class implements a public interface, we can interact with its public methods. Let’s declare an interface and a factory to retrieve a list:

public interface IIngredientList
{
    public bool AreCarrotsHealthy();
}

internal class IngredientList : IIngredientList
{
    public bool AreCarrotsHealthy() => true;
}

public class IngredientListFactory
{
    public IIngredientList Get() => new IngredientList();
}

Now, instead of exposing IngredientList, we return IIngredientList. External code can call AreCarrotsHealthy(), but it doesn’t need to know (or care) about IngredientList. This also keeps things clean and modular.

Reflections

We also have reflections as a hacky bypass. However, this is usually a bad idea. The cons of reflections in this case are too heavy: it’s slow, it’s harder to maintain, it can break easily, and even some environments block it.

var type = Assembly.Load("ClassLibrary").GetType("ClassLibrary.IngredientList");
var instance = Activator.CreateInstance(type);
var method = type.GetMethod("AreCarrotsHealthy");
var result = (bool)method.Invoke(instance, null);

This essentially tricks .NET into calling something it shouldn’t be able to. While the task is solved, let’s mention it once again: use this only if you absolutely have to.

Testing and InternalsVisibleTo

Sometimes, we want to test our classes and methods without implementing additional interfaces or reflections. For this, there’s a simple and elegant solution: the InternalsVisibleTo attribute. Just add this line to your assembly:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ClassLibrary.Tests")]

Now, your test project ClassLibrary.Tests can access all internal stuff as if it were public. No hacks, no reflection, just a simple, built-in way to test your internal logic.

***

The internal access modifier is a great tool to separate the presentation of your class library from its internal logic. But managing the mix of internal and public modifiers can get tricky sometimes!


Last modified on 2025-01-09

Get new posts by email: