Не давайте классам и пространствам имен одинаковые названия. Часть 1
В книге “Framework Design Guidelines” в разделе 3.4 сказано «не используйте одинаковые названия для пространства имен и типа внутри этого пространства имен». (*) То есть:
namespace MyContainers.List
{
public class List { … }
}
Что в этом плохого? О, позвольте мне перечислить все недостатки.
Часть 1. Коллизии между подключенными сборками.
Вы можете оказаться в ситуации, когда будете думать, что ссылаетесь на что-то одно, а на самом деле будете ссылаться на нечто другое. Предположим вы оказались в следующем положении: вы разрабатываете Blah.DLL и импортируете Foo.DLL и Bar.DLL, и в каждой из них, к сожалению, есть тип с именем Foo:
// Foo.DLL:
namespace Foo { public class Foo { } }
// Bar.DLL:
namespace Bar { public class Foo { } }
// Blah.DLL:
namespace Blah
{
using Foo;
using Bar;
class C { Foo foo; }
}
Компилятор выдаст сообщение об ошибке. “Foo” является неоднозначным между Foo.Foo и Bar.Foo. Какая досада. Думаю, я смогу исправить это путем использования полного квалифицированного имени:
class C { Foo.Foo foo; }
В этом случае мы получаем ошибку неоднозначности “Тип Foo в выражении Foo.Foo является неоднозначным между Foo.Foo и Bar.Foo”. Мы все еще не знаем, на что ссылается первое Foo, и пока этого не выясним, даже не будем пытаться выяснить, на что ссылается второе Foo.
Эта ситуация открывает интересный момент в реализации алгоритма «привязки типов» языка C#. Т.е. того алгоритма, который определяет, о каком типе или пространстве имен идет речь в выражении “X.Y”. Мы не выполняем логического «возврата назад». Мы не рассуждаем следующим образом: «Пусть X означает вот это. Тогда Y не имеет смысла. Возвращаемся назад. Предположим X теперь означает вот то, ага, тогда Y имеет смысл». Мы вычисляем, что однозначно означает X, и только после этого выясняем, что означает Y. Если однозначно определить значение X невозможно, мы не проверяем все варианты для определения того, содержит ли какое-то из этих значений тип Y, мы просто сдаемся.
Предположим, что вы не можете изменить Foo.DLL, в этом случае остается либо удалить “using Foo”, и кто знает, к каким ошибкам это приведет, либо использовать extern alias:
// Blah.DLL:
extern alias FOODLL;
namespace Blah
{
using Foo;
using Bar;
class C { FOODLL::Foo.Foo foo; }
}
Многие разработчики не знакомы с такой возможностью, как “extern alias”, поэтому многие из них, попавшие в эту ситуацию, не по собственной воле оказываются в безвыходном положении. Некоторый из них отправляют гневный письма людям, не являющихся источниками этой проблемы, например, мне.
Эта проблема может быть решена прежде всего авторами Foo.DLL, путем следования рекомендациям и не называя типы и пространства имен одинаковыми именами. В следующей статье рассмотрим следующее: автоматически сгенерированный код поломает свою же собственную работу.
****************
(*) FXCOP содержит флаги проверки более строгих рекомендаций. FXCOP сообщит о конфликте *любых* типов с *любым* пространством имен. Но сегодня я говорю только о простом сценарии, когда одноименный тип содержится в одноименном пространстве имен.