Introduction
The internal
keyword of C# allows access to non-public types and members provided that the access is from within the same assembly or from within a friend assembly as specified using the "assembly:InternalsVisibleTo
" attribute. According to MSDN:
A common use of internal
access is in component-based development because it enables a group of components to cooperate in a private manner without being exposed to the rest of the application code. For example, a framework for building graphical user interfaces could provide Control
and Form
classes that cooperate using members with internal access. Since these members are internal
, they are not exposed to code that is using the framework.
Within a single, well understood and well documented component assembly, with well defined separation between component code and client code, this makes sense. However, developers should nonetheless exercise caution because encapsulation is broken with respect to internal types and members. In fact, I find it helpful to think of the internal
keyword not so much as an "access modifier", but more like an "access allower". It is a selective encapsulation breaking device.
Examples of this situation can be seen in many applications where a clean separation between business objects and service objects is desired, but the business objects often must carry around some serviceful bookkeeping attributes that are not part of the business model, like data access ids or modified times. Should the implementation allow internal access to these attributes (hidden but not encapsulated)? Should the attributes be accessible through a public interface available to the client code (well encapsulated but hidden)? Should serviceful attributes be located outside of business objects altogether? There are many design approaches one could take, but this situation can also be addressed by using internal interfaces. In this article, I will show how internal interfaces could be used to enforce encapsulation while exposing internal functionality to service code. It is not claimed that this solution is ideal for all situations, but even where it may not fit a particular design situation, using internal interfaces is still an interesting strategy to consider and is a great way for designers to document their design intentions in the code.
A Brief History of internal
First a few words about the internal
keyword. The internal
keyword of C# can be used to allow access to otherwise non-public types and members from within the same assembly or specially declared friend assemblies. In spite of this compact description, it has surprisingly rich semantics.
- A top-level type definition (
interface
, class
or struct
) can be marked as either public
or internal
. If no access modifier is given for a top-level type, the default is internal
. Metadata for top-level types that are marked internal
are available anywhere within the defining assembly or within referencing friend assemblies. - A member (field, method, property, indexer, or event) can be marked with any of the possible access modifiers:
public
, protected
, protected internal
, internal
, or private
. If no access modifier is given for a member definition, the default is private
. Metadata for type members that are marked internal
are available anywhere within the defining assembly or within referencing friend assemblies provided that the metadata for the enclosing type is also available. (Note that in a struct
, a member can be marked as neither protected
nor protected internal
because a struct
is implicitly sealed.) - A nested type definition (a type defined within another type) can be marked with any of the possible access modifiers:
public
, protected
, protected internal
, internal
, or private
. If no access modifier is given for a nested type definition, the default is private
as for members. Metadata for nested types that are internal are available anywhere within the defining assembly or within referencing friend assemblies provided that the metadata for the enclosing type is also available.
In C#, the interface
keyword is used to define a type without implementation. An interface can contain signatures for the following kinds of members: methods, properties, indexers, and events. Member definitions occurring within an interface definition must all be public
. (In fact, the C# compiler will not accept any access modifier on interface members and just makes them all public
implicitly.)
When a class implements a public
or internal
interface, the class may be either public
or internal
. (However, when a class extends a base class or when an interface extends a base interface, the extending type cannot be less accessible than the base type.) In the implementation itself, the implementing members must be marked public
. (The only exception to this rule is when using explicit interface method implementations (EIMI), which have no accessibility modifiers and have compiler-fixed accessibility rules.)
A "friend assembly" is declared using an assembly attribute specified within the granting assembly. In many Visual Studio project types (but not web site projects) this attribute is defined within a file named Properties\AssemblyInfo.cs. The syntax is:
[assembly: InternalsVisibleTo("AssemblyName, PublicKey="XYZ123ABC456")]
The granting assembly needs to include this attribute. The friend assembly can be specified using only the name if it is a weakly named assembly, or it can be specified by name plus public key (as above) if it is a strongly named assembly. It is recommended for production code to use strongly named assemblies when granting friendship, because a weakly named assembly can be easily spoofed with the effect of granting internal access to malicious code. To get the public key of a strongly named assembly, use the sn.exe tool provided with Visual Studio as described elsewhere. (Note in .NET 2.0, the somewhat shorter hash "PublicKeyToken" is specified instead of the full PublicKey.)
The Example
The example is the backend of a small hypothetical N-tier application that tracks course enrollment for reporting at some college. Being an example, it is lean on the domain detail and has only one entity: a college course with some simple properties and some simple enrollment logic. It nonetheless has a business logic layer, a data access layer, and a factory for creating business objects. We would like to maintain a clean separation between business logic on one hand and service logic (data access and object creation) on the other.
The ICollegeCourse
and ICollegeFactory
interfaces contain simple, business-only definitions.
namespace CollegeCourseClient
{
public interface ICollegeCourse
{
string CourseName { get; set; }
string CourseNumber { get; set; }
string Term { get; set; }
string Instructor { get; set; }
string CourseDescription { get; set; }
int CreditHours { get; set; }
string[] GetEnrolledStudents();
void EnrollStudent(string student);
void UnEnrollStudent(string student);
}
public interface ICollegeFactory
{
ICollegeCourse GetNewCourse();
}
}
Likewise, the data access layer exposes a simple interface as well.
namespace DataAccessClient
{
public interface IDataAccess
{
long Save(ICollegeCourse course);
ICollegeCourse Load(long id);
void Delete(ICollegeCourse course);
void AcceptChanges();
void RejectChanges();
ICollegeFactory Factory { get; set; }
}
All of these interfaces are collected into an "interface
" assembly. While not a hard and fast rule, I find that keeping the interfaces reasonably grouped and away from implementations helps cut down on the number of references I need. (See for example, Agile Principles, Patters and Practices in C# by Martin and Martin.)
The data access service will assume only the following very general and simple bookkeeping properties and methods are available on the business objects.
AUID
: Some object ID that is considered temporary until the first time the object is saved, and then it is an application-wide unique ID after the object is saved. IsDirty
: A flag which tells the data access layer if the object has been modified since the last time it was saved. Modified
, Created
: Some bookkeeping attributes tracking date and time the object was (last) touched by the data access layer. ToStringRep()
, FromStringRep()
: Some methods for serializing the object to/from a string
. (These are actually not for bookkeeping; more on this below in the Conclusion.)
At this point, a simple and quick anti solution for this backend might be to create public
interfaces for the business object assembly with special "data access aware" internal
properties and methods accessible by a friend data access assembly. This leads to several problems, some of which were outlined above.
- Encapsulation is broken. The data access assembly now has access to whatever internals happen to be defined in the business object assembly, including ones not meant for data access.
- A reference to the business object assembly must be added to the data access assembly.
- There may now be a chicken and egg reference problem if the business implementation needs also to be aware of definitions in the data access assembly (assuming they are in different assemblies.)
Alternatively, these attributes could be defined in a separate public
interface. While much better, it still has the undesirable aspect that client code has full access to the service functionality. While this may not be an issue for some designs, let's create an internal
interface.
namespace DataAccessClient
{
internal interface IDataAccessService
{
long AUID { get; set; }
bool IsDirty { get; set; }
DateTime? Modified { get; set; }
DateTime? Created { get; set; }
string ToStringRep();
void FromStringRep(string rep);
}
This is the interface that will be exposed to friend assemblies, so wherever it goes it will require the assembly: InternalsVisibleTo
attribute. But for this simple case, it can just go into the same assembly as the other interfaces. By making the implementation and the service objects adhere to the same interface, one can make them agree on a contract for handling business objects without concerning the client.
Just for kicks, in the example, there are two CollegeCourse
implementations in namespaces Impl1
and Impl2
. Each implementation is intentionally, and somewhat artificially, different and each also has its own concrete factory class. (The code as shown below is abridged for clarity.) Note that the implementations are also internal
. Although not required, this is consistent with the factory pattern and it requires client access through the public
interfaces.
namespace CollegeCourseImpl.Impl1
{
internal class CollegeCourse : ICollegeCourse, IDataAccessService
{
internal string courseName;
internal string courseNumber;
internal string term;
internal string courseDescription;
internal string instructor;
internal int creditHours = 0;
internal List<string> enrolledStudents = new List<string>();
}
internal class CollegeCourseImplFactory : ICollegeFactory
{
private long currentAUID = 0L;
public ICollegeCourse GetNewCourse()
{
CollegeCourse retval = new CollegeCourse();
currentAUID = currentAUID - 1;
retval.AUID = currentAUID;
return retval;
}
}
}
namespace CollegeCourseImpl.Impl2
{
internal class CollegeCourse : ICollegeCourse, IDataAccessService
{
private static readonly string MinorFieldSeparator = "++";
internal string[] fields = new string[] { "", "", "", "", "", "0", "" };
}
internal class CollegeCourseImplFactory : ICollegeFactory
{
public ICollegeCourse GetNewCourse()
{
CollegeCourse retval = new CollegeCourse();
retval.AUID = -999L;
return retval;
}
}
}
In the above, many of the fields are marked internal
. These could easily be private (and probably should be) but are here internal
because:
- It is a frequent usage of
internal
to allow access to unit testing assemblies and these would likely be internal for that reason, and - To illustrate and underline the obvious danger to encapsulation if access to these fields were not controlled.
Similarly, there are two data access implementations, each one is again intentionally different, and are shown abridged for clarity.
namespace DataAccessImpl.Impl1
{
internal class DataAccess : IDataAccess
{
private DataTable mockDatabase;
}
}
namespace DataAccessImpl.Impl2
{
internal class DataAccess : IDataAccess
{
private long insertionCount = 0L;
private Dictionary<long> mockDatabase;
private Dictionary<long> insertionList;
private Dictionary<long> updateList;
}
}
The data access implementations are kept in an assembly separate from the college course implementations, and there are no references between the two implementations. Each implementation assembly has exactly one reference to the interface assembly, and the implementation assemblies are friends of the interface assembly so that they can have access to the IDataAccessService
interface, but not to any implementation details. A client assembly may now reference all three assemblies without being a friend of any assembly, is constrained to use business objects and invoke services through the public
interfaces only. And if it is desired to grant internal
access to unit test assemblies, these need be the only friends of the implementations.
Using the Code
The code was developed in C# on Visual Studio 2005 using .NET 2.0. It consists of three library projects and one console project. Simply download the code example, compile, and run the console application. The console application will run through a test which tests each of the two business implementations against each of the two data access implementations using only the public
client interfaces. This is the test provided.
static void RunTest(Implementation impl)
{
ICollegeCourse course = impl.Factory.GetNewCourse();
course.CourseDescription = "This is a College Course";
course.CourseName = "A College Course";
course.CourseNumber = "CC 101";
course.Instructor = "George Washington";
course.Term = "Fall 2009";
course.EnrollStudent("Moe");
course.EnrollStudent("Larry");
course.EnrollStudent("Curly");
long courseID = impl.DataAccess.Save(course);
impl.DataAccess.AcceptChanges();
System.Threading.Thread.Sleep(50);
course.Instructor = "Thomas Jefferson";
impl.DataAccess.Save(course);
impl.DataAccess.AcceptChanges();
ICollegeCourse course2 = impl.DataAccess.Load(courseID);
Debug.Assert(course.CourseName == course2.CourseName);
Debug.Assert(course.CourseNumber == course2.CourseNumber);
Debug.Assert(course.Term == course2.Term);
Debug.Assert(course.Instructor == course2.Instructor);
Debug.Assert(course.CourseDescription == course2.CourseDescription);
Debug.Assert(course.GetEnrolledStudents().Length ==
course2.GetEnrolledStudents().Length);
}
Conclusion
Using internal
interfaces, the above code allows for both toy business implementations to work with both toy data access implementations. No references exist between the assemblies containing the implementations. The client code furthermore has no access to the internal
interface used in data access and object creation. Encapsulation has been maintained and internal functionality hidden from the client code.
A common use of the internal
keyword is to allow unit test code to access implementation code encapsulation for more detailed testing or to use popular frameworks externally (such as nUnit). This solution supports those cases by making it much safer: Since the internal
interfaces are located in another assembly, the implementation assemblies do not need to have any other friend assemblies, so the testing assemblies can be the only ones. (This is the most popular use as documented at Stack Overflow.)
In the above example data access implementations, there is a subtle design problem that is interesting to point out. The Impl2
data access implementation uses the ToStringRep()
and FromStringRep()
methods, thus delegating some data access formatting responsibility back to the CollegeCourse
implementations. This has an unfortunate side effect: An Impl1 CollegeCourse
object is not directly transformable to an Impl2 CollegeCourse
object across the Impl2
data access layer, because the Impl1 FromStringRep()
may not parse an Impl2 ToStringRep() string
. Note that I did not have to change the interface to break this, only the implementation! There is no such problem with the Impl1
data access layer, which sticks to the simple public
interface of ICollegeCourse
to accomplish data access.
History
- 19th October, 2009: Initial post
- 27th Otober, 2009: Article updated
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.