12

Consider the following code pattern:

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

Problem: there is no way to create a new Foo and update both m_foos list in the FooManager, and m_manager reference in the new Foo instance without exposing some privates publicly (and running the risk of someone desyncing the list with actual Foos).

E.g. one could implement a constructor Foo(FooManager manager) in Foo. It could set m_manager reference, but it has no way to access the m_foos list. Or you could implement CreateFoo() method in the manager. It can access m_foos list, but it has no way to set m_manager in Foo.

In C++, one would obviously declare FooManager a friend of Foo to express the design intent, but this is not possible in C#. I also know that I could make Foo an inner class of FooManager to gain access, but this is not a solution either (what if Foo could belong to more than one manager class?)

Btw. I know about "internal" access in .NET, but it requires that Foo and FooManager live on their own in a separate assembly, which is not acceptable.

Any workarounds for that without making private stuff public?

kaalus
  • 4,475
  • 3
  • 29
  • 39
  • 3
    There's always the InternalsVisibleToAttribute, but that sounds like an hack. How about hiding stuff behind an interface? – sisve May 07 '11 at 14:18
  • How would such an interface look like without allowing the user to make m_foos and m_manager to go out of sync? – kaalus May 07 '11 at 15:56
  • Can FooManager be turned into a factory for Foo? Then the manager knows all the Foo that it created . – Godeke May 07 '11 at 16:53
  • @Godeke: But how can FooManager change m_manager of Foo without it being accessible publicly? – kaalus May 07 '11 at 19:19
  • Why would separating to more assemblies be unacceptable? Even if there are other classes in the assembly, they should be friends and to hostiles (or others as some call them). – Danny Varod May 08 '11 at 20:31
  • If FooManager is creating the Foo then it is simply setting that at creation of Foo (i.e., it is passed into the actual Foo constructor at that time). Unless you need to change managers after creation (which seems odd) this would seem vastly easier and more in line with how C#/Java code is traditionally written when a factory class is required. Personally I try to avoid bidirectional references and have the factory/manager be a singleton, so the objects don't need to remember the reference at all. – Godeke May 09 '11 at 15:37
  • @Godeke: For the FooManager to invoke a Foo(FooManager manager) constructor, this constructor must be public. So anyone else can invoke it as well, desyncing the information. – kaalus Oct 12 '11 at 12:52
  • You have a great example here of how the lack of Friends in C# can cause problem in theory. The fact you have rejected every possible solution (especially the assembly solution, where assemblies stand in for the Friend relationship) has made me wonder if this has come up in a real application, or if this is a rallying cry for Microsoft to implement Friends. If the former, I'm curious why the assembly solution did not work (as it fits 99% of the requirement, except that "which is not acceptable" clause). Frankly at some point I'm willing to trust my programmers or fire them if needed :) – Godeke Oct 12 '11 at 21:47
  • @Godeke The assembly solution, although in theory solves the problem, is at best a hack that will cause problems elsewhere. Foo/FooManager will likely logically belong to some assembly that contains a lot more classes than just these two. Splitting this presumably consistent assembly into two (or more if there are more Foo/FooManager instances there) is like killing a fly with a cannon. – kaalus Jul 04 '12 at 23:00
  • @Godeke As to the "rallying Microsoft" vs "a real life problem", it's both. I encounter problems that cry for friend addition to C# very often. In C++ I can make the class interface strictly safe by using friends. In C# I have (after a couple of years) learned to live with sub-par solutions of making something public that shouldn't be. Just one of the tradeoffs of using C#. – kaalus Jul 04 '12 at 23:04
  • "Killing a fly with a cannon" is why I advocated the manager approach. Yes, someone *could* use a public constructor improperly, but not all solutions must be technical. Friends comes with costs in terms of complexity (just search "C++ Friends Template Problems") and I think the tradeoff is an acceptable one. I haven't had any programmers go rogue on *me* and violate the manager pattern yet... and if they did it would simply be a bug, fixed and life goes on. Compared to the chaos people can cause (but usually don't) in duck typed languages (for example), C# is a reasonable compromise. – Godeke Jul 04 '12 at 23:59

7 Answers7

7

If I understand all correctly:

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;
    }
}
Petr Abdulin
  • 33,883
  • 9
  • 62
  • 96
  • 1
    What's with the `FooManager fm` in the signature for CreateFoo? – Chris Eberle May 07 '11 at 14:38
  • 2
    Well, that is not a good solution because someone can call new Foo() directly, and m_foos list will be invalid. (Btw I understand there's public in front of Foo constructor) – kaalus May 07 '11 at 15:53
  • @kaalus you're right this solution doesn't work, but can't figure out something better yet. – Petr Abdulin May 07 '11 at 16:23
  • @kaalus another try, check it out. – Petr Abdulin May 07 '11 at 17:22
  • @Petr Thanks for your effort, I appreciate it. Please don't take my criticisms as something negative. So here it goes: (1) this is not thread safe. (2) this gives runtime error instead of compile error. (3) someone else can inherit from FooBus and still desync list and m_manager references (4) even if this could be made to work, this is clearly too much of a hack to be a valid implementation of a common design pattern :-) (5) We need friend declarations in C# :-( – kaalus May 07 '11 at 19:22
  • @kaalus 1) correct, but we not talking about a production quality code here :) 2) are you referencing to 1?, 3) that is not true, but FooManager class must be made sealed, 4) maybe, that depends on how bad you want use it in exaclty this implementation form 5) frined in c# will make more harm than good in my opinion. – Petr Abdulin May 08 '11 at 07:47
  • @Petr (2) No I'm referencing what is currently in the answer. You can write code that will look like it desyncs list and m_manager, but it will only fail when run. Ideally, it should be impossible to even write it (compile error). (3) Why is it untrue, what stops me writing a class that inherits from FooBus? (5) Lack of friend is the root cause of problems like that (and others) - it prevents you making several classes that work together and have access to their private data. Without friend, it must always be one class. This does not suffice in some cases, as this question shows. – kaalus May 08 '11 at 09:36
  • @kaalus 2) ok, I got the point. well anyway it's better to have runtime error than to nave incorrectly behaving code that runs. – Petr Abdulin May 08 '11 at 10:55
  • @kaalus 2) ok, I got the point. well anyway it's better to have runtime error than to nave incorrectly behaving code that runs. 3) you're right, I was wrong about that. but still this requires much more deeper knoledge of internal structure, and have low probability of accidental usage. 5) I don't see it as a problem, just you want exactly that design (without AddFoo and SetFooManger methods for example). Most of the code will break of improper usage at runtime, it's impossible to make it all compile time safe. – Petr Abdulin May 08 '11 at 11:24
3

One option would be to use a private nested class for Foo that implements a public interface:

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

As the Foo class is a private nested class, it can't be created outside the FooManager, and so FooManager's CreateFoo() method ensures that everything stays in-sync.

Iridium
  • 23,323
  • 6
  • 52
  • 74
  • This is a good try. But what if I wanted to have Foo added to more than 1 manager class? I cannot make Foo a private inner class of 2 other classes :-( Also, this solution requires me to duplicate declarations (both in interface and actual Foo), and it makes all calls slower by requiring them to go through an interface. This would all be unneccesary if friend was available in C#. – kaalus May 08 '11 at 09:43
  • 2
    On your first point, you can make FooManager a base class of your other manager classes. The base class could then provide the CreateFoo() method, and store the relationship. On your second, that's not how interfaces work, and exposing functionality through interfaces is good programming practice. – Iridium May 08 '11 at 10:58
3

What you can do is create your classes inside a different kind of namespace, let's call it a "module" (don't be fooled by the class keyword, this is not a real 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);
    }
  }

}

Since the "module" is a partial class, you can still create other members inside it in other files in the same compilation unit.

Jordão
  • 55,340
  • 13
  • 112
  • 144
0

I'm considering using reflection.

There is no good solution for a failed design (lack of friends in CLR).

drowa
  • 682
  • 5
  • 13
  • Must agree with you here. In C# it is not possible to express the fundamental idea that classes sometimes need to work in tandem. The heavyweight workarounds (putting them in a new separate assembly or using reflection) do not solve the problem. Btw. I came back to this old question because I again hit the same problem! We need friends in C#. Otherwise sloppy design follows. – kaalus Feb 11 '16 at 14:42
0

How about something like this:

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

Of course this doesn't prevent the user from doing something like this:

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

I suppose there can be a way to work around this, but any ideas I have start to become quite excessive.

AustinWBryan
  • 3,249
  • 3
  • 24
  • 42
0

How about having a base class:

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

Then Foo and FooManager have FooBase as base class and can update their mapping without exposing it externally. Then you can be sure that no one will alter this collection from outside.

Then in Foo you have a property Manager that will return the manager associated with it and similar, a Foos property in Manager that will give all Foos.

Victor Hurdugaci
  • 28,177
  • 5
  • 87
  • 103
  • The same issue as with Petr in another answer: what stops someone else inheriting FooBase and messing around with _managerMapping? Also, this is quite heavyweight performance wise - dictionary lookup instead of a simple member access that we would have if friend was available in C# – kaalus May 08 '11 at 09:38
0

In this example the only ones that can get the relationship out of sync is the Foo and the Manager themselves. CreateFoo() is called on the manager to create a "managed foo". Someone else can create a Foo but they can't get it to be managed by a manager without the manager "agreeing".

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);
    }
}
D.H.
  • 1,083
  • 2
  • 11
  • 22