alex.49.98
alex.49.98

Reputation: 629

What is the best way to restrict method access at compile time?

Let's say I have a Manager class

public class Manager {
   public Item Create() {
      ...
      return new Item(...);
   }
}

and I have an Item class:

public class Item {
   [AllowCallBy(typeof(Manager))]
   public Item(...) {
   }
   ...
}

Now, I would like to use the easiest and most straightforward way to analyze the attributes like AllowCallBy at compile time and display errors or warnings. If, in this particular case, a class other than Manager class tries to instantiate Item with new Item(...) I would like to display something like "don't instantiate Item class directly, call Manager.Create(...) instead".

I suppose that at least one of the systems: Roslyn, ReSharper, PostSharp or maybe something else would allow me to do it or something that is very close to what I'm trying to achieve. Could somebody give an example of what to use and how to use it?

Upvotes: 1

Views: 367

Answers (3)

Patrick Quirk
Patrick Quirk

Reputation: 23747

Well color me surprised. PostSharp lets you do exactly what you're looking for. In a nutshell, you'd use the ComponentInternalAttribute to control visibility of a type:

public class Item {
   [ComponentInternal(typeof(Manager))]
   public Item(...) {
   }
   ...
}

According to their documentation linked above, attempting to invoke Item's constructor outside of Manager will yield a compile-time warning:

Method Item.ctor cannot be referenced from [some other method] because of the [ComponentInternal] constraint.

You can make it an error by changing the severity level of the attribute:

public class Item {
   [ComponentInternal(typeof(Manager), Severity = SeverityType.Error)]
   public Item(...) {
   }
   ...
}

Upvotes: 1

Patrick Quirk
Patrick Quirk

Reputation: 23747

This is definitely a code smell as @Habib mentions (can someone link to a specific one?), but without a more complete example it's difficult to offer alternatives beyond what has already been suggested in comments. I'd encourage you to expand your sample or rethink your design.


However, I can present one option that I've used in the past though not for this purpose. You could mark Item's constructor as Obsolete:

public class Item {
   [Obsolete("Don't instantiate Item class directly, call Manager.Create(...) instead")]
   public Item(...) {
   }
   ...
}

Then in your Manager class, you'd specifically ignore this warning where you invoke the constructor:

public class Manager {
   public Item Create() {
      ...
#pragma warning disable 618
      return new Item(...);
#pragma warning restore 618
   }
}

This way, whenever someone tries to create their own Item elsewhere in the code, they'll get a level 2 CS0618 warning indicating that they should not use the method (note that I didn't say cannot) with exactly the text entered in the attribute. If warnings as errors is enabled (for all warnings or just this one), then it will be a compile error as you originally wanted.

Be aware, nothing prevents others from adding these pragma statements to get around the error. However, with this method the developer can't say they didn't know they weren't supposed to use the constructor.

Upvotes: 3

Andrew B
Andrew B

Reputation: 880

There are way better ways to achieve your goal than your current approach, given that you can actually change that code.

You could for example mark the contructor of Item class as private and add a static factory method to Item class which would be responsible for creating an instance of the class.

Another way is to move Item class to another assembly, mark its constructor as internal and implement another class (a factory) which would be responsible for creating different Item objects. Then you class is visible from other assemblies, but it cannot be directly instantiated, so forces the code user to use provided factory.

Upvotes: 0

Related Questions