CLSCompliant(true) 拖入未使用的引用

发布于 2024-08-01 20:58:13 字数 1887 浏览 5 评论 0原文

任何人都可以解释以下行为吗?

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

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

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

namespace Ploeh
{
    public abstract class Class1InLibraryA
    {
    }
}

确保该库符合 CLS,方法是将 [程序集:CLSCompliant(true)] 添加到 AssemblyInfo.cs。

创建另一个名为 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 合规性之间似乎存在一些奇怪的相互作用。

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

Can anyone explain the following behavior?

In summary, if you create multiple CLS compliant libraries in Visual Studio 2008 and have them share a common namespace root, a library referencing another library will require references to that library's references even though it doesn't consume them.

It's pretty difficult to explain in a single sentence, but here are steps to reproduce the behavior (pay close attention to the namespaces):

Create a library called LibraryA and add a a single class to that library:

namespace Ploeh
{
    public abstract class Class1InLibraryA
    {
    }
}

Make sure that the library is CLS Compliant by adding [assembly: CLSCompliant(true)] to AssemblyInfo.cs.

Create another library called LibraryB and reference LibraryA. Add the following classes to LibraryB:

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

and

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

Make sure that LibraryB is also CLS Compliant.

Notice that Class1InLibraryB derives from a type in LibraryA, whereas Class2InLibraryB does not.

Now create a third library called LibraryC and reference LibraryB (but not LibraryA). Add the following class:

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

This should still compile. Notice that Class1InLibraryC derives from the class in LibraryB that doesn't use any types from LibraryA.

Also notice that Class1InLibraryC is defined in a namespace that is part of the namespace hierarchy defined in LibraryB.

So far, LibraryC has no reference to LibraryA, and since it uses no types from LibraryA, the solution compiles.

Now make LibraryC CLS compliant as well. Suddenly, the solution no longer compiles, giving you this error message:

The type 'Ploeh.Class1InLibraryA' is defined in an assembly that is not referenced. You must add a reference to assembly 'Ploeh, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.

You can make the solution compile again in one of the following ways:

  • Remove CLS Compliance from LibraryC
  • Add a reference to LibraryA (although you don't need it)
  • Change the namespace in LibraryC so that it is not part of LibraryB's namespace hierarchy (e.g. to Fnaah.Samples.LibraryC)
  • Change the namespace of Class1InLibraryB (that is, the one not used from LibracyC) so that it is does not lie in LibraryC's namespace hierarchy (e.g. to Ploeh.Samples.LibraryB)

It seems that there is some strange interplay between the namespace hierarchy and CLS compliance.

Solving this issue can be done by picking one of the options in the list above, but can anyone explain the reason behind this behavior?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清风不识月 2024-08-08 20:58:13

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

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

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

确定 CLS 合规性的规则是:

  • 当程序集未携带显式 System.CLSCompliantAttribute 时,应将其
    假定携带 System.CLSCompliantAttribute(false)。
  • 默认情况下,类型继承其封闭类型(对于嵌套类型)的 CLS 合规性属性,或获取附加到其程序集的合规性级别(对于顶级类型)。
  • 默认情况下,其他成员(方法、字段、属性和事件)继承其类型的 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/binRelease/bin 文件夹,以便在构建时它们位于正确的目录中。应用程序在编译后运行。

Remember that the CLS is a set of rules that apply to generated assemblies and is designed to support interoperability between languages. In a sense, it defines the smallest common subset of rules that a type must follow to ensure that it is language and platform agnostic. CLS-compliance also only applies to items that are visible outside of their define assembly.

Looking at some of the guidelines CLS-compliant code should follow:

  • Avoid the use of names commonly used as keywords in programming languages.
  • Not expect users of the framework to be able to author nested types.
  • Assume that implementations of methods of the same name and signature on different interfaces are independent.

The rules for determining CLS-compliance are:

  • When an assembly does not carry an explicit System.CLSCompliantAttribute, it shall be
    assumed to carry System.CLSCompliantAttribute(false).
  • By default, a type inherits the CLS-compliance attribute of its enclosing type (for nested types) or acquires the level of compliance attached to its assembly (for top-level types).
  • By default, other members (methods, fields, properties, and events) inherit the CLS-compliance of their type.

Now, as far as the compiler is concerned, (CLS Rule 1) it must be able to apply the rules for CLS-compliance to any information that will be exported outside the assembly and considers a type to be CLS-compliant if all its publicly accessible parts (those classes, interfaces, methods, fields, properties, and events that are available to code executing in another assembly) either

  • have signatures composed only of CLS-compliant types, or
  • are specifically marked as not CLS-compliant.

By CTS rules, a scope is simply a group/collection of names and within a scope a name may refer to multiple entities as long as they are of different kinds (methods, fields, nested types, properties, events) or have different signatures. A named entity has its name in exactly one scope so in order to identify that entry both a scope and a name must be applied. The scope qualifies the name.

Since types are named, the names of types are also grouped in to scopes. To fully identify a type, the type name must be qualified by the scope. Types names are scoped by the assembly that contains the implementation of that type.

For scopes which are CLS-compliant, all names must be distinct independent of kind, except where the names are identical and resolved via overloading. In otherwords, while the CTS allows a single type to use the same name for a field and a method, the CLS does not (CLS Rule 5).

Taking this one step further, a CLS-compliant type must not require the implementation of non-CLS-compliant types (CLS Rule 20) and must also inherit from another CLS-complaint type (CLS Rule 23).

An assembly can depend on other assemblies if the implementations in the scope of one assembly reference resources that are scoped in or owned by another assembly.

  • All references to other assemblies are resolved under the control of the current assembly scope.
  • It is always possible to determine which assembly scope a particular implementation is running in. All requests originating from that assembly scope are resolved relative to that scope.

What all of this ultimately means is that in order to verify-CLS compliance of a type, the compiler must be able to verify that all public parts of that type are also CLS-compliant. This means that it must ensure that the name is unique within a scope, that it does not depend on non-CLS-compliant types for parts of its own implementation and that it inherits from other types that are also CLS-compliant. The only way for it to do so is by examining all of the assemblies that the type references.

Remember that the build step in Visual Studio is essentially a GUI wrapper around executing MSBuild, which ultimately is nothing more than a scripted way to call the C# command line compiler. In order for the compiler to verify CLS-compliance of a type, it must know of and be able to find all of the assemblies that type references (not the project). Since it is called through MSBuild and ultimately Visual Studio, the only way for Visual Studio (MSBuild) to inform it of those assemblies is by including them as references.

Obviously, since the compiler is able to figure out that it is "missing" references in order to verify CLS-compliance and compile successfully, it would have been nice if it could have simply included those reference automatically on our behalf. The problem here is in determining which version of the assembly to include and where that assembly is on the file system. By forcing the developer to provide that information, the compiler helps guarantee that it is given the correct information. This also has the side-effect of ensuring that all dependent assemblies are copied to the Debug/bin or Release/bin folders during the build so they are in the correct directory when the application is run after it has been compiled.

梦与时光遇 2024-08-08 20:58:13

我查看了 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),添加引用并编译,您将看到该引用实际上并未在生成的程序集元数据中标记,因此这只是编译/验证时依赖项。

I had a look into the official documents for the CLS (http://msdn.microsoft.com/en-us/netframework/aa569283.aspx), but my head exploded before I could find a simple answer.

But I think the basis is that the compiler, in order to verify the CLS compliance of LibraryC, needs to look into possible naming conflicts with LibraryA.

The compiler must verify all "parts of a type that are accessible or visible outside of the defining assembly" (CLS Rule 1).

Since public class Class1InLibraryC inherits Class2InLibraryB, it must verify the CLS compliance against LibraryA as well, in particular because "Ploeh.*" is now "in scope" for CLS Rule 5 "All names introduced in a CLS-compliant scope shall be distinct independent of kind".

Changing either the namespace of Class1InLibraryB or Class1InLibraryC so they become distinct seems to convince the compiler there is no chance for a name conflict anymore.

If you choose option (2), add the reference and compile, you'll see that the reference is not actually marked in the resulting assembly meta-data, so this is a compilation/verification-time dependency only.

So要识趣 2024-08-08 20:58:13

此问题已在 Roslyn 中修复,可在 Visual Studio 14 中使用。
截至 2014 年 7 月,当前的 CTP 可在此处获取
请参阅此错误报告< /a> 了解详细信息。

The issue is fixed in Roslyn, which is available in Visual Studio 14.
As of July 2014, the current CTP is available here.
See this bug report for details.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文