Временное решение для примера использования классов друзей в С#
Рассмотрим следующий шаблон кода:
// 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 и обновить как список m_foos в FooManager, так и ссылку m_manager в новом экземпляре Foo без публичного публичного ознакомления некоторых публичных пользователей (и вероятность того, что кто-то удалит список с фактическими Foos),
например. можно было реализовать конструктор Foo (менеджер FooManager) в Foo. Он может установить ссылку m_manager, но у него нет доступа к списку m_foos. Или вы можете реализовать метод CreateFoo() в менеджере. Он может получить доступ к списку m_foos, но у него нет способа установить m_manager в Foo.
В С++ можно было бы объявить FooManager другом Foo, чтобы выразить намерение дизайна, но это невозможно в С#. Я также знаю, что я могу сделать Foo внутренним классом FooManager, чтобы получить доступ, но это тоже не решение (что, если Foo может принадлежать более чем одному классу менеджера?)
Btw. Я знаю о "внутреннем" доступе в .NET, но он требует, чтобы Foo и FooManager живут самостоятельно в отдельной сборке, что неприемлемо.
Любые обходные пути для этого, не публикуя публичные материалы?
Ответы
Ответ 1
Если я все правильно понимаю:
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;
}
}
Ответ 2
Один из вариантов - использовать частный вложенный класс для Foo, который реализует открытый интерфейс:
public interface IFoo
{
// Foo 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 является частным вложенным классом, он не может быть создан за пределами FooManager, поэтому метод FooManager CreateFoo()
гарантирует, что все будет оставаться в синхронизации.
Ответ 3
Что вы можете сделать, это создать свои классы внутри другого пространства имен, называть его "модулем" (не обманывать ключевое слово класса, это не реальный класс):
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);
}
}
}
Поскольку "модуль" является частичным классом, вы все равно можете создавать другие элементы внутри него в других файлах в одном компиляторе.
Ответ 4
Как насчет базового класса:
class FooBase
{
protected static readonly Dictionary<Foo,FooManager> _managerMapping = new Dictionary<Foo,FooManager>();
}
Затем Foo и FooManager имеют базовый класс FooBase и могут обновлять их сопоставление, не подвергая его внешнему воздействию. Тогда вы можете быть уверены, что никто не изменит эту коллекцию снаружи.
Тогда в Foo у вас есть свойство Manager
, которое вернет связанный с ним менеджер и подобное, свойство Foos в Manager
, которое даст все Foos.
Ответ 5
В этом примере единственными, которые могут получить связь вне синхронизации, являются Foo и сам менеджер. CreateFoo() вызывается в менеджере для создания "управляемого foo". Кто-то еще может создать Foo, но они не могут заставить его управлять менеджером без согласия менеджера.
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);
}
}
Ответ 6
Я рассматриваю возможность использования отражения.
Нет хорошего решения для неудавшегося проекта (отсутствие друзей в CLR).
Ответ 7
Как насчет чего-то вроде этого:
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
}
}
Конечно, это не мешает пользователю делать что-то вроде этого:
public class NonFriendClass
{
public void DoSomething()
{
MainClass.FriendOnly(new FriendClass());
}
}
Я полагаю, что это может быть способ обойти это, но любые идеи, которые я начинаю становиться чрезмерно чрезмерными.