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

C# C中的类型安全区分联合,或者:如何限制接口实现的数量?,c#,interface,f#,discriminated-union,algebraic-data-types,C#,Interface,F#,Discriminated Union,Algebraic Data Types,首先,很抱歉发了这么长的帖子。基本上,我的问题是: 我试图在C中复制以下F区分的联合类型: 类型关系= |obj的LessThan*obj |obj的等额*obj |比obj大*obj 有人能推荐一个比下面更简单的基于接口的解决方案吗 我所有的算法都只识别这三种关系类型,因此我需要防止第三方(即其他程序集)进一步实现IRelation 脚注:对一些人来说,如果我只是在面向对象/多态性方面正确地获得了接口和算法,那么在我的算法方法中注入第三方实现应该无关紧要,只要接口实现正确。这是一个有效的批评。

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

我试图在C中复制以下F区分的联合类型:

类型关系= |obj的LessThan*obj |obj的等额*obj |比obj大*obj 有人能推荐一个比下面更简单的基于接口的解决方案吗

我所有的算法都只识别这三种关系类型,因此我需要防止第三方(即其他程序集)进一步实现IRelation

脚注:对一些人来说,如果我只是在面向对象/多态性方面正确地获得了接口和算法,那么在我的算法方法中注入第三方实现应该无关紧要,只要接口实现正确。这是一个有效的批评。但是让我们假设目前我更喜欢函数式编程风格,而不是严格的面向对象

到目前为止,我的最佳想法是将上述所有类型声明为内部ie。外部人员永远不会直接看到它们,并创建代理类型关系,这将是第三方唯一可见的类型:

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);
}
限制接口实现意味着它实际上不是一个应该接受任何实现替代的接口,比如装饰器,所以我不建议这样做

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

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


现在您控制了子类型,装箱不再是一个问题,因为它是一个类,对替换的期望也更少

我同意基于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中编码有区别的并集,那么最好的选择可能是使用抽象基类,然后在每个情况下使用继承类和附加字段。由于您不打算通过添加新的子类来扩展它,因此可以定义列出所有可能的子类型的标记枚举,这样您就可以通过打开标记轻松实现模式匹配。

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

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

外部程序集可以看到关系类的所有成员,但内部构造函数除外,从外部看,该类似乎没有定义构造函数,因此第三方程序集不可能定义自己的实现。

您知道,如果通过其接口使用结构,结构是装箱的,对吗@DelnAn,你是指限制接口的实现数量,然后在回答的中间看到脚注,或者在C?@中做判别的联合。xanatos,说得好!我没有想到这一点,因为我现在不太关心性能。。。或者你会建议从结构到类进行简单的更改,尽管这没有意义,因为这些类型应该具有值语义?@xanatos-有一个例外,但并不总是存在,但这不是…xanatos是一个具有泛型接口约束t的泛型类型t:ISomeInterface;然后将其约束而不是装箱;这意味着在不需要打电话框的情况下拨打适当的电话。这里有一个特殊的操作代码。它也适用于泛型类型上的非泛型方法,很明显,我希望我的实现类型是结构,这就是我最初使用接口的原因,因为继承不适用于值类型,但是你确实提出了一个很好的反对理由。使用一个没有嵌套类但在同一个程序集中的内部构造函数?@xanatos-是的,非嵌套和内部是关于sameThanks的,Jon Skeet,这篇博文启发了这个答案:或者,正如xanatos在对另一个答案的评论中所建议的,你可以将基类的构造函数设置为内部,并删除GetNull方法…?+1@stakx:不知道为什么我没有想到这一点:相应地更新了答案。关于你的F建议:我想区别只是在于匹配模式的方式,例如:与| LessThan |,->…的匹配关系与| LessThan |的匹配关系, _, _ -> …. 后一种方法对性能或内存是否有好处?
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) { }
}