How to reserve public API to internal usage in .NET?
- by mark
Dear ladies and sirs.
Let me first present the case, which will explain my question. This is going to be a bit long, so I apologize in advance :-).
I have objects and collections, which should support the Merge API (it is my custom API, the signature of which is immaterial for this question). This API must be internal, meaning only my framework should be allowed to invoke it. However, derived types should be able to override the basic implementation.
The natural way to implement this pattern as I see it, is this:
The Merge API is declared as part of some internal interface, let us say IMergeable.
Because the interface is internal, derived types would not be able to implement it directly. Rather they must inherit it from a common base type. So, a common base type is introduced, which would implement the IMergeable interface explicitly, where the interface methods delegate to respective protected virtual methods, providing the default implementation. This way the API is only callable by my framework, but derived types may override the default implementation.
The following code snippet demonstrates the concept:
internal interface IMergeable
{
void Merge(object obj);
}
public class BaseFrameworkObject : IMergeable
{
protected virtual void Merge(object obj)
{
// The default implementation.
}
void IMergeable.Merge(object obj)
{
Merge(obj);
}
}
public class SomeThirdPartyObject : BaseFrameworkObject
{
protected override void Merge(object obj)
{
// A derived type implementation.
}
}
All is fine, provided a single common base type suffices, which is usually true for non collection types.
The thing is that collections must be mergeable as well. Collections do not play nicely with the presented concept, because developers do not develop collections from the scratch. There are predefined implementations - observable, filtered, compound, read-only, remove-only, ordered, god-knows-what, ... They may be developed from scratch in-house, but once finished, they serve wide range of products and should never be tailored to some specific product. Which means, that either:
they do not implement the IMergeable interface at all, because it is internal to some product
the scope of the IMergeable interface is raised to public and the API becomes open and callable by all.
Let us refer to these collections as standard collections. Anyway, the first option screws my framework, because now each possible standard collection type has to be paired with the respective framework version, augmenting the standard with the IMergeable interface implementation - this is so bad, I am not even considering it.
The second option breaks the framework as well, because the IMergeable interface should be internal for a reason (whatever it is) and now this interface has to open to all.
So what to do?
My solution is this. make IMergeable public API, but add an extra parameter to the Merge method, I call it a security token. The interface implementation may check that the token references some internal object, which is never exposed to the outside. If this is the case, then the method was called from within the framework, otherwise - some outside API consumer attempted to invoke it and so the implementation can blow up with a SecurityException.
Here is the modified code snippet demonstrating this concept:
internal static class InternalApi
{
internal static readonly object Token = new object();
}
public interface IMergeable
{
void Merge(object obj, object token);
}
public class BaseFrameworkObject : IMergeable
{
protected virtual void Merge(object obj)
{
// The default implementation.
}
public void Merge(object obj, object token)
{
if (!object.ReferenceEquals(token, InternalApi.Token))
{
throw new SecurityException("bla bla bla");
}
Merge(obj);
}
}
public class SomeThirdPartyObject : BaseFrameworkObject
{
protected override void Merge(object obj)
{
// A derived type implementation.
}
}
Of course, this is less explicit than having an internally scoped interface and the check is moved from the compile time to run time, yet this is the best I could come up with.
Now, I have a gut feeling that there is a better way to solve the problem I have presented.
I do not know, may be using some standard Code Access Security features? I have only vague understanding of it, but can LinkDemand attribute be somehow related to it? Anyway, I would like to hear other opinions.
Thanks.