29

谁能解释以下行为?

总之,如果您在 Visual Studio 2008 中创建多个符合 CLS的库并让它们共享一个公共命名空间根,则引用另一个库的库将需要对该库的引用的引用,即使它不使用它们。

这很难用一句话来解释,但这里有一些重现行为的步骤(密切注意命名空间):

创建一个名为 LibraryA 的库并向该库添加一个类:

namespace Ploeh
{
    public abstract class Class1InLibraryA
    {
    }
}

[assembly: CLSCompliant(true)]通过添加到 AssemblyInfo.cs确保库符合 CLS 。

创建另一个名为 LibraryB 的库并引用 LibraryA。将以下类添加到 LibraryB:

namespace Ploeh.Samples
{
    public class Class1InLibraryB : Class1InLibraryA
    {
    }
}

namespace Ploeh.Samples
{
    public abstract class Class2InLibraryB
    {
    }
}

确保 LibraryB 也符合 CLS。

请注意,Class1InLibraryB 派生自 LibraryA 中的类型,而 Class2InLibraryB 不是。

现在创建名为 LibraryC 的第三个库并引用 LibraryB(但不是 LibraryA)。添加以下类:

namespace Ploeh.Samples.LibraryC
{
    public class Class1InLibraryC : Class2InLibraryB
    {
    }
}

这仍然应该编译。请注意, Class1InLibraryC 派生自 LibraryB 中的类,该类不使用 LibraryA 中的任何类型

另请注意,Class1InLibraryC 是在一个命名空间中定义的,该命名空间是 LibraryB 中定义的命名空间层次结构的一部分。

到目前为止,LibraryC 没有对 LibraryA 的引用,并且由于它不使用 LibraryA 中的任何类型,因此解决方案可以编译。

现在也使 LibraryC CLS 兼容。突然,解决方案不再编译,给你这个错误信息:

'Ploeh.Class1InLibraryA' 类型在未引用的程序集中定义。您必须添加对程序集“Ploeh,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null”的引用。

您可以通过以下方式之一再次编译解决方案:

  • 从 LibraryC 中删除 CLS 合规性
  • 添加对 LibraryA 的引用(尽管您不需要它)
  • 更改 LibraryC 中的命名空间,使其不属于 LibraryB 的命名空间层次结构(例如更改为 Fnaah.Samples.LibraryC)
  • 更改 Class1InLibraryB 的命名空间(即 LibracyC 中使用的命名空间),使其不在 LibraryC 的命名空间层次结构中(例如更改为 Ploeh.Samples.LibraryB)

名称空间层次结构和 CLS 合规性之间似乎存在一些奇怪的相互作用。

可以通过选择上面列表中的一个选项来解决这个问题,但是任何人都可以解释这种行为背后的原因吗?

4

3 回答 3

19

我查看了 CLS 的官方文档 ( http://msdn.microsoft.com/en-us/netframework/aa569283.aspx ),但在我找到一个简单的答案之前,我的脑袋就爆炸了。

但我认为基础是编译器为了验证 LibraryC 的 CLS 合规性,需要查看与 LibraryA 可能的命名冲突。

编译器必须验证所有“在定义程序集之外可访问或可见的类型的所有部分”(CLS 规则 1)。

由于公共类 Class1InLibraryC 继承 Class2InLibraryB,因此它还必须验证对 LibraryA 的 CLS 合规性,特别是因为“Ploeh.*”现在是“在范围内”,适用于 CLS 规则 5“在符合 CLS 的范围中引入的所有名称都应是不同的独立的种”。

更改 Class1InLibraryB 或 Class1InLibraryC 的命名空间以使它们变得不同似乎使编译器不再有可能发生名称冲突。

如果您选择选项 (2),添加引用并编译,您将看到该引用实际上并未在生成的程序集元数据中标记,因此这只是编译/验证时的依赖。

于 2009-08-10T16:47:44.647 回答
7

请记住,CLS 是一组适用于生成的程序集的规则,旨在支持语言之间的互操作性。从某种意义上说,它定义了一个类型必须遵循的最小公共规则子集,以确保它与语言和平台无关。CLS 合规性也仅适用于在其定义程序集之外可见的项目。

查看符合 CLS 的代码应遵循的一些准则:

  • 避免使用在编程语言中通常用作关键字的名称。
  • 不要期望框架的用户能够创作嵌套类型。
  • 假设不同接口上同名同签名的方法的实现是独立的。

确定 CLS 合规性的规则是:

  • 当程序集不携带显式 System.CLSCompliantAttribute 时,应假定它携带 System.CLSCompliantAttribute(false)。
  • 默认情况下,类型继承其封闭类型的 CLS-compliance 属性(对于嵌套类型)或获取附加到其程序集的符合性级别(对于顶级类型)。
  • 默认情况下,其他成员(方法、字段、属性和事件)继承其类型的 CLS 合规性。

现在,就编译器而言,(CLS 规则 1)它必须能够将 CLS 合规性规则应用于将在程序集之外导出的任何信息,并且如果其全部公开,则认为该类型符合 CLS可访问部分(可用于在另一个程序集中执行的代码的类、接口、方法、字段、属性和事件)

  • 具有仅由符合 CLS 的类型组成的签名,或
  • 被特别标记为不符合 CLS。

根据 CTS 规则,范围只是名称的组/集合,并且在范围内,名称可以引用多个实体,只要它们属于不同类型(方法、字段、嵌套类型、属性、事件)或具有不同的签名。命名实体的名称恰好在一个范围内,因此为了识别该条目,必须应用范围和名称。范围限定名称。

由于类型是命名的,因此类型的名称也被分组到范围内。要完全识别类型,类型名称必须由作用域限定。类型名称由包含该类型实现的程序集限定。

对于符合 CLS 的范围,所有名称必须独立于种类而不同,除非名称相同并通过重载解析。换句话说,虽然 CTS 允许单个类型对字段和方法使用相同的名称,但 CLS 不允许(CLS 规则 5)。

更进一步,符合 CLS 的类型不得要求实现不符合 CLS 的类型(CLS 规则 20),并且还必须继承自另一个符合 CLS 的类型(CLS 规则 23)。

如果一个程序集范围内的实现引用另一程序集范围内或拥有的资源,则程序集可以依赖于其他程序集。

  • 所有对其他程序集的引用都在当前程序集范围的控制下解析。
  • 始终可以确定特定实现在哪个程序集范围内运行。源自该程序集范围的所有请求都相对于该范围进行解析。

所有这一切最终意味着,为了验证一个类型的 CLS 合规性,编译器必须能够验证该类型的所有公共部分也符合 CLS。这意味着它必须确保名称在一个范围内是唯一的,它不依赖于它自己的部分实现的不符合 CLS 的类型,并且它继承自同样符合 CLS 的其他类型。它这样做的唯一方法是检查该类型引用的所有程序集。

请记住,Visual Studio 中的构建步骤本质上是一个围绕执行 MSBuild 的 GUI 包装器,它最终只不过是一种调用 C# 命令行编译器的脚本方式。为了让编译器验证一个类型的 CLS 兼容性,它必须知道并能够找到所有类型引用的程序集(而不是项目)。由于它是通过 MSBuild 并最终通过 Visual Studio 调用的,因此 Visual Studio (MSBuild) 通知它这些程序集的唯一方法是将它们作为引用包含在内。

显然,由于编译器能够确定它“缺少”引用以验证 CLS 合规性并成功编译,因此如果它可以简单地代表我们自动包含这些引用,那就太好了。这里的问题在于确定要包含哪个版本的程序集以及程序集在文件系统上的位置。通过强制开发人员提供该信息,编译器有助于确保其获得正确的信息。Debug/bin这还具有确保在构建期间将所有依赖程序集复制到或文件夹的副作用,Release/bin以便在编译后运行应用程序时它们位于正确的目录中。

于 2009-08-10T18:53:15.767 回答
1

该问题已在 Roslyn 中得到修复,它在 Visual Studio 14 中可用。
截至 2014 年 7 月,当前的 CTP 可在此处获得。
有关详细信息,请参阅此错误报告

于 2014-07-24T07:48:44.173 回答