C#中的类型安全区分联合,或者:如何限制接口的实现数量?

sta*_*ica 7 c# f# interface algebraic-data-types discriminated-union

首先,对于冗长的帖子感到抱歉.基本上,我的问题是:

我试图在C#中重现以下F#区别联合类型:

type Relation =
     | LessThan of obj * obj
     | EqualTo of obj * obj
     | GreaterThan of obj * obj
Run Code Online (Sandbox Code Playgroud)

任何人都可以建议一个比以下更简单的基于接口的解决方案?


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 { … }
Run Code Online (Sandbox Code Playgroud)

我的所有算法都识别这三种关系类型,而这些只是这些,所以我需要阻止IRelation第三方(即其他程序集)的任何进一步实现.

脚注:对某些人来说,如果我只是在面向对象/多态性方面得到了我的接口和算法,那么可能会出现第三方实现被注入到我的算法方法中并不重要,只要接口是正确实施.这是一个有效的批评.但是,让我们假设,在这种情况下,我倾向于在严格的面向对象上采用更多功能编程风格.

我的最好的办法,到目前为止是宣布所有以上类型的internal(即它们将永远不会被外人直接看到.),并创建一个代理类型Relation,这将是唯一可见的类型的第三方:

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
}
Run Code Online (Sandbox Code Playgroud)

到目前为止一切都很好,但它更精细......

  • ...如果我公开具体关系类型的工厂方法:

    public Relation CreateLessThanRelation(…)
    {
        return new Relation { value = new LessThanRelation { … } };
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • ...每当我公开处理关系类型的算法时,因为我必须从/向代理类型映射:

    public … ExposedAlgorithm(this IEnumerable<Relation> relations)
    {
        // forward unwrapped IRelation objects to an internal algorithm method:
        return InternalAlgorithm(from relation in relations select relation.value);
    }
    
    Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 13

限制接口实现意味着它不是真正充当接口(它应该接受任何实现(替换),例如装饰器) - 所以我不能推荐它.

另外,请注意,除了泛型之外,将结构视为接口会导致装箱.

这留下了一个有趣的案例; 带有私有构造函数的抽象类,以及作为嵌套类型的已知数量的实现,这意味着它们可以访问私有构造函数.

现在你控制子类型,装箱不是问题(因为它是一个类),并且对替换的期望较少.


Jul*_*iet 6

我认为您的一般方法正朝着正确的方向发展,但看起来您可以使用抽象类来简化代码:

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) { }
}
Run Code Online (Sandbox Code Playgroud)

外部程序集可以看到除了内部构造函数之外的所有Relation类成员- 从外部看,它看起来该类没有定义构造函数,因此第三方程序集不可能定义自己的实现.