In big solutions - especially monolithic or module-lithic ones - it can be hard to prevent others and yourself from accidentally referencing an assembly in another assembly where it should not be used at all. That's what I mean by 'assembly leaking'.
It seems odd to me that most multi-layer software designs use public
to expose classes, interfaces, or whatever to another project/assembly.
For example in CleanArchitecture all classes and interfaces of the infrastructure project are public
yet they are only used by the web project. So how could one restrict the use of these public
infrastructure classes to be only used in the web project?
One interesting .NET feature to accomplish such a restriction could be Friend Assemblies, but I don't think this should be used in every context. So what other ways are there?
It is what it is. From the perspective of an assembly (the root level entity): we have the scope internal
that restricts the visibility of a type to the current assembly.
If a type must be visible outside its assembly, then the type's scope is public
.
From the perspective of the assembly, everything that is outside the assembly, external, is public. This definition makes perfect sense.
With the InternalsVisibleToAttribute
you can break this rule by qualifying an assembly as friend/exception.
Why isn't this used more often? We can only guess. But there are also facts to consider. At least we can say that InternalsVisibleToAttribute
comes with all the disadvantages that attribute based type or member configuration brings.
Some don't have this feature on their mind unless they find themselves in a situation where they have to increase the visibility of internal
types and members only to implement a unit test.
Some consider it a bad smell and avoidable by proper class/assembly design. Because there must be/is a better way to solve this problem without sacrificing valuable design principles.
Some would argue that InternalsVisibleToAttribute
scales bad because when you have a new assembly that needs access to the internal type you have to decorate the assembly with a new InternalsVisibleToAttribute
and re-deploy it. Thats not nice at all.
Some could think that InternalsVisibleToAttribute
is kind of an emergency button for developers that don't know proper class design or don't know their language well enough.
Most reasons are highly subjective.
From experience we can tell that properly designed classes don't make the use of InternalsVisibleToAttribute
necessary (except for unit test assemblies).
We are able to value information hiding and avoid defining our internal types public
.
One major drawback is that the InternalsVisibleTo
attribute makes all internal APIs public to the friend assembly.
We no longer have control over what the friend assembly is allowed to access. The assembly and its friend assembly are treated like they were virtually a single assembly (in terms of member and type accessibility).
However, assuming that they are two logical assemblies for a reason, then the effect of exposing the complete internals (types and members declared as internal
) is not desirable.
If this degree of visibility is desirable, then the two assemblies likely should be merged instead. It all starts with the design of the assemblies.
When InternalsVisibleTo
bypasses and breaks our original design decisions (those APIs were internal
for a good reason), then we must carefully evaluate whether the use of InternalsVisibleTo
is really reasonable or could/should be avoided by improving the design in the first place.
I suggest changing the focus of your question: what can we do to avoid the use of the InternalsVisibleToAttribute
attribute?
Answering this question also allows us to understand better why InternalsVisibleToAttribute
attribute is not really necessary except for edge cases: that's why friend assemblies are realized using the attribute and not via e.g. a friend
keyword. If it is not really necessary it makes sense that we don't find it often. Edge cases are rare by definition.
For example, if you create a library, you have a public API and the internal business logic of that library.
Our goal is to avoid the InternalsVisibleToAttribute
attribute to improve extensibility (improve maintainability).
To solve our problem of having types that depend on types defiend in different assemblies we can pick one of the following solution or a combine them:
You could move the public API to its own assembly and move the internal logic to the internal assembly. Of course, having everything in the same assembly works too. However, the key is to move all internal dependencies to the same assembly and specify all these types as internal
.
Well-structured assemblies strive to contain all their internal types i.e. don't have external dependencies.
In other words, it all depends on how you design assemblies.
One can come up with different assembly design rules.
You can use assemblies to group types by the first letter of the type name.
You can use responsibilities of types to define assemblies.
You can use a deployment scheme.
You can define assemblies based on some architectural definitions.
However, you can also define assemblies based on type scope and the dependency graph of the types.
This seems to be the most sophisticated approach. But it requires deeper understanding of the class level responsibilities of the application, framework or library.
Proper design must be the foundation. If the design is bad, you open the door for a lot of smell to creep in.
protected class
You can also make use of the protected
access modifier, which allows a class defined in a different assembly to subclass it. This is kind of the InternalsVisibleToAttribute
attribute behavior achieved via inheritance. This language/compiler feature allows us to have a protected
base class in one assembly and the derived subclass in another assembly.
This is useful if we want to make internals visible only to those types that extend our e.g. library. This is the most common case that would make the InternalsVisibleToAttribute
useful. But it's more elegant and scales better.
See the following example to see how we can use protected class
to make internal functionality visible to different assemblies, that extend the defining assembly, without exposing the types to our public API.
This requires to define the protected
type as a nested type:
Library.dll
public abstract class LibraryBaseClass
{
// The type is not directly visible via the public library API
protected class LibraryHelperClass
{
// The functionality is not directly accessible via the public library API
public void MemberIsOnlyVisibleToTypesThatExtendLibraryBaseClass()
{
// Access library internal features
}
}
public abstract void LibraryExtensionCode();
}
internal class AnotherLibraryClass
{
public void DoSomething()
{
// INVALID CODE:
// LibraryHelperClass is inaccessible due to the protection level.
// AnotherLibraryClass would have to subclass LibraryBaseClass
// in order to be able to access LibraryHelperClass (not optimal for some cases).
var internalLibraryHelper = new LibraryHelperClass();
}
}
B.dll
// We have to subclass LibraryBaseClass in order to get access to the LibraryHelperClass class. It's hidden from the public API but visible if we extend library code
public class LibraryExtension : LibraryBaseClass
{
public override void LibraryExtensionCode()
{
// Add extended functionality
// We have access to the "internal" type similar to InternalsVisbleToAttribute.
// The difference is that in order to become a friend of the assembly "Library.dll"
// we must implement library code, which is what we usually intent to do
// when we believe we need InternalsVisibleToAttribute
var internalHelperClass = new LibraryHelperClass();
// Execute some internal library code
internalHelperClass.MemberIsOnlyVisibleToTypesThatExtendLibraryBaseClass();
}
protected internal class
Then there is protected internal
which is a combination of protected
and internal
: the type is visible to all types in the same assembly but only visible to subclasses defined in different assemblies.
For example, while in the previous example the nested LibraryHelperClass
is visible to subclasses of LibraryBaseClass
it won't be visible directly to the defining assembly itself - except for subclasses of LibraryBaseClass
.
The protected
access modifier requires the caller to be a subclass of LibraryBaseClass
.
Adding internal
will also allow the current assembly to reference the nested LibraryHelperClass
class without having to subclass LibraryBaseClass
.
protected internal
does not change the accessibility for external DLLs. For external DLLs the protected
is relevant (as it lifts the internal
access restriction):
Library.dll
public abstract class LibraryBaseClass
{
// The type is not directly visible via the public library API
protected class LibraryHelperClass
{
// The functionality is not directly accessible via the public library API
public void MemberIsOnlyVisibleToTypesThatExtendLibraryBaseClass()
{
// Access library internal features
}
}
public abstract void LibraryExtensionCode();
}
internal class AnotherLibraryClass
{
public void DoSomething()
{
// The protection level of 'protected internal' now enables other types
// of the current assembly to treat the LibraryHelperClass
// as a common internal type.
// No subclassing of LibraryBaseClass is required for assembly types.
var internalLibraryHelper = new LibraryBaseClass.LibraryHelperClass();
}
}
B.dll
However, nothing has changed for the external assemblies.
They still have to subclass the LibraryBaseClass
in order to get access to the internal LibraryHelperClass
// We have to subclass LibraryBaseClass in order to get access to the LibraryHelperClass class. It's hidden from the public API but visible if we extend library code
public class LibraryExtension : LibraryBaseClass
{
public override void LibraryExtensionCode()
{
// Add extended functionality
// We have access to the "internal" type similar to InternalsVisbleToAttribute.
// The difference is that in order to become a friend of the assembly "Library.dll"
// we must implement library code, which is what we usually intent to do
// when we believe we need InternalsVisibleToAttribute
var internalHelperClass = new LibraryHelperClass();
// Execute some internal library code
internalHelperClass.MemberIsOnlyVisibleToTypesThatExtendLibraryBaseClass();
}
We can see that we can achieve similar behavior without using the InternalsVisibleToAttribute
attribute using the protected internal
access modifier.
Both solution, the protected internal
access modifier and proper assembly design, are far easier to maintain then using the InternalsVisibleToAttribute
attribute.
It's always good to know the language features in order to write better code and develop better applications.