jltrem
jltrem

Reputation: 12524

limit method to only be called by a particular class

I want a particular method in one class to only be accessible by a particular class. For example:

public class A
{
  public void LimitedAccess() {}
  public void FullAccess() {}
}

public class B
{
  public void Func()
  {
     A a = new A();
     a.LimitedAccess();       // want to be able to call this only from class B
  }
} 

public class C
{
  public void Func()
  {
     A a = new A();
     a.FullAccess();           // want to be able to call this method
     a.LimitedAccess();        // but want this to fail compile
  }
} 

Is there is a keyword or attribute that I can use to enforce this?

UPDATE:

Due to existing system complexity and time constraints, I needed a low impact solution. And I wanted something to indicate at compile time that LimitedAccess() could not be used. I trust Jon Skeet's answer that exactly what I had asked for could not be done in C#.

The question and Jon's answer are good for those who may run across this later. And the fact that this design smells can hopefully veer anyone away for choosing something like this as a desired a solution.

As mentioned in a comment, the C# friend conversation is useful reading if you are trying to solve a similar situation.

As for my particular solution: "why would A contain B's logic" (asked by @sysexpand in comments). That's the rub. B.Func() was called throughout the system I'm working on, but it primarily operated on a singleton of A. So what I ended up doing was moving B's Func() into A and making A.LimitedAccess() private. There were a few other details to work around, as there always are, but I got a low impact solution that gave me compile-time errors on callers to A.LimitedAccess().

Thanks for the discussion.

Upvotes: 31

Views: 27731

Answers (7)

cowlinator
cowlinator

Reputation: 8777

Yes. What you are asking for is perfectly possible.

You can restrict access to methods and variables for a specific instance, by using an interface.

However, an interface alone cannot prevent someone from creating their own instance of the class, at which point they will have full access to that instance.

To do that, next you should nest it as a private class inside of another class in order to restrict access to the constructor.

Now you have a particular method in one class to only be accessible by a particular class.

In this example, only class B is ever able to access function LimitedAccess.

public interface IA
{
  void FullAccess();
}

public class B
{
  private class A : IA
  {
    public void LimitedAccess() {}  //does not implement any interface
    public void FullAccess() {}     //implements interface
  } 
    
  private A a = new A();

  public IA GetA()
  {
    return (IA)a;
  }
  
  public void Func()
  {
     /* will be able to call LimitedAccess only from class B, 
        as long as everybody else only has a reference to the interface (IA). */
     a.LimitedAccess();       
  }
} 


//This represents all other classes
public class C
{  
  public void Func(IA ia)
  {
     ia.FullAccess();           // will be able to call this method
     ia.LimitedAccess();        // this will fail to compile
  }
} 

public static class MainClass
{
  public static void Main(string[] args)
  {
    B b = new B();
    b.Func();
          
    IA ia = b.GetA();
      
    C c = new C();
    c.Func(ia);
  }
}

Upvotes: 7

stenci
stenci

Reputation: 8481

This is a variation of the solution suggested by @cowlinator using class AWithUnlimitedAccess derived from class A rather than class A implementing interface IA.

The result and the limitations are the same, but I like it better because (1) the limited access methods are defined inside its own class and (2) it's easier to add documentation comments.

public class A
{
    public void FullAccess() { }
}


public class AWithUnlimitedAccess : A
{
    public void LimitedAccess() { }
}


public class B
{
    private AWithUnlimitedAccess a = new AWithUnlimitedAccess();

    public A GetA()
    {
        return a;
    }

    public void Func()
    {
        a.FullAccess();
        a.LimitedAccess();
    }
}

// This represents all other classes
public class C
{
    public A A;

    public void Func()
    {
        A.FullAccess();
        A.LimitedAccess(); // this will fail compile
    }
}

public static class MainClass
{
    static void Main(string[] args)
    {
        B b = new B();
        b.Func();
        C c = new C();
        c.A = b.GetA();
        c.Func();
    }
}

Upvotes: 0

Addio
Addio

Reputation: 137

You could always see the calling type with a StackTrace. Just note that when building in release mode, the call on the stack will get optimized, and its possible that the stack trace could return a completely different class, so just make sure to test it before you publish.

/// <summary>
/// Warning: Any class that calls this other than "B" will throw an exception.
/// </summary>
public void LimitedAccess()
{
     if (new System.Diagnostics.StackTrace().GetFrame(1).GetMethod().DeclaringType != typeof(B)) throw new Exception("Invalid Caller Type, B is only class able to call this method.");
}

Unfortunately you wont be able to know if its an error on compile time. Best you can do is throw an exception if it gets called, and add a comment warning people about it.

Upvotes: 1

sa.he
sa.he

Reputation: 1421

In case you just want to remind yourself (or team mates) to not call LimitedAccess everywhere, you could consider using explicit interface implementation or mark LimitedAccess as obsolete.

public interface IA
{
    void LimitedAccess();
    void FullAccess();
}

public class A : IA
{
    private void LimitedAccess() { }
    public void FullAccess() { }

    void IA.LimitedAccess() => LimitedAccess();
    void IA.FullAccess() => FullAccess();
}

public class B
{
    public void Func()
    {
        IA a = new A();
        a.LimitedAccess();       // want to be able to call this only from class B
    }
}

public class C
{
    public void Func()
    {
        A a = new A();
        a.FullAccess();           // want to be able to call this method
        a.LimitedAccess();        // -> fails to compile
    }
}

Upvotes: 3

be_mi
be_mi

Reputation: 569

Maybe this is a workaround.

Use the System.Runtime.CompilerServices and then you can either check the Name of the calling function and/or the file, in which the calling function is defined. If you have a class per file, the filename might be a substitude for the class name. Check it and block the call.

internal void MySecretFunction (string something,
  [CallerMemberName] string memberName = null,
  [CallerFilePath] string filePath = null,
  [CallerLineNumber] int lineNumber = 0) {
    if (!filePath.EndsWith(@"\goodClass.cs")) return;

    // else do something
}

Upvotes: 1

Zoran Horvat
Zoran Horvat

Reputation: 11301

It is against OOP best practices to make such a design. Methods of classes are not supposed to be protected from being called.

If your design requires control over calling a method, then control should be exercised by testing the arguments - caller which is authorized to make a call would "know" the magic word to pass as the argument.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499830

No. The only thing you could do would be to make LimitedAccess a private method, and nest class B within class A.

(I'm assuming you want all the classes in the same assembly. Otherwise you could put A and B in the same assembly, and C in a different assembly, and make LimitedAccess an internal method.)

Upvotes: 26

Related Questions