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