4

I have a class (basically a linked list, so let's call it List) that uses another class to store data (the nodes, so let's call the class Node). Node has methods that must be called from List, but calling them from elsewhere could be messy. I need other parts of my program to use references of Node, but to change them only through List.
In C++ I would create another class (let's call it Opaque) that has a private pointer to Node and expose some members of Node, and if this was passed to List, the pointer to the actual Node would be "unpacked" and used internally, but in C# I have not thought of a way to hide the reference to the Node from the rest of the program without also makking it inaccessible to List.
Is there any way to do it or some C#-specific idiom that has the same functionality?

CLARIFICATION: I would like to make it visible to the List class only. I already know about the internal keyword.

Tomeamis
  • 477
  • 6
  • 14
  • 1
    What happens when you try the C++ solution in C#? Separate class, private reference, delegating members? – John Saunders Sep 20 '14 at 16:06
  • I tried, but if I make Node a private class nested in List, then create another class to pass around which has a private reference to Node, I can't get the reference out of it from inside List. In C++ this could be solved by making the class `friend` or just sticking the actual implementation into a .cpp file, but c# has no `friend` and AFAIK it doesn't have the header(.h)/implementing code(.cpp) structure either. – Tomeamis Sep 20 '14 at 16:15

3 Answers3

3

What about

public interface INode
{
    string Name { get; }
}

public class List
{
    private class Node : INode
    {
        public string Name { get; set; }
        public Node Next { get; set; }
    }

    private List<Node> _items = new List<Node>();

    public INode GetItemAt(int index)
    {
        return _items[index];
    }

    public INode GetNextItem(INode item)
    {
        return (item is Node) ? ((Node)item).Next : null;
    }
}

Where only the INode would be publicly visible, implementation details hidden.

In case there would be lots of the internal code working with the Node then it is better to mark the class as internal as suggested by @eugene-podskal as then the Node class will be accessible to many "friends" living in the same original assembly (see MSDN: internal (C# reference) for more detailed description of this accessibility model)

There is nothing wrong per-se with the internal concept (as proposed and then disputed by @dasblinkenlight). It is also quite often used by the .NET framework, see http://referencesource.microsoft.com/#q=internal for examples or various .NET framework internal classes. An example of whole internal .NET framework namespaces can be seen using ILSpy e.g. at system.dll in namespace Microsoft.Win32.SafeHandles

xmojmr
  • 8,073
  • 5
  • 31
  • 54
  • 1
    I have thought about nested private class, but it seems that `Nodes` are created outside of the MyList class, otherwise Factory pattern is an answer. In any case it is for OP to clarify and decide. – Eugene Podskal Sep 20 '14 at 16:57
  • Nodes are created inside `List`. If I understand your thinking correctly, the `Name` property is not really meant to do anything, and therefore passing around a reference to `INode` doesn't really let its users do anything with the `Node`without passing it to `List`, which is what I needed. – Tomeamis Sep 20 '14 at 17:18
  • @Tomeamis yes, the `Name` property is meant to be an example of something that can be only read through the `INode` interface (harmless). In order to assign a value to it you need the `Node` reference, only `Node` or `List` can do it. Using the same concept you can build 2 different APIs: one public and harmless accessed through the opaque `INode` variable type and the other having access to everything else typecasting the opaque handles internally to the `Node` internal data type... – xmojmr Sep 20 '14 at 17:22
  • I know that there is nothing wrong with it, it's just that its scope is a bit too broad for making an opaque reference unless one is willing to create a special assembly for 2~3 classes. – Tomeamis Sep 20 '14 at 18:00
  • @Tomeamis the "internal" in my last comment was not meant literally as C# internal, it was more like conceptual internal implemented using C# private. See my edited answer and the `GetNextItem` method showing how you can "unpack" the opaque handle. If you want the pointers to be super-opaque then you can cast them to [System.IntPtr](http://msdn.microsoft.com/en-us/library/System.IntPtr(v=vs.110).aspx) and back with the same result. Using interfaces is however more C#-like and can be more useful as the pointer does not have to be complete "black box" – xmojmr Sep 20 '14 at 18:10
2

In C# has two levels of granularity for information hiding.

The most basic one is the class itself: this is the scope that you get when you declare a member private. This is not sufficient for your purposes, because the node and the list are different classes.

Next up is the level of the assembly, which is designated using the internal keyword. This is what you should be able to use for your purposes, assuming that the list class and the node class share the same assembly:

public class Node {
    internal void SpecialMethod() {
        ...
    }
}

The obvious downside is that the internal methods of the node class are visible to all classes inside your library, not only to the list. However, one could argue that internals of an opaque reference in C++ are visible to everyone who has access to the internal headers, so you get a closely matched functionality between the two languages.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • I am working with other people on this project, so I would like to avoid calling the Node methods instead of the List methods by mistake, which would cause bugs and probably be hard to find. Maybe if it was possible to split that class only into a separate assembly? Oh and by the way, with C#, the would just get access to the Node methods by default, in C++ they would have to `#include` the .cpp file, which is not usually done. – Tomeamis Sep 20 '14 at 16:23
2

I can propose you to use interface for your node values(or least derived class) and pass it through the app, but you List will try to cast it to use advanced features:

public interface INode
{
    void DoPublic();
}

// As it is pointed by @xmomjr
internal class Node : INode
{
    public void DoPublic(){}
    public void DoHidden(){}
}

public class MyList
{
    public void Process(INode node)
    {
        if (node is Node)
            // ...
    }
}

Of course it is not true hiding(anyone who knows about the Node can use it), but for most usage scenarios it will suffice.

Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • Again, this uses the `internal` keyword, which makes it visible to _all_ the code in the given assembly. I want to make it visible to one class only. But if there is no way to do that, I will probably accept dasblinkenlight's answer, as it properly explains that little catch. – Tomeamis Sep 20 '14 at 16:42
  • @Tomeamis you can move `Node` as private class into the `MyList`. The point of this answer is that you should publish and use outside only the `INode` as your opaque type, all variables used in the rest of your program being declared as `INode` – xmojmr Sep 20 '14 at 16:45