关于.net:C#中的朋友类用例的解决方法

Workaround for an use-case of friend classes in C#

考虑以下代码模式:

1
2
3
4
5
6
7
8
9
10
11
// Each foo keeps a reference to its manager
class Foo
{
    private FooManager m_manager;
}

// Manager keeps a list of all foos
class FooManager
{
    private List<Foo> m_foos;
}

问题:无法创建新的foo并在foomager中同时更新m_foos列表,也无法在新foo实例中更新m_manager引用,而不会公开公开某些特权(并且存在有人将该列表与实际foos取消同步的风险)。

例如,可以在foo中实现构造函数foo(foomager管理器)。它可以设置m_管理器引用,但无法访问m_foos列表。或者可以在管理器中实现createfoo()方法。它可以访问m foo列表,但无法在foo中设置mfoo管理器。

在C++中,很明显地声明FooManger-Foo的一个朋友来表达设计意图,但是这在C语言中是不可能的。我也知道我可以让foo成为foomager的一个内部类来获得访问权,但这也不是一个解决方案(如果foo可以属于多个manager类呢?)

顺便说一句,我知道.NET中的"内部"访问,但它要求foo和foomager单独生活在一个程序集中,这是不可接受的。

在不公开私人物品的情况下,有什么解决办法吗?


如果我理解正确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class FooBus
{
    protected static FooBus m_bus;
}

public sealed class Foo : FooBus
{
    private FooManager m_manager;

    public Foo(FooManager fm)
    {
        if (fm == null)
        {
            throw new ArgumentNullException("Use FooManager.CreateFoo()");
        }

        if (m_bus != fm)
        {
            throw new ArgumentException("Use FooManager.CreateFoo()");
        }

        m_manager = fm;
    }
}

public class FooManager : FooBus
{
    private List<Foo> m_foos = new List<Foo>();

    public Foo CreateFoo()
    {
        m_bus = this;
        Foo f = new Foo(this);
        m_foos.Add(f);
        m_bus = null;

        return f;
    }
}


您可以在不同类型的命名空间中创建类,我们称其为"模块"(不要被class关键字愚弄,这不是真正的类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static partial class FooModule {

  // not visible outside this"module"
  private interface IFooSink {
    void Add(Foo foo);
  }

  public class Foo {
    private FooManager m_manager;
    public Foo(FooManager manager) {
      ((IFooSink)manager).Add(this);
      m_manager = manager;
    }
  }

  public class FooManager : IFooSink {
    private List<Foo> m_foos = new List<Foo>();
    void IFooSink.Add(Foo foo) {
      m_foos.Add(foo);
    }
  }

}

由于"module"是一个分部类,所以您仍然可以在同一编译单元的其他文件中创建它内部的其他成员。


一个选项是为实现公共接口的foo使用私有嵌套类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface IFoo
{
    // Foo's interface
}

public sealed class FooManager
{
    private readonly List<Foo> _foos = new List<Foo>();

    public IFoo CreateFoo()
    {
        var foo = new Foo(this);
        _foos.Add(foo);
        return foo;
    }

    private class Foo : IFoo
    {
        private readonly FooManager _manager;

        public Foo(FooManager manager)
        {
            _manager = manager;
        }
    }
}

由于foo类是一个私有的嵌套类,它不能在foomager之外创建,因此foomager的CreateFoo()方法确保所有内容保持同步。


像这样的怎么样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FriendClass
{
     public void DoSomethingInMain()
     {
         MainClass.FriendOnly(this);
     }
}

public class MainClass
{
    public static void FriendOnly(object Caller)
    {
        if (!(Caller is FriendClass) /* Throw exception or return */;

        // Code
    }
}

当然,这并不妨碍用户这样做:

1
2
3
4
5
6
7
public class NonFriendClass
{
    public void DoSomething()
    {
         MainClass.FriendOnly(new FriendClass());
    }
}

我想有办法解决这个问题,但我的任何想法都开始变得过分了。


我正在考虑使用反射。

对于失败的设计(clr中缺少朋友)没有好的解决方案。


在本例中,唯一可以使关系不同步的是foo和管理器本身。在管理器上调用create foo()以创建"托管foo"。其他人可以创建一个foo,但如果没有经理的"同意",他们就无法让经理管理它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Foo
{
    private FooManager m_manager;

    public void SetManager(FooManager manager)
    {
        if (manager.ManagesFoo(this))
        {
            m_manager = manager;
        }
        else
        {
            throw new ArgumentException("Use Manager.CreateFoo() to create a managed Foo");
        }
    }
}

public class FooManager
{
    private List<Foo> m_foos = new List<Foo>();

    public Foo CreateFoo()
    {
        Foo foo = new Foo();
        m_foos.Add(foo);
        foo.SetManager(this);

        return foo;
    }

    public bool ManagesFoo(Foo foo)
    {
        return m_foos.Contains(foo);
    }
}


有一个基类如何?

1
2
3
4
class FooBase
{
     protected static readonly Dictionary<Foo,FooManager> _managerMapping = new Dictionary<Foo,FooManager>();
}

然后foo和foomager将foo base作为基类,可以在不对外公开的情况下更新它们的映射。然后您可以确保没有人会从外部更改此集合。

然后在foo中,您有一个属性Manager,它将返回与之相关联的经理,类似的,Manager中的foos属性将提供所有foo。