First, sorry for the lengthy post. Basically, my question is this:
I'm trying to reproduce the following F# discriminated union type in C#:
type Relation =
| LessThan of obj * obj
| EqualTo of obj * obj
| GreaterThan of obj * obj
Can anyone suggest a simpler interface-based solution than the following?
interface IRelation // concrete types represent ◊ in the expression "Subject ◊ Object"
{
object Subject { get; }
object Object { get; }
}
struct LessThanRelation : IRelation { … }
struct EqualToRelation : IRelation { … }
struct GreaterThanRelation : IRelation { … }
All my algorithms recognise these three relation types, and these only, so I need to prevent any further implementations of IRelation
by third parties (i.e. other assemblies).
Footnote: To some, it might occur that if I just got my interface and algorithms right in terms of object orientation / polymorphism, it shouldn't matter that an third-party implementation is injected into my algorithm methods, as long as the interface is implemented correctly. This is a valid critique. But let's just assume that for the moment that I'm favouring a more functional-programming style over strict object-orientation in this case.
My best idea so far is to declare all above types as internal
(ie. they will never be seen directly by outsiders) and create a proxy type Relation
, which will be the only visible type to third parties:
public struct Relation // constructors etc. are omitted here for brevity's sake
{
public RelationType Type { get { … /* concrete type of value -> enum value */ } }
public Relation Subject { get { return value.Subject; } }
public Relation Object { get { return value.Object; } }
internal readonly IRelation value;
}
public enum RelationType
{
LessThan,
EqualTo,
GreaterThan
}
All is well so far, but it gets more elaborate…
… if I expose factory methods for the concrete relation types:
public Relation CreateLessThanRelation(…)
{
return new Relation { value = new LessThanRelation { … } };
}
… whenever I expose an algorithm working on relation types, because I must map from/to the proxy type:
public … ExposedAlgorithm(this IEnumerable<Relation> relations)
{
// forward unwrapped IRelation objects to an internal algorithm method:
return InternalAlgorithm(from relation in relations select relation.value);
}
Limiting the interface implementations means it isn't really acting as an interface (which should accept any implementation (substitution), such as decorators) - so I can't recommend that.
Also, note that with a small exception of generics, treating a struct as an interface leads to boxing.
So that leaves one interesting case; an abstract class with a private constructor, and a known number of implementations as nested types, which means that they have access to the private constructor.
Now you control the subtypes, boxing isn't an issue (as it is a class), and there is less expectation of substitution.