CLSCompliant(true) перетаскивает в неиспользованные ссылки

Кто-нибудь может объяснить следующее поведение?

Таким образом, если вы создаете несколько совместимых с CLS библиотек в Visual Studio 2008 и имеете общий корень пространства имен, для библиотеки, ссылающейся на другую библиотеку, потребуются ссылки на ссылки этой библиотеки, даже если она их не использует.

Это довольно сложно объяснить в одном предложении, но вот шаги для воспроизведения поведения (обратите особое внимание на пространства имен):

Создайте библиотеку с именем LibraryA и добавьте в нее отдельный класс:

namespace Ploeh
{
    public abstract class Class1InLibraryA
    {
    }
}

Убедитесь, что библиотека совместима с CLS, добавив [assembly: 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, версия =1.0.0.0, Culture= нейтральный, PublicKeyToken=null".

Вы можете заново скомпилировать решение одним из следующих способов:

  • Удалить CLS Соответствие из LibraryC
  • Добавьте ссылку на LibraryA (хотя она вам не нужна)
  • Измените пространство имен в LibraryC, чтобы оно не входило в иерархию пространства имен LibraryB (например, Fnaah.Samples.LibraryC).
  • Измените пространство имен Class1InLibraryB (то есть того, которое не используется в LibracyC), чтобы оно не входило в иерархию пространства имен LibraryC (например, на Ploeh.Samples.LibraryB).

Кажется, что есть некоторая странная взаимосвязь между иерархией пространства имен и соответствием CLS.

Решить эту проблему можно, выбрав один из вариантов в списке выше, но кто-нибудь может объяснить причину такого поведения?

3 ответа

Решение

Я посмотрел официальные документы для CLS ( http://msdn.microsoft.com/en-us/netframework/aa569283.aspx), но моя голова взорвалась, прежде чем я смог найти простой ответ.

Но я думаю, что основа состоит в том, что компилятор, чтобы проверить CLS-соответствие LibraryC, должен изучить возможные конфликты имен с LibraryA.

Компилятор должен проверить все "части типа, которые доступны или видимы за пределами определяющей сборки" (правило 1 CLS).

Так как открытый класс Class1InLibraryC наследует Class2InLibraryB, он должен также проверить соответствие CLS для LibraryA, в частности потому, что "Ploeh.*" Теперь находится "в области действия" для правила 5 CLS. "Все имена, введенные в области, совместимой с CLS, должны различаться независимо в своем роде ".

Изменение пространства имен Class1InLibraryB или Class1InLibraryC, чтобы они стали различимыми, похоже, убеждает компилятор, что больше нет шансов для конфликта имен.

Если вы выберете option (2), добавите ссылку и скомпилируете, вы увидите, что ссылка на самом деле не помечена в результирующих метаданных сборки, так что это только зависимость времени компиляции / проверки.

Помните, что CLS- это набор правил, которые применяются к сгенерированным сборкам, и предназначен для поддержки взаимодействия между языками. В некотором смысле он определяет наименьшее общее подмножество правил, которым должен следовать тип, чтобы гарантировать его независимость от языка и платформы. Соответствие CLS также применимо только к элементам, которые видны за пределами их определенной сборки.

Глядя на некоторые из руководств CLS-совместимый код должен следовать:

  • Избегайте использования имен, обычно используемых в качестве ключевых слов в языках программирования.
  • Не ожидайте, что пользователи фреймворка смогут создавать вложенные типы.
  • Предположим, что реализации методов с одинаковыми именами и сигнатурами на разных интерфейсах независимы.

Правила определения соответствия CLS:

  • Если в сборке отсутствует явный атрибут System.CLSCompliantAttribute, предполагается, что он содержит атрибут System.CLSCompliantAttribute(false).
  • По умолчанию тип наследует атрибут соответствия CLS своего включающего типа (для вложенных типов) или получает уровень соответствия, прикрепленный к его сборке (для типов верхнего уровня).
  • По умолчанию другие члены (методы, поля, свойства и события) наследуют CLS-соответствие своего типа.

Теперь, что касается компилятора (правило 1 CLS), он должен иметь возможность применять правила соответствия CLS к любой информации, которая будет экспортирована за пределы сборки, и считает тип совместимым с CLS, если все его общедоступны. доступные части (те классы, интерфейсы, методы, поля, свойства и события, которые доступны для кода, выполняемого в другой сборке) либо

  • иметь подписи, состоящие только из CLS-совместимых типов, или
  • специально помечены как не соответствующие CLS.

По правилам CTS область действия - это просто группа / коллекция имен, и в пределах области имя может ссылаться на несколько объектов, если они имеют разные виды (методы, поля, вложенные типы, свойства, события) или имеют разные подписи. Именованная сущность имеет свое имя ровно в одной области, поэтому для идентификации этой записи должны применяться и область, и имя. Объем квалифицирует имя.

Так как типы именуются, имена типов также группируются в области видимости. Чтобы полностью идентифицировать тип, имя типа должно быть квалифицировано областью. Имена типов ограничены сборкой, которая содержит реализацию этого типа.

Для областей, которые совместимы с CLS, все имена должны отличаться независимо от вида, за исключением случаев, когда имена идентичны и разрешаются путем перегрузки. Другими словами, хотя CTS позволяет одному типу использовать одно и то же имя для поля и метода, CLS этого не делает (правило 5 CLS).

Сделав еще один шаг, CLS-совместимый тип не должен требовать реализации несовместимых с CLS типов (правило 20 CLS), а также должен наследовать от другого типа жалобы CLS (правило 23 CLS).

Сборка может зависеть от других сборок, если реализации в области одной сборки ссылаются на ресурсы, находящиеся в области действия или принадлежащие другой сборке.

  • Все ссылки на другие сборки разрешаются под контролем текущей области сборки.
  • Всегда можно определить, в какой области сборки выполняется конкретная реализация. Все запросы, исходящие из этой области сборки, разрешаются относительно этой области.

В конечном итоге все это означает, что для проверки соответствия CLS типа, компилятор должен иметь возможность проверить, что все открытые части этого типа также являются CLS-совместимыми. Это означает, что оно должно гарантировать, что имя является уникальным в пределах области, что оно не зависит от не-CLS-совместимых типов для частей своей собственной реализации и что оно наследуется от других типов, которые также являются CLS-совместимыми. Единственный способ сделать это - изучить все сборки, на которые ссылается тип.

Помните, что этап сборки в Visual Studio - это, по сути, оболочка GUI для выполнения MSBuild, которая, в конечном счете, является не чем иным, как скриптовым способом вызова компилятора командной строки C#. Чтобы компилятор мог проверить CLS-соответствие типа, он должен знать и уметь находить все сборки, на которые ссылаются типы (а не проект). Поскольку он вызывается через MSBuild и, в конечном счете, Visual Studio, единственный способ для Visual Studio (MSBuild) сообщить ему об этих сборках - включить их в качестве ссылок.

Очевидно, что поскольку компилятор может выяснить, что он "пропускает" ссылки для проверки соответствия CLS и успешной компиляции, было бы неплохо, если бы он просто включил эти ссылки автоматически от нашего имени. Проблема здесь заключается в определении, какую версию сборки включить и где эта сборка находится в файловой системе. Вынуждая разработчика предоставлять эту информацию, компилятор помогает гарантировать, что ему предоставлена ​​правильная информация. Это также имеет побочный эффект обеспечения того, что все зависимые сборки копируются в Debug/bin или же Release/bin папки во время сборки, поэтому они находятся в правильном каталоге при запуске приложения после его компиляции.

Проблема устранена в Roslyn, который доступен в Visual Studio 14.
С июля 2014 года текущая ОСАГО доступна здесь.
Смотрите этот отчет об ошибке для деталей.

Другие вопросы по тегам