Solução alternativa para um caso de uso de classs de amigos em c #

Considere o seguinte padrão de código:

// 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 m_foos; } 

Problema: não há como criar um novo Foo e atualizar tanto a lista m_foos no FooManager, quanto a referência m_manager na nova instância Foo sem expor publicamente alguns privilégios (e correr o risco de alguém desescalonar a lista com Foos reais).

Por exemplo, pode-se implementar um construtor Foo (FooManager manager) no Foo. Poderia definir a referência m_manager, mas não tem como acessar a lista m_foos. Ou você poderia implementar o método CreateFoo () no gerenciador. Ele pode acessar a lista m_foos, mas não tem como configurar o m_manager no Foo.

Em C ++, obviamente seria declarado ao FooManager um amigo de Foo para expressar a intenção do projeto, mas isso não é possível em C #. Eu também sei que eu poderia fazer de Foo uma class interna do FooManager para obter access, mas isso também não é uma solução (e se Foo pudesse pertencer a mais de uma class de gerentes?)

Btw. Eu sei sobre o access “interno” no .NET, mas requer que o Foo e o FooManager vivam sozinhos em uma assembly separada, o que não é aceitável.

Quaisquer soluções alternativas para isso sem tornar as coisas privadas públicas?

Se eu entendi tudo corretamente:

 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 m_foos = new List(); public Foo CreateFoo() { m_bus = this; Foo f = new Foo(this); m_foos.Add(f); m_bus = null; return f; } } 

Uma opção seria usar uma class aninhada privada para Foo que implementa uma interface pública:

 public interface IFoo { // Foo's interface } public sealed class FooManager { private readonly List _foos = new List(); 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; } } } 

Como a class Foo é uma class aninhada privada, ela não pode ser criada fora do FooManager e, portanto, o método CreateFoo() do FooManager garante que tudo fique sincronizado.

O que você pode fazer é criar suas classs dentro de um tipo diferente de namespace, vamos chamá-lo de “módulo” (não se deixe enganar pela palavra-chave class, isso não é uma class real ):

 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 m_foos = new List(); void IFooSink.Add(Foo foo) { m_foos.Add(foo); } } } 

Como o “módulo” é uma class parcial, você ainda pode criar outros membros dentro dele em outros arquivos na mesma unidade de compilation.

Que tal ter uma class base:

 class FooBase { protected static readonly Dictionary _managerMapping = new Dictionary(); } 

Então Foo e FooManager têm FooBase como class base e podem atualizar seu mapeamento sem expor externamente. Então você pode ter certeza de que ninguém irá alterar essa coleção de fora.

Então, em Foo você tem um Manager propriedade que irá retornar o gerente associado a ele e semelhante, uma propriedade Foos no Manager que dará todas as Foos.

Neste exemplo, os únicos que podem deixar o relacionamento fora de sincronia são o próprio Foo e o gerente. CreateFoo () é chamado no gerenciador para criar um “gerenciado foo”. Alguém pode criar um Foo, mas não consegue que ele seja gerenciado por um gerente sem que o gerente concorde.

 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 m_foos = new List(); 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); } } 

Eu estou pensando em usar a reflection.

Não há uma boa solução para um projeto com falha (falta de amigos no CLR).

Que tal algo como isso:

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

Claro que isso não impede que o usuário faça algo assim:

 public class NonFriendClass { public void DoSomething() { MainClass.FriendOnly(new FriendClass()); } } 

Eu suponho que pode haver uma maneira de contornar isso, mas qualquer idéia que eu comece a se tornar excessiva.