7

首先,很抱歉这篇冗长的帖子。基本上,我的问题是:

我正在尝试在 C# 中重现以下 F# 可区分联合类型:

type Relation =
     | LessThan of obj * obj
     | EqualTo of obj * obj
     | GreaterThan of obj * obj

谁能提出比以下更简单的基于接口的解决方案?


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 { … }

我所有的算法都识别这三种关系类型,而且只有这些,所以我需要防止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
}

到目前为止一切都很好,但它变得更加复杂......

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

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

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

3 回答 3

13

限制接口实现意味着它并没有真正充当接口(它应该接受任何实现(替换),例如装饰器) - 所以我不推荐这样做。

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

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

现在您控制了子类型,拳击不是问题(因为它是一个类),并且对替换的期望更少。

于 2011-03-12T14:23:57.770 回答
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) { }
}

外部程序集可以看到除Relation内部构造函数之外的类的所有成员——从外部看,这使得该类似乎没有定义构造函数,因此第三方程序集无法定义自己的实现。

于 2011-03-13T05:43:15.787 回答
3

我会采用基于enum. 事实上,我也会在 F# 中使用该解决方案。由于您始终只有两个参数,因此您实际上并不需要有区别的联合:

// 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

一般来说,如果你想在 C# 中编码有区别的联合,那么最好的选择是使用抽象基类,然后为每种情况使用继承类(带有附加字段)。由于您不打算通过添加新的子类来扩展它,因此您可以定义列出所有可能子类型的标签枚举(以便您可以通过switch标签轻松实现“模式匹配”)。

于 2011-03-12T16:56:01.887 回答