【问题标题】:C# workaround for mutual friend classes共同朋友类的 C# 解决方法
【发布时间】:2019-08-18 12:32:50
【问题描述】:

所以,我有一个类似于this one 的用例,但有一些额外的细节,我觉得需要提出一个新问题。 (relatedquestions,供参考)

我正在编写一个实现a cycle 的数据结构。基本设计是这样的:

public class Cycle<T>
{
    public Node<T> Origin { get; private set; }
    public int Count { get; private set; }
}

public class Node<T>
{
    public Cycle<T> Cycle { get; private set; }
    public Node<T> Next { get; private set; }
    public Node<T> Previous { get; private set; }
    public T Value { get; set; }
}

但是,我想实现以下所有行为:

  1. 在插入/删除节点时更新 Cycle.Count
  2. 允许“空”Cycle(即Origin = null)为Origin 创建一个新的Node
  3. 防止在案例 1 和 2 之外构造新的 Node 对象
  4. 避免暴露不需要由这两个以外的类调用的方法

在 C++ 中,我只需将每个类设为另一个类的 friend。但是在 C# 中,我看不到让它工作的方法。

我知道如果我将Node 嵌套在Cycle 内并且只为Node 公开一个构造函数,我可以实现除#3 之外的所有内容,如下所示:

public class Cycle<T>
{
    public Node Origin { get; private set; }
    public int Count { get; private set; }
    
    public class Node
    {
        public Cycle<T> Cycle { get; private set; }
        public Node Next { get; private set; }
        public Node Previous { get; private set; }
        public T Value { get; set; }
        
        internal Node<T>(Cycle<T> cycle)
        {
            if (cycle.Origin != null)
                throw new InvalidOperationException();
            else
            {
                Cycle = cycle;
                Next = this;
                Previous = this;
                cycle.Origin = this;
                cycle.Count = 1;
            }
        }
    }
}

但是如你所见,我只能到internal,所以我还是要验证数据完整性或者破坏封装。

我确实有一个“聪明”的想法,但这是一种黑魔法的答案:

public abstract class Cycle<T>
{
    public Node Origin { get; private set; }
    public int Count { get; private set; }
    
    public sealed class Node
    {
        private CycleInternal _cycle;
        public Cycle<T> Cycle { get { return _cycle; } }
        public Node Next { get; private set; }
        public Node Previous { get; private set; }
        public T Value { get; set; }
        
        // this constructor can be called by CycleInternal, but not other classes!
        private Node(CycleInternal cycle)
        {
            Cycle = cycle;
            Next = this;
            Previous = this;
            cycle.Origin = this;
            cycle.Count = 1;
        }
        
        private sealed class CycleInternal :  Cycle<T>
        {
            // this constructor can be called by Node, but not other classes!
            public CycleInternal() {}
        }
    }
}

在这种情况下,我担心其他东西可以从 Cycle 继承;可以通过将构造函数设置为 Cycle 私有来防止这种情况吗?还是我只是在这里偏执?

【问题讨论】:

  • 使用internal有什么问题?您想阻止这里发生什么?
  • 也许我没有得到主题,但为什么在节点内有一个 Cycle 实例?你不是还需要一个 IEnumerable> 在 Cycle 中吗?
  • @Matthew internal 将它暴露给同一程序集中的其他类,我不想这样做。这是图书馆的一部分,我不想冒险任何事情我不需要。 @DavideVitali 每个Node 都在跟踪它属于哪个Cycle,以便计算Cycle.Count,检查两个Nodes 是否属于同一个Cycle,并更轻松地更新Cycle.Origin . Cycle 只需要引用一个节点就可以实现 IEnumerable&lt;Node&lt;T&gt;&gt; 本身。

标签: c# inner-classes encapsulation friend


【解决方案1】:

internal 将其暴露给同一程序集中的其他类,我不想这样做

我的建议是:克服这种恐惧并标记它internal

我听说过这个特性请求——C# 实现 C++ 风格的友元语义——很多很多次。该功能的动机通常是担心“如果我允许我的议会中的任何班级就内部状态进行聚会,我的同事将滥用该特权”。

但是您的同事已经有能力滥用该特权,因为您的同事已经可以添加朋友(在 C++ 中)或创建作为后门的内部方法(在C#)。

C# 可访问性系统根本不是为了保护您免受对源代码具有写入权限的人对您的兴趣怀有敌意的情况。正如private 的意思是“这是这个类的一个实现细节”,protected 的意思是“这是这个层次结构的一个实现细节,internal 的意思是“这是这个组件的一个实现细节”。如果你的同事谁负责该程序集的正确操作不能被信任明智地使用他们的权力,获得更好的同事。如果您的类型需要访问彼此的内部详细信息以使程序集作为一个整体正常工作,这就是@987654326 @ 代表,所以使用它。

或者,换句话说,这是一个社会问题,而不是技术问题,所以你应该通过施加社会压力来解决它。 C# 可访问性系统并非旨在解决应通过代码审查解决的问题。如果您的同事滥用他们的权限,代码审查时间就是让他们停止的时间,就像您使用代码审查来阻止他们向您的项目添加任何其他不良代码一样。

回答您的具体问题:

我担心的是其他东西可以从 Cycle 继承;可以通过将构造函数设置为 Cycle 私有来防止这种情况吗?

是的,一个抽象类可以有一个私有构造函数,那么唯一的派生类型就是嵌套类型。

我经常使用该模式,并将嵌套类型设为私有。在 C# 中创建公开的嵌套类型是一种不好的气味。

旁白:通常我这样做是为了制作“案例类”,例如:

abstract class ImmutableStack<T>
{
  private ImmutableStack() { }
  private sealed class EmptyStack : ImmutableStack<T> { ... }
  private sealed class NormalStack : ImmutableStack<T> { ... }

等等。这是一种很好的方法,可以让一个类型的实现细节分布在多个类中,但仍然全部封装到一个类中。

我只是在这里偏执吗?

在我看来是的。

【讨论】:

  • 我不担心任何故意滥用,我担心有人(包括我未来的自己)会误解如何使用课程,并做某事(在一个不同的文件)编译良好但在运行时失败(可能是静默)。我宁愿让失败成为不可能(只要没有人编辑课程本身)。
  • @TravisReed 会提供详尽的文档帮助吗?包括做什么和不做什么。代码示例。等
  • @TravisReed:在这种情况下,您需要两件事:首先,一个测试套件来验证该类型的 public 行为,其次,整个过程中有大量 Debug.Assert 语句您的代码验证必须由代码维护的内部不变量。在签入每个更改之前,在调试模式下运行测试套件,如果将来您破坏了公共行为或内部不变量,您将收到通知,您可以解决问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-19
相关资源
最近更新 更多