简体   繁体   中英

How do I ensure a Class can call a method on another Class, but not other Classes can call that method?

I have two classes that I'd like to keep in separate files.

namespace GridSystem
{
    public class Grid
    {
        public void AddItem(GridItem item)
        {
            item.InformAddedToGrid();
        }
    }
}

namespace GridSystem
{
    public class GridItem
    {
        public void InformAddedToGrid()
        {
            Debug.Log("I've been added to the grid");
        }
    }
}

How do I ensure no other classes are allowed to call InformAddedToGrid?

I'm trying to emulate Actionscript namespaces, which can be used on a method, in place of public, private, internal, etc. It doesn't exactly protect the method, but forces an extra step of including the namespace before the method can be accessed. Is there an alternative approach to this in C#?

If GridItem itself can be hidden from the outside world as well I would consider putting GridItem inside Grid as a nested class. That way it won't be visible outside of the class

http://www.codeproject.com/Articles/20628/A-Tutorial-on-Nested-Classes-in-C

A really ugly answer would be to make it private and use reflection.

Another ugly answer would be to make it throw an exception if the caller is wrong.

Both of these are much slower to execute than a normal call also.

I don't think there's a good answer. C# doesn't have friends.

Not that you should do this, you should do what TGH suggests, have a public interface for GridItem, and have gridItem nested in Grid (then have a factory method on Grid to create Items and use partial Grid class to have them in separate files).

Because there isn't a way of having friend methods ( you can do friend classes through InternalsVisibleToAttribute )

You COULD do this ( but don't... )

public partial class Grid
{
   public void AddItem(GridItem item)
   {
      item.InformAddedToGrid();
   }
}        

public class GridItem
{
   public void InformAddedToGrid()
   {                
      if (new StackTrace().GetFrame(1).GetMethod().DeclaringType != 
                   typeof(Grid)) throw new Exception("Tantrum!");
      Console.WriteLine("Grid called in...");

   }
}

then

 var g = new Grid();
 g.AddItem(new GridItem()); // works
 new GridItem().InformAddedToGrid(); // throws a tantrum...

IMHO the answer is simple: access modifiers are just there to remind the programmer of the intent of how public/private a class should be. Through reflection you can lift those barriers.

The usage you make of a class is all in your hands: if your class is meant to only be used in one place, make it so. If anything, if a class has a special way of being used, document it - put it in the XML comments.

That said, in this specific example I'd believe since the GridItem doesn't add itself to the grid, it's not its job to notify about it (what if " I've not been added to the grid "?). I think InformAddedToGrid belongs somewhere in your Grid class as a private method, where there's a concept of adding an item ... assuming that's what AddItem(GridItem) really does.

You can do it as TGH suggested, with nested classes, except the other way around. Nest Grid within GridItem and make InformAddedToGrid private. Here I use a nested base class so the public API can remain the same. Note that no one outside of your assembly can inherit from GridBase because the constructor is internal.

public class GridItem
{
    public class GridBase
    {
        internal GridBase() { }

        public void AddItem(GridItem item)
        {
            item.InformAddedToGrid();
        }
    }

    private void InformAddedToGrid()
    {
        Debug.Log("I've been added to the grid");
    }
}

public class Grid : GridItem.GridBase { }

Another option is to have GridItem explicitly implement an internal interface. This way no one outside of your assembly can use the interface by name and therefore cannot call InformAddedToGrid .

public class Grid
{
    public void AddItem(GridItem item)
    {
        ((IGridInformer)item).InformAddedToGrid();
    }
}

public class GridItem : IGridInformer
{
    void IGridInformer.InformAddedToGrid()
    {
        Debug.Log("I've been added to the grid");
    }
}

internal interface IGridInformer
{
    void InformAddedToGrid();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM