Проблема с LINQ — необходимо добавить ссылку на ненужную библиотеку

#c# #visual-studio #linq #compiler-construction #extension-methods

#c# #visual-studio #linq #компилятор-конструирование #расширение-методы #visual-студия #linq ( ссылка )

Вопрос:

У меня следующая проблема. У меня есть решение, которое содержит около 40 проектов. Существует проект A, который ссылается на проект B, который ссылается на проект C. В проекте A нет никакого кода, который использует классы из проекта C. Однако, если я использую какой-либо метод расширения LINQ в любом коде, например:

 var r = new int[] { 1, 2, 3 }.Where(a => a > 1);
  

Я получаю ошибку компилятора:

somefile.cs(70,13): ошибка CS0012: тип ‘XXX’ определен в сборке, на которую нет ссылки. Необходимо добавить ссылку на сборку «Project C assembly name, Version=0.0.0.0, Culture = нейтральный, PublicKeyToken=xxx».

Ошибка в строке, которая использует метод расширения linq.

Я использую VS2010, .NET 3.5.

Обновление: Это происходит с каждым методом расширения. Я создал класс в том же файле, который выглядит следующим образом:

 public static class SomeClass
{
    public static int[] Test(this int[] a)
    {
        return a;
    }
}
  

И затем я пишу этот код, и компиляция прерывается с той же ошибкой:

 new int[] { 1, 2, 3 }.Test();
  

Update2: Хорошо, я выяснил, что вызывает ошибку. Но я не знаю почему. Следующий код вызывает ошибку:

 using System.Linq;
using B;

namespace TestApp
{
    public class A
    {
        public void M()
        {
            var c = new string[] { "a", "b", "c" }.Where(s => s != null);
        }
    }
}
  

Но если я удалю использование B (я все еще следую именам из моего описания, которые A ссылаются на B, B ссылается на C), он компилируется.
Это ЕДИНСТВЕННЫЙ код в проекте A. Он компилируется, если я удаляю использование метода расширения или если я удаляю «использование B», как я уже говорил ранее.

Update3:

Прежде всего, спасибо за все ваши предложения. Самый маленький пример, который я могу придумать, следующий. Я создал новый проект, csproj выглядит следующим образом (ничего не было изменено, только название проекта C и идентификатор guid проекта C):

 <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
    <ProductVersion>8.0.30703</ProductVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>{B649AB2C-926A-4AD1-B7E3-5A29AE1E9CC2}</ProjectGuid>
    <OutputType>Library</OutputType>
    <AppDesignerFolder>Properties</AppDesignerFolder>
    <RootNamespace>ClassLibraryTest</RootNamespace>
    <AssemblyName>ClassLibraryTest</AssemblyName>
    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
    <FileAlignment>512</FileAlignment>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>binDebug</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <OutputPath>binRelease</OutputPath>
    <DefineConstants>TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..C.csproj">
      <Project>{55AFFA2D-63E0-4BA9-XXXX-B70E6A936F5E}</Project>
      <Name>C</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <Compile Include="Class1.cs" />
  </ItemGroup>
  <ItemGroup>
    <Folder Include="Properties" />
  </ItemGroup>
  <Import Project="$(MSBuildToolsPath)Microsoft.CSharp.targets" />
</Project>
  

Class1.cs содержит следующий код:

 using C;

namespace TestApp
{
    public static class Ext
    {
        public static void M213dsacxvz(this string[] a)
        {
        }
    }

    public class A
    {
        public void B()
        {
            new string[] { "a", "b", "c" }.M213dsacxvz();
        }
    }
}
  

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

D:xxxClass1.cs (16,13): ошибка CS0012: тип ‘xxx’ определен в сборке, на которую нет ссылки. Вы должны добавить ссылку на сборку ‘xxx, Version=0.0.0.0, Culture = neutral, PublicKeyToken=xxx’.

Если я удалю using C; , он компилируется просто отлично.

Заранее спасибо за помощь.

Комментарии:

1. @empi: Можете ли вы привести короткий, но полный пример, который демонстрирует проблему? Было бы действительно полезно, если бы мы могли воспроизвести это…

2. Иногда вам просто нужно закрыть Visual Studio и перезапустить ее.

3. @empi: Я не предлагал вам публиковать свой реальный проект. Возьмите копию реальной вещи и вырезайте из нее отдельные фрагменты, пока вы либо не разберетесь с причиной этого, либо не получите крошечный пример, показывающий проблему. Вам вообще не нужно публиковать какой-либо коммерчески чувствительный код.

4. Что . Для NET framework предназначен каждый проект?

5. @empi: Да, проект A завершен — но этого недостаточно. Это странно, но пример Рика довольно убедителен. Вы должны быть в состоянии свести вашу ситуацию к тому же самому. Как я уже сказал, вместо того, чтобы начинать с нового решения, возьмите копию существующего и удаляйте из него фрагменты, пока не выясните, что требуется.

Ответ №1:

Вот небольшой пример, который воспроизводит проблему. Это вызвано методом расширения в B, который использует типы, определенные в C, в своей подписи. Даже если метод расширения не используется, ошибка возникает в процессе поиска всех методов расширения, которые доступны через usings.

ConsoleApplicationA.cs:

 using ClassLibraryB;

namespace ConsoleApplication1
{
    public static class Extensions
    {
        public static int Test(this int a)
        {
            return a;
        }
    }

    public class ProgramA
    {
        static void Main(string[] args)
        {
            0.Test();
        }
    }
}
  

ClassLibraryB.cs:

 using ClassLibraryC;

namespace ClassLibraryB
{
    public static class Extensions
    {
        public static ClassC Test(this ClassB b)
        {
            return new ClassC();
        }
    }

    public class ClassB
    {
    }
}
  

ClassLibraryC.cs:

 namespace ClassLibraryC
{
    public class ClassC
    {
    }
}
  

Этот тестовый пример выдает эту ошибку:

ошибка CS0012: тип ‘ClassLibraryC.ClassC’ определен в сборке, на которую нет ссылки. Вы должны добавить ссылку на сборку ‘ClassLibraryC, Version=1.0.0.0, Culture = нейтральный, PublicKeyToken=null’.

Редактировать:

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

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

Вторая правка:

Предостережение: я только что просмотрел это, чтобы очистить пример, и это слабее, чем я думал. Сбой происходит только в том случае, если имя вызываемого метода расширения в A точно совпадает с именем невостребованного метода расширения в B. Так, например, если вы переименуете Test в B в NotCalled , это больше не приведет к сбою, хотя в исходной задаче, предположительно, так и было бы.

Окончательное редактирование:

В сотрудничестве мы сузили необходимые условия для воспроизведения проблемы, описанной в исходном вопросе: метод расширения в B с типами из C в сигнатуре, которые используют ограничение универсального типа. Приведенный выше тестовый пример является более узким примером. Конкретные условия явно связаны с конкретной реализацией способа, которым компилятор выполняет поиск методов расширения.

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

Комментарии:

1. Да, это было бы. Это происходит с моим пользовательским методом и каждым методом расширения из Linq: Where, First и т.д.

2. Проверьте мое UPDATE3 — я создал совершенно случайное имя метода.

3. @empi: Для меня очень важно, можете ли вы ответить на этот вопрос: существуют ли методы расширения в B, которые используют типы из C в своих сигнатурах? Просто используйте «Найти в файлах» -> «Только в проекте» -> Строка поиска: «(this», а затем найдите тип xxx в вашем исходном сообщении об ошибке.

4. Я нашел полный ответ — проблема заключалась в общем ограничении.

Ответ №2:

Я нашел решение.

Вы можете загрузить все решение (по сравнению с 2010) по этой ссылке:https://rapidshare.com/files/4269394110/ExtensionProblem.zip .

Я нашел полный ответ благодаря @Rick Sladkey, чей ответ был почти полным. Единственное, из-за чего код всегда не компилируется, — это ограничение на универсальный метод в проекте B, который задается с использованием class из проекта C.

Хорошо, вот список:

ПРОЕКТ A

ClassA.cs

 using B;

namespace A
{
    public class ClassA
    {
        public void Foo()
        {
            new int[] { 1, 2, 3 }.ExtMethodC();
        }
    }
}
  

Если вы хотите, чтобы код компилировался, прокомментируйте первую строку ( using B; ).

Вот класс, который включает метод случайного расширения в проекте A.

ExtensionsA.cs

 namespace A
{
    public static class ExtensionsA
    {
        public static void ExtMethodC(this int[] a)
        {

        }
    }
}
  

ПРОЕКТ B

Extensions.cs — этот класс имеет метод с общим ограничением T : ClassC

 using C;

namespace B
{
    public static class Extensions
    {
        public static string ExtMethodB<T>(this T cInstance) where T : ClassC
        {
            return cInstance.Foo;
        }
    }
}
  

ПРОЕКТ C

ClassC.cs — нам нужен этот класс, чтобы использовать его в методе расширения в проекте B

 namespace C
{
    public class ClassC
    {
        public string Foo;
    }
}
  

Для меня это выглядит как ошибка в компиляторе, который слишком стремится проверить, может ли метод расширения в проекте B использоваться в проекте A.

Спасибо за все ваши ответы! Особенно @Rick Sladkey.

Комментарии:

1. Интересная головоломка! Обязательно сообщите об этом на connect.microsoft.com

2. Если вам интересно, вот ссылка на мой отчет: connect.microsoft.com/VisualStudio/feedback/details/668498 /…

3. Спасибо, что сообщили об этом. Хотя это закрыто как «Не исправит», это избавило меня от большой головной боли. 🙂

Ответ №3:

Мне кажется, наиболее вероятным ответом является то, что существует метод расширения на IEnumerable или IEnumrable<T> вызываемый Where в B . Этот метод расширения использует тип из C (в качестве возвращаемого типа? или ограничение типа?) которая требует, чтобы вы ссылались на нее.

Что, по словам Visual Studio Where , такое?

Что касается решения, похоже, B оно находится под вашим контролем, поэтому вам следует удалить метод расширения, его действительно не должно там быть. В качестве альтернативного решения вы можете использовать длинный синтаксис: Enumerable.Where(collection, s => s != null) .

Комментарии:

1. Это происходит с моим пользовательским методом и каждым методом расширения из Linq: Where, First и т.д. так что я не думаю, что это так.