Странная ошибка компиляции при косвенной ссылке на сборку, которая объявляет общий метод расширения с ограничением типа

Ну, мне ясно, что название моего вопроса слишком сложное. Я просто попытался сделать это как можно более конкретным. Итак, я постараюсь объяснить проблему лучше.

Контекст проблемы

Предположим, у нас есть три проекта .NET в решении. Основной проект представляет собой простое консольное приложение ApplicationAssembly. В этом проекте есть ссылка на другую библиотеку управляемых сборок DirectlyReferencedLibrary. В то же время DirectlyReferencedLibrary ссылается на IndirectlyUsedLibrary.

Итак, использование проекта выглядит так: ApplicationAssembly --> DirectlyReferencedLibrary --> IndirectlyUsedLibrary.

Обратите внимание, что ApplicationAssembly не использует напрямую какой-либо тип, объявленный IndirectlyUsedLibrary. Предположим также, что все типы, объявленные в этих сборках, находятся в одном и том же пространстве имен.

Это решение компилируется и работает нормально.

Странная проблема

Проблема возникает, когда у меня есть вместе следующие условия:

  1. в проекте ApplicationAssembly используются выражения LINQ. Например, если есть вызов Select() для любого объекта перечислимого типа.
  2. DirectlyReferencedLibrary объявляет класс, который имеет общий метод расширения с ограничением типа. Ограничение типа гласит, что общий тип должен быть потомком класса из IndirectlyUsedLibrary.

Вот пример такого класса.

using System;

namespace Test
{
    public static class UnUsedType
    {
        // It's a generic extension method with a type restriction.
        public static void Magic<T>(this T @this)
            // It's a type restriction that uses a type from the IndirectlyUsedLibrary.
            where T : ProblemType
        {
            Console.WriteLine("I do nothing actually.");
        }
    }
}

Когда я пытаюсь скомпилировать этот проект, я получаю следующую ошибку:

Ошибка Тип Test.ProblemType определен в сборке, на которую нет ссылок. Вы должны добавить ссылку на сборку «IndirectlyUsedLibrary, версия = 1.0.0.0, культура = нейтральная, PublicKeyToken = null». C:\Projects\Test\ApplicationAssembly\Program.cs 22 13 ApplicationAssembly

Вопрос

Может ли кто-нибудь помочь мне понять, почему это так?

P.S.

Я сделал крошечное решение для исследования. Если вы так любезны помочь мне, вы сможете взять заархивированное решение здесь

P.P.S.

Извините за мой плохой английский.

UPD1

Еще одна странность заключается в том, что разные вызовы метода LINQ могут вызывать или не вызывать ошибку времени компиляции:

// Ok. Let's do some work using LINQ we love so much!
var strings = new[] { "aaa", "bbb", "ccc" };
Func<string, object> converter = item => (object) item;

// The following line makes problems.
var asObjects1 = strings.Select(converter);

// Everything is OK if we use the following line:
var asObjects2 = Enumerable.Select(strings, converter);

person Igor Soloydenko    schedule 21.12.2011    source источник
comment
Почему бы вам не добавить ссылку на IndirectlyUsedLibrary в проект ApplicationAssembly   -  person L.B    schedule 22.12.2011
comment
Это просто. Я не хочу этого делать. В моем реальном проекте в этом нет смысла. Я не знаю, как это вежливо выразить по-английски... Не пытайтесь изменить определение проблемы. Я хочу понять, почему компилятор запрашивает у меня эту ссылку.   -  person Igor Soloydenko    schedule 22.12.2011
comment
Похоже, он не будет жаловаться, если класс, содержащий метод расширения, находится в пространстве имен без ссылок, таком как Test.Magic.   -  person BrainStorm.exe    schedule 23.05.2016


Ответы (3)


Может ли кто-нибудь помочь мне понять, почему это так?

Компилятор C# разумно ожидает, что транзитивное закрытие сборок, на которые ссылаются, доступно во время компиляции. У него нет какой-либо продвинутой логики, которая рассуждает о том, что ему определенно нужно, может понадобиться, может не понадобиться или определенно не нужно знать, чтобы решить все проблемы в анализе типов, которые ваша программа собирается ей бросить. . Если вы прямо или косвенно ссылаетесь на сборку, компилятор предполагает, что там может быть необходимая ему информация о типе.

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

Я не хочу этого делать.

Нам всем приходится делать в жизни то, что мы не хотим делать.

person Eric Lippert    schedule 21.12.2011
comment
Единственная причина, по которой я не хочу ссылаться на сборку, заключается в том, что эти сборки не имеют отношения, если мы смотрим на них с точки зрения «бизнес-модели». Возможно, это была ошибка, но в нашем проекте используется такой подход. Это глупо, но я не могу объяснить, почему мы его используем. Я думаю, что это просто кажется нам естественным. Не лучше ли рассматривать сборки как единицы развертывания типов? В этом случае я мог бы чувствовать себя более свободно в обращении к требуемым сборкам. - person Igor Soloydenko; 22.12.2011
comment
Другие компиляторы для других языков (например, ghc) имеют аналогичное ограничение, и я предположил, что оно было на самом деле в интересах разработчика. Компилятор мог бы разрешить ссылку, не ворча, но принуждение разработчика к предоставлению явных ссылок упрощает развертывание. - person arx; 22.12.2011
comment
И снова. Для меня очень странно, что компилятор молчит, если я вызываю методы LINQ в стиле статического метода. Проблема возникает только тогда, когда я использую стиль вызова «метод экземпляра». - person Igor Soloydenko; 22.12.2011
comment
@keykeeper: лучше думать о сборках как о единицах развертывания типов? Я не знаю -- лучше, чем что? Это хорошая идея думать о них таким образом? Ну, так как это то, чем они являются, да, это хорошая идея думать о них именно так! Сборка по определению является наименьшей самоописывающей единицей развертывания типов в .NET. Вот для чего нужны сборки; для чего еще они нужны, если не для развертывания типов? - person Eric Lippert; 22.12.2011
comment
@keykeeper: я не знаю, почему компилятор жалуется так, а не иначе, но я могу догадаться. Вероятно, происходит то, что ваше использование беглого синтаксиса и перевода компилятора слегка различаются. Поэтому компилятор может загружать разные методы разных типов. Компилятор не агрессивно проверяет наличие транзитивного замыкания сборок. - person Eric Lippert; 22.12.2011
comment
@EricLippert Я недавно читал что-то подобное здесь, но не могу найти вопрос, чтобы процитировать его. Объяснение IIRC заключалось в том, что компилятору нужна информация в сборке, на которую косвенно ссылаются, при разрешении перегрузки при вызове метода расширения, но когда вы вызываете статический метод с традиционным синтаксисом, разрешение перегрузки намного проще и не требует метаданных, на которые косвенно ссылаются. Имеет ли это смысл? - person phoog; 23.12.2011

Я думаю, вы знали об этом, но поскольку тип ProblemType определен в «IndirectlyUsedLibrary», но является обязательным определением для метода расширения Magic, на него нужно ссылаться, чтобы он был доступен во время компиляции.

Что касается «почему»… Ну, компилятору нужно знать подробности о том, что он компилирует, не так ли? Для меня имеет смысл, что компилятору требуется тот же минимальный набор ссылок времени компиляции, что и во время выполнения...

person Reddog    schedule 21.12.2011
comment
Что ж, странно не то, что компилятор запрашивает у меня ссылку, а то, что он запрашивает меня только тогда, когда я использую что-либо, связанное с LINQ, используя методы расширения. Если я делаю то же самое, используя стиль вызова статического метода, компилятор молчит. Вы можете поэкспериментировать с этим: просто прокомментируйте var asObjects1 = strings.Select(converter); - person Igor Soloydenko; 22.12.2011
comment
Обратите внимание еще на одну вещь. Я не использую метод Magic‹T› в ApplicationAssembly. Вот почему я не могу понять, почему компилятор заставляет меня добавить ссылку на IndirectlyUsedAssembly. - person Igor Soloydenko; 22.12.2011

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

Похоже, компилятор начинает сканировать пространство имен Test на наличие расширений, как только вы впервые их используете. Поэтому ссылка обязательна.

person Sergei B.    schedule 21.12.2011
comment
Спасибо за конструктивный ответ! На самом деле программисты сами решают, какие пространства имен использовать в проектах. Предположим, что в моем реальном проекте на это есть веская причина. По крайней мере, я и мои коллеги уже приняли такое решение. Ваша идея о сканировании расширений ОЧЕНЬ интересна, но я не понимаю, как это доказать. - person Igor Soloydenko; 22.12.2011