«CompanyName.Foo» является «пространством имен», но используется как «тип» - PullRequest
43 голосов
/ 12 января 2010

Переформулировка вопроса

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

Если у меня есть ...

public Foo MyFoo { get; set; }

... почему компилятору все равно, что Foo является одновременно пространством имен и типом? Можете ли вы объявить свойство как пространство имен вместо типа?

Какова логика ошибки компилятора «namespace used like type»? От чего меня это спасает?

[А как мне пометить Эрика Липперта? :)]


Оригинальный вопрос


Проблема

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

И когда я запускаю SqlMetal.exe в базе данных, он генерирует класс CompanyName.Foo.Models.Foo.

Затем, когда я пытаюсь создать свойство с этим классом в качестве типа, как это ...

using CompanyName.Foo.Models;
...
public Foo DataContext { get; set; }

... я получаю ошибку:

«CompanyName.Foo» является «пространством имен», но используется как «тип».

Я вынужден сделать ...

public CompanyName.Foo.Models.Foo Foo { get; set; } // :-(

Вопросы:

  1. Почему возникает эта ошибка? Моя декларация свойства не содержит CompanyName, так почему это проблема? Проще говоря: Foo != CompanyName.Foo. Кроме того, просто чтобы быть уверенным, я выполнил поиск всего своего решения для namespace Foo и нашел ноль хитов (если бы я действительно использовал пространство имен Foo, я мог бы понять, что получаю ошибку).

  2. [ответил] Есть ли способ полностью квалифицироваться Foo каждый раз, когда я хочу его использовать?

  3. [ответил] Есть ли какой-нибудь способ заставить SqlMetal присвоить классу имя, отличное от Foo (без изменения имени моей базы данных)? Я могу изменить пространство имен с помощью переключателя, но я не знаю, как изменить реальное имя класса.

Обновление

Все еще ищу ответ на вопрос (1).

O.K.W. прибил (2) и (3).

Usings

Был сделан запрос на все мои using заявления:

using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Linq;
using MyCompany.Foo.Models;

Ответы [ 8 ]

72 голосов
/ 25 февраля 2010

как мне пометить Эрика Липперта?

Если у вас есть что-то, на что вы хотите обратить мое внимание, вы можете воспользоваться ссылкой "контакт" в моем блоге.

Я все еще совершенно сбит с толку, почему компилятор C # старается проверять наличие коллизий между пространствами имен и типами в тех контекстах, где пространство имен не имеет смысла.

Действительно, правила здесь хитры. По совпадению, две недели назад я написал и опубликовал серию статей в блоге о некоторых из этих проблем, тесно связанных с этой самой проблемой; они действительно начнут жить в начале марта. Подробности смотрите в блоге.

ОБНОВЛЕНИЕ: Статьи, упомянутые выше, здесь:

http://blogs.msdn.com/b/ericlippert/archive/tags/namespaces/

Почему возникает эта ошибка?

Позвольте мне перефразировать вопрос на несколько вопросов.

Какие разделы спецификации оправдывают появление этой ошибки?

Я думаю, что это уже было удовлетворительно рассмотрено в других ответах. Алгоритм разрешения типов очень хорошо указан. Но просто подытожим: будучи внутри что-то с правильным именем «связывает более тесно», чем , используя что-то с правильным именем из снаружи . Когда вы говорите:

using XYZ;
namespace ABC.DEF
{
    class GHI : DEF { }
}

то же самое, что

using XYZ;
namespace ABC
{
    namespace DEF 
    {
        class GHI : DEF { }
    }
}

Так что теперь мы должны определить значение DEF. Мы идем изнутри наружу. Есть ли параметр типа GHI с именем DEF? Посмотри на контейнер. Есть ли член DEF по имени DEF? Посмотри на контейнер. Есть ли член ABC по имени DEF? YES . Были сделаны; мы определили значение DEF, это пространство имен. Мы узнаем значение DEF до и спрашиваем: «Есть ли у XYZ член DEF?»

Какие принципы дизайна влияют на этот дизайн?

Один принцип дизайна: «имена означают одно и то же независимо от того, как вы их используете». Язык не на 100% подчиняется этому принципу; Есть ситуации, в которых одно и то же имя может использоваться для ссылки на две разные вещи в одном и том же коде. Но в целом мы стремимся к ситуации, когда «Foo» два раза в одном и том же контексте означает одно и то же. (См. Мою статью о проблеме цветового цвета для получения более подробной информации по этому вопросу, а также мои статьи по , идентифицирующие нарушения правил "простого имени" .)

Один принцип разработки - «без возврата». В C # мы никогда не говорим: «Я вижу, что вы использовали имя для ссылки на что-то, на что нельзя ссылаться в этом контексте. Позвольте мне отказаться от результата привязки имени и начать заново, ища что-то, что могло бы работать».

Более общий принцип, лежащий в основе принципа «без возврата», заключается в том, что C # не является языком «угадай, что имел в виду пользователь». Вы написали программу, в которой наилучшее связывание идентификатора идентифицировало пространство имен, когда ожидался тип. Есть две возможности. Возможность первая: вы допустили ошибку, о которой хотите сообщить, чтобы you мог предпринять действия для ее исправления. Возможность вторая: вы имели в виду, что мы выбрали менее хорошее связывание, и поэтому мы должны угадать из всех возможных менее удачных связываний, чтобы выяснить, какое из них вы, вероятно, имели в виду.

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

Смысл "отсутствия возврата" заключается в том, что он делает язык намного проще для понимания. Предположим, у вас есть что-то вроде этого беспорядка:

namespace XYZ.DEF
{
    public class GHI {}
}
namespace QRS.DEF.GHI
{
    public class JKL { }
}
...
using QRS;
namespace TUV 
{
  using XYZ;
  namespace ABC
  {
    namespace DEF 
    {
        class GHI { }
        class MNO : DEF.GHI.JKL { }
    }
  }
}

Разработка базового типа МНО. Без возврата мы говорим: «DEF - это ABC.DEF». Поэтому GHI - это ABC.DEF.GHI. Поэтому JKL - это ABC.DEF.GHI.JKL, которая не существует, ошибка. Вы должны исправить ошибку, указав имя типа, которое позволит компилятору определить, какой DEF вы имели в виду.

Если бы у нас был откат, что бы мы делали? Мы получаем эту ошибку, а затем возвращаемся. XYZ содержит DEF? Да. Содержит ли он GHI? Да. Содержит ли он JKL? Снова вернитесь назад. QRS содержит DEF.GHI.JKL? Да.

Что работает , но можем ли мы логически заключить из того, что оно работает, что это то, что пользователь имел в виду ?

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

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

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

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

Наглядный урок о том, как многим разработчикам сильно не нравятся эффекты языкового дизайна, который агрессивно идентифицирует ошибки, вместо того, чтобы догадываться, что разработчик имел в виду выбор худшего результата, см. 116 комментариев к этой статье по второстепенному и довольно незначительному аспекту разрешения перегрузки :

(Обратите внимание, что я больше не отвечаю на комментарии по этому вопросу; я объяснил свою позицию более десяти раз. Если все эти объяснения не убедительны, это потому, что я не очень хороший убедитель.)

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

11 голосов
/ 12 января 2010
  1. Столкновение происходит между пространством имен CompanyName.Foo и CompanyName.Foo.Models.Foo, а не Foo. Я не совсем уверен, как / почему компилятор не может различить оба.

  2. Вы можете попробовать использовать псевдоним пространства имен , чтобы сократить полную квалификацию Foo
    например using coyModels = CompanyName.Foo.Models

  3. Из ссылки кажется, что вы можете использовать /context:<type> и /namespace:<name> для указания класса контекста данных (вместо использования имени таблицы) и пространства имен.

5 голосов
/ 12 января 2010

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

Всякий раз, когда у вас есть что-то вроде этого:

using CompanyName.Foo.Models;

namespace CompanyName.Foo {
    class Test {
        public Foo Model { get; set; } // error CS0118: 'CompanyName.Foo' is a 'namespace' but is used like a 'type'
        public Foo1 Model { get; set; } //OK
    }
}

namespace CompanyName.Foo.Models {
    class Foo1 {
    }
    class Foo {
    }
}

Что на самом деле происходиткаждый предыдущий уровень пространства имен неявно импортируется на каждом уровне.Это имеет смысл, поскольку синтаксис вложенного пространства имен с использованием точки такой же, как и для вложенных пространств имен:

namespace CompanyName {
    using CompanyName; //<--using1 - Implicit using, since we should be able to access anything within CompanyName implicitly.
    namespace Foo {
        using CompanyName.Foo; //<-- using2 Same as above
        class Test {
            public Foo Model { get; set; } //At this stage due to using1 Foo is actually CompanyName.Foo, hence the compiler error
        }
    }
}

Таким образом, внутри класса Test есть два неявных using s:

using CompanyName;
using CompanyName.Foo;

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

РЕДАКТИРОВАТЬ Хороший вопрос.Я выкопал это из MSDN :

Значение имени пространства имен или типа определяется следующим образом:

  • Если namespace-or-type-name состоит из одного идентификатора:

    • Если namespace-or-type-name появляется в теле объявления класса или структуры, тогданачиная с этого объявления класса или структуры и продолжая с каждым вмещающим объявлением класса или структуры (если оно есть), если член с заданным именем существует, доступен и обозначает тип, то namespace-or-type-name ссылается на эточлен.Обратите внимание, что нетипичные члены (константы, поля, методы, свойства, индексаторы, операторы, конструкторы экземпляров, деструкторы и статические конструкторы) игнорируются при определении значения namespace-or-type-name.

    • В противном случае, начиная с пространства имен, в котором происходит имя-пространства-имени-типа, продолжая каждое включающее пространство имен (если оно есть) и заканчивая глобальным пространством имен, выполняются следующие шаги, пока объект не станетрасположен:

    • Если пространство имен содержит член пространства имен с заданным именем , тогда имя пространства имен или типа относится к этому члену и, в зависимости отmember, классифицируется как пространство имен или тип.

    • В противном случае, если пространство имен имеет соответствующее объявление пространства имен, включающее местоположение, где происходит имя пространства имен или типа, то:

    • Если объявление пространства имен содержит директиву using-alias , которая связывает данное имя сh импортированное пространство имен или тип, тогда namespace-or-type-name ссылается на это пространство имен или тип.

    • В противном случае, если пространства имен импортированы директивами using-namespace-директивОбъявление пространства имен содержит ровно один тип с указанным именем, тогда имя пространства имен или типа ссылается на этот тип.
      ...

(жирный шрифт - мой). Это означает, что при разрешении Foo сопоставление его с CompanyName.Foo (первый жирный бит) происходит до сопоставления с директивой using (вторая жирная сборка).

1 голос
/ 01 июля 2013

Я новичок в c #, и я столкнулся с этой ошибкой после декомпиляции приложения ac #, сохранения в виде проекта, попытки немедленной перекомпиляции ... почему приложение могло компилироваться в первую очередь, мне не под силу. ... однако ... проблема и решение довольно просты: по умолчанию, после добавления нового класса, c # использует то же имя для пространства имен, что и для класса в пространстве имен !!!!! Это плохо, потому что без какого-либо скрытого идентификатора, явно указывающего, на какой (пространство имен или тип) вы ссылаетесь, компилятор не сможет определить разницу !!!!! DOH! путь к # !!!! ... РЕШЕНИЕ: Вместо переименования тысячи вещей и двойной проверки всех исправлений, запустите проект, когда перед вами появится список ошибок, щелкните каждую по очереди, чтобы перейти к каждой проблеме. Однажды в вопросе «foo» введите точку (.) После указанного «foo», так что он отображает: foo. .. это должно вызвать меню классов, содержащихся внутри. В этом списке дважды щелкните «foo» (или просто введите имя заново), изменив исходный «foo» на «foo.foo» ... Делайте это для каждой ошибки и решаемой проблемы !!! Вуаля !!!! Я сделал это для целого приложения со сложными именами, и оно работало отлично! Удачного кодирования! - Тим Х.

1 голос
/ 01 марта 2012

У меня была эта проблема, когда я ссылался на класс в отдельной библиотеке классов, где его тип имел то же имя, что и корень пространства имен. Первоначально при ссылке на этот тип в отдельном проекте консольного приложения проблем не было, все скомпилировалось нормально. Однако ссылка из проекта службы Windows генерировала сообщение is a 'namespace' but is used like a 'type'.. Оказывается, для проекта службы Windows в качестве целевого фреймворка было установлено «.NET Framework 4 Client Profile». Изменение этого параметра на .NET Framework 4 устранило ошибку. Надеюсь, это поможет кому-то в подобной ситуации.

1 голос
/ 12 января 2010

почему ты не можешь просто сделать

using CompanyName.Foo; 
... 
public Models.Foo DataContext { get; set; } 
0 голосов
/ 10 января 2013

Это также происходит, если вы генерируете модульные тесты, когда у вас есть пространство имен и класс с тем же именем. Что вы никогда не должны делать, как объяснил Эрик Липперт здесь:

http://blogs.msdn.com/b/ericlippert/archive/2010/03/09/do-not-name-a-class-the-same-as-its-namespace-part-one.aspx

0 голосов
/ 23 февраля 2010

Поскольку вы использовали точечную нотацию для разделения Company и Foo, вы неявно создаете пространство имен Company с вложенным пространством имен Foo, а не Company.Foo, как вы считаете.

Вот почему это не работает:

namespace Company.Foo
{
}

namespace Company.Foo.Models
{
    public class TestClass {
        public Foo DataContext { get; set; }
    }
}

Наиболее близким к Foo является вложенное пространство имен Foo в пространстве имен Company.Однако вы можете сделать это:

using Company.Foo;
using Company.Foo.Models;

namespace Company.Foo
{
    class Program {
        public static void Main() {}
    }
}

namespace Company.Foo.Models
{
    public class Foo { }

}

public class DataContextClass
{
    public Foo DataContext { get; set; } /* Foo here is automatically resolved to Company.Foo.Models.Foo */
}

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

Игорь сказал то же самое, но был более техничным.

...