7

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);
    }
    
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • 3
    You know that if you use a struct through its interface, the struct is boxed, right? http://stackoverflow.com/questions/63671/is-it-safe-for-structs-to-implement-interfaces/63757#63757 – xanatos Mar 12 '11 at 14:08
  • 1
    **@delnan**, are you referring to "limiting the number of implementations of an interface" (then see the "footnote" in the middle of my answer), or to "doing discriminated unions in C#"? **@xanatos**, good point! I didn't think about that, as I'm not too concerned about performance right now... or would you suggest a simple change from `struct` to `class`, even though that doesn't make sense (as these types should have value semantics)? – stakx - no longer contributing Mar 12 '11 at 14:22
  • @xanatos - there is an exception to that (isn't there always?), but this isn't it... – Marc Gravell Mar 12 '11 at 14:58
  • 1
    xanatos a generic type T with a generic interface constraint T : ISomeInterface; this is then **constrained** rather than boxed; meaning the appropriate call is made *without* needing to box. there is a special op-code just for this. – Marc Gravell Mar 12 '11 at 15:03
  • (it also applies to a non-generic method on a generic type, obviously) – Marc Gravell Mar 12 '11 at 15:05
  • Hi, I mae a library for doign DU's in c#. You have a base class which has an internal ctor, and you can seal the subtypes. https://github.com/mcintyre321/OneOf – mcintyre321 Aug 19 '16 at 09:20

3 Answers3

13

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
6

I think your general approach is going in the right direction, but it looks like you can simplify your code using abstract classes:

public abstract class Relation
{
    internal Relation(object subject, object obj)
    {
        Subject = subject;
        Object = obj;
    }
    public object Subject { get; private set; }
    public object Object { get; private set; }
}

public sealed class LessThanRelation : Relation
{
    public LessThanRelation(object subject, object obj) : base(subject, obj) { }
}

public sealed class EqualToRelation : Relation
{
    public EqualToRelation(object subject, object obj) : base(subject, obj) { }
}

public sealed class GreaterThanRelation : Relation
{
    public GreaterThanRelation(object subject, object obj) : base(subject, obj) { }
}

Outside assemblies can see all the members of the Relation class except the internal constructor -- which, from the outside, makes it appear that the class has no constructors defined, so its not possible for third-party assemblies to define their own implementations.

Juliet
  • 80,494
  • 45
  • 196
  • 228
  • Thanks, Jon Skeet, for the blog post which inspired this answer :) http://msmvps.com/blogs/jon_skeet/archive/2010/10/03/the-curious-case-of-the-publicity-seeking-interface-and-the-shy-abstract-class.aspx – Juliet Mar 13 '11 at 05:47
  • 1
    Or, as xanatos suggests in a comment to another answer, you could just make the base class' constructor `internal` and drop the `GetNull` method...? – stakx - no longer contributing Mar 13 '11 at 13:54
  • +1 @stakx: don't know why I didn't think of that :) Updated answer accordingly. – Juliet Mar 13 '11 at 18:05
3

I would go with the idea based on enum. In fact, I would use that solution in F# too. Since you always have only two arguments, you don't really need discriminated union:

// Note: with numbers assigned to cases, this becomes enum
type RelationType =      
  | LessThan = 1
  | EqualTo = 2
  | GreaterThan = 3

// Single-case union (could be record, depending on your style)
type Relation = 
  | BinaryRelation of RelationType * obj * obj

In general, if you want to encode discriminated union in C# then it is probably the best option to use abstract base class and then inherited class for each cases (with additional fields). Since you're not planing to extend it by adding new subclasses, you can define tag enum that lists all the possible subtypes (so that you can easily implement "pattern matching" by switch on the tag).

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • concerning your F# suggestion: I suppose the difference is only in the way one will match patterns, e.g.: `match relation with | LessThan (_, _) -> …` vs. `match relation with | (LessThan, _, _) -> …`. Are there any performance or memory benefits to the latter approach? – stakx - no longer contributing Mar 13 '11 at 13:57