在编译时限制方法访问的最佳方法是什么?

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

假设我有一个经理class

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

我有一个项目 class:

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

现在,我想用最简单直接的方式在编译时分析像AllowCallBy这样的属性,并显示错误或警告。如果在这种特殊情况下,除管理器 class 以外的 class 试图用 new Item(...) 实例化 Item 我想显示类似 "don't instantiate Item class directly, call Manager.Create(...) instead".

我想至少有一个系统:Roslyn、ReSharper、PostSharp 或其他系统可以让我做到这一点或与我想要实现的目标非常接近的系统。有人可以举例说明使用什么以及如何使用它吗?

有比当前方法更好的方法来实现您的目标,假设您实际上可以更改该代码

例如,您可以将 Item class 的构造函数标记为私有,并向 Item class 添加一个静态工厂方法,该方法负责创建 class 的实例。

另一种方法是将 Item class 移动到另一个程序集,将其构造函数标记为内部并实现另一个 class(工厂),它将负责创建不同的 Item 对象。然后你 class 对其他程序集是可见的,但它不能直接实例化,因此强制代码用户使用提供的工厂。

正如@Habib 提到的那样,这绝对是一个 code smell(有人可以 link 到一个特定的吗?),但如果没有更完整的示例,很难提供超出已经建议的替代方案在评论中。我鼓励您扩大样本或重新考虑您的设计。


但是,我可以提供一个我过去使用过的选项,但不是为了这个目的。您可以将 Item 的构造函数标记为 Obsolete:

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

然后在您的 Manager class 中,您将在调用构造函数时特别忽略此警告:

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

这样,每当有人试图在代码的其他地方创建他们自己的 Item 时,他们就会得到一个 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 已启用(对于所有警告或仅此一个),然后它将是你最初想要的编译错误。

请注意,没有什么能阻止其他人添加这些 pragma 语句来绕过错误。但是,使用这种方法,开发人员不能说他们不知道他们不应该使用构造函数。

好吧,让我感到惊讶。 PostSharp lets you do exactly what you're looking for。简而言之,您将使用 ComponentInternalAttribute 来控制类型的可见性:

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

根据上面链接的文档,尝试在 Manager 之外调用 Item 的构造函数将产生编译时警告:

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

您可以通过更改属性的严重级别使其成为错误:

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