Обходной путь для варианта использования классов друзей в C # - PullRequest
12 голосов
/ 07 мая 2011

Рассмотрим следующую кодовую комбинацию:

// 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.

В C ++ можно было бы объявить FooManager другом Foo, чтобы выразить намерение проекта, но это невозможно в C #. Я также знаю, что мог бы сделать Foo внутренним классом FooManager для получения доступа, но это тоже не решение (что, если Foo может принадлежать более чем одному классу менеджера?)

Btw. Я знаю о «внутреннем» доступе в .NET, но он требует, чтобы Foo и FooManager жили отдельно в отдельной сборке, что недопустимо.

Есть ли какие-либо обходные пути для этого без обнародования личных вещей?

Ответы [ 7 ]

7 голосов
/ 07 мая 2011

Если я все правильно понимаю:

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;
    }
}
3 голосов
/ 08 мая 2011

Что вы можете сделать, это создать ваши классы внутри другого типа пространства имен, давайте назовем его «модулем» (не обманывайте себя ключевым словом class, это не реальный класс ):

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

}

Поскольку «модуль» является частичным классом, вы все равно можете создавать внутри него другие члены в других файлах в том же модуле компиляции.

3 голосов
/ 08 мая 2011

Одним из вариантов будет использование закрытого вложенного класса для Foo, который реализует открытый интерфейс:

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 является закрытым вложенным классом, его нельзя создать вне FooManager,и поэтому метод CreateFoo() FooManager гарантирует, что все будет синхронизировано.

0 голосов
/ 12 марта 2016

Как насчет этого:

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());
    }
}

Полагаю, можно обойти это, но любые идеи, которые у меня появляются, становятся чрезмерными.

0 голосов
/ 14 февраля 2015

Я подумываю об использовании отражения.

Нет хорошего решения для неудачного дизайна (недостаток друзей в CLR).

0 голосов
/ 08 мая 2011

В этом примере единственными, кто может вывести связь из синхронизации, являются 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);
    }
}
0 голосов
/ 08 мая 2011

Как насчет базового класса:

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

Тогда Foo и FooManager имеют FooBase в качестве базового класса и могут обновить свое отображение, не подвергая его внешнему воздействию.Тогда вы можете быть уверены, что никто не будет изменять эту коллекцию извне.

Тогда в Foo у вас есть свойство Manager, которое будет возвращать связанный с ним менеджер и тому подобное, свойство Foos в Manager,даст все фу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...