'CompanyName.Foo'是'命名空间',但用作'类型'

dev*_*xer 45 c# compiler-construction code-generation namespaces linq-to-sql

重述问题

我正在复活这个问题,因为我今天刚刚再次遇到这个错误,我仍然完全混淆为什么C#编译器无法检查命名空间和类型中的类型之间的冲突,因为命名空间不存在.

如果我有...

public Foo MyFoo { get; set; }
Run Code Online (Sandbox Code Playgroud)

...为什么编译器会关心Foo命名空间和类型?您可以将属性声明为命名空间而不是类型吗?

"名称空间使用类似"编译器错误背后的逻辑是什么?这有什么问题可以拯救我?

[我如何标记Eric Lippert?:)]


原始问题


问题

我有一个带有默认命名空间的项目"Foo" CompanyName.Foo.我有一个名为"Foo"的数据库.

当我在数据库上运行SqlMetal.exe时,它会生成一个类CompanyName.Foo.Models.Foo.

然后,当我尝试使用此类作为类型创建属性时,像这样......

using CompanyName.Foo.Models;
...
public Foo DataContext { get; set; }
Run Code Online (Sandbox Code Playgroud)

......我收到错误:

'CompanyName.Foo'是'命名空间',但用作'类型'.

我被迫做...

public CompanyName.Foo.Models.Foo Foo { get; set; } // :-(
Run Code Online (Sandbox Code Playgroud)

问题:

  1. 为什么会出现此错误?我的财产声明不包含CompanyName,为什么这是一个问题?简单地说:Foo != CompanyName.Foo.另外,为了确保,我搜索了我的整个解决方案,namespace Foo并提出了零命中(如果我实际使用了命名空间Foo,我可以理解得到错误).

  2. [回答]Foo每次我想使用它时,有没有办法完全符合条件?

  3. [回答]有没有办法让SqlMetal将类命名为Foo(不用更改我的数据库名称)?我可以使用开关更改命名空间,但我不知道如何更改实际的类名.

更新

仍在寻求(1)的答案.

OKW钉(2)和(3).

Usings

我的所有using陈述都提出了要求:

using System;
using System.ComponentModel;
using System.Data.Linq;
using System.Linq;
using MyCompany.Foo.Models;
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 72

我如何标记Eric Lippert?

如果您想要引起我的注意,可以使用我博客上的"联系"链接.

我仍然完全混淆为什么C#编译器无法检查命名空间和类型中的类型之间的冲突,因为命名空间不存在.

实际上,这里的规则很棘手.巧合的是,两周前我写了一篇博文,发表了一些关于这个与这个问题密切相关的问题的博客文章; 它们实际上会在三月初上线.观看博客了解详情.

更新:上面提到的文章在这里:

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

为什么会出现此错误?

让我把这个问题改成几个问题.

规范的哪些部分证明了产生此错误的合理性?

我认为其他答案已经令人满意.类型分辨率算法非常明确.但总结一下:在正确的名称内部 "绑定得更紧",而不是从外部使用正确的名称.当你说:

using XYZ;
namespace ABC.DEF
{
    class GHI : DEF { }
}
Run Code Online (Sandbox Code Playgroud)

那是一样的

using XYZ;
namespace ABC
{
    namespace DEF 
    {
        class GHI : DEF { }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以现在我们必须确定DEF的含义.我们从内到外.GHI的类型参数是否称为DEF?没有.看看容器.是否有一个名为DEF的DEF成员?没有.看看容器.是否有ABC的成员称为DEF?YES.我们完成了; 我们已经确定了DEF的含义,它是一个命名空间.我们问"XYZ有成员DEF吗?" 之前,我们发现了DEF的含义.

哪些设计原则会影响这种设计?

一个设计原则是"无论你如何使用它们,名称都是一样的".该语言并非100%遵守这一原则; 在某些情况下,同一名称可用于在同一代码中引用两个不同的东西.但总的来说,我们努力争取的情况是,当你在同一个环境中看到"Foo"两次时,它意味着同样的事情.(有关此问题的一些详细信息,请参阅我关于颜色问题的文章,以及关于识别违反"简单名称"规则的文章.)

一个设计原则是"没有回溯".我们永远不会在C#中说"我看到你使用一个名称来引用在这种情况下不合法的东西.让我放弃名称绑定的结果并重新开始,寻找可行的东西."

作为"无回溯"原则的基础的一个更大的原则是C#不是"猜测用户意味着什么"的语言.您编写了一个程序,其中标识符的最佳绑定在预期类型时标识了命名空间.有两种可能性.可能性1:您发出了一个错误消息,希望能够采取措施纠正错误.可能性二:你意味着一个不那么好的绑定是我们选择的那个,所以我们应该从所有可能的不太好的绑定中猜出来找出你可能意味着哪一个.

这在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 { }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

计算出MNO的基本类型.没有回溯我们说"DEF是ABC.DEF".因此GHI是ABC.DEF.GHI.因此JKL是ABC.DEF.GHI.JKL,它不存在,错误.您必须通过提供一个类型名称来修复错误,该名称允许编译器识别您的DEF.

如果我们有回溯,我们该怎么做?我们得到了这个错误,然后我们回溯了.XYZ是否包含DEF?是.它是否含有GHI?是.它是否包含JKL?没有.再次回溯.QRS是否包含DEF.GHI.JKL?是.

这是有效的,但我们可以从逻辑上得出这样的结论:它是否适用于用户的意思

谁知道这个疯狂的傻瓜?我们在那里得到了各种好的装订,然后在游戏中很晚就坏了.在经历了许多失明的小巷之后,我们偶然发现了想要的答案的想法似乎非常可疑.

这里正确的做法是不要多次回溯并为查找的每个阶段尝试各种更糟糕的绑定.正确的做法是说"伙计,这次查找的最佳匹配给出了无意义的结果;请给我一些不太模糊的东西,请在这里工作."

有关编写编译器在其中一个语言一个不幸的事实被设计大声抱怨,如果最佳匹配的东西,不工作,是开发商经常说"很好,当然,在一般我希望编译器指出我的错误-或者更确切地说,是我所有同事的错误.但对于这个具体案例,我知道我在做什么,所以请编译,做我的意思,而不是我说的."

麻烦的是,你无法双管齐下.你不能同时拥有一个编译器,这两个强制硬性规定,使得它极有可能是可疑代码将积极认定为错误,并允许通过该弄清楚"我真正的意思是"当你写的东西编译启发式疯狂代码编译器非常正确地认为是模棱两可或错误的.

对于很多职业开发者如何强烈反感语言的设计,积极识别错误,而不是猜测,开发商意味着最坏结果被选择的作用对象的教训,看到的未成年人,而不是不重要的116条评论这篇文章重载决策方面:

(请注意,我不再回答有关此问题的评论;我已经十多次解释了我的立场.如果所有这些解释都没有说服力,那是因为我不是一个非常好的说服者.)

最后,如果你真的想测试你对C#中名称解析规则如何工作的理解,试试这个小谜题.几乎每个人都弄错了,或者出于错误的原因做对了.答案就在这里.

  • @DanM:确实,但是有一些不幸的限制。首先,别名指令仅作用域为文件或词法命名空间主体;您必须在使用它的每个文件中重复别名。其次,它不适用于泛型类型。`using IntList = System.Collections.Generic.List<System.Int32>;` 有效,但 `using Foo<T> = Bar<T>;` 无效。第三,IntelliSense 有时会出错;如果你有类似 `using HANDLE=System.Int32;` 之类的东西,那么 IntelliSense 有时会在你不希望它的时候使用 `HANDLE` 而不是 `int`。 (2认同)

o.k*_*k.w 11

  1. 冲突是命名空间之间CompanyName.FooCompanyName.Foo.Models.Foo,而不是Foo.我不确定编译器如何/为什么不能区分这两者.

  2. 您可以尝试使用命名空间别名来缩短完全符合条件,Foo
    例如using coyModels = CompanyName.Foo.Models

  3. 引用来看,似乎可以使用/context:<type>/namespace:<name>指定数据上下文类(而不是使用表名)和命名空间.


Igo*_*aka 5

当类和具有相同名称的命名空间之间存在歧义时,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 {
    }
}
Run Code Online (Sandbox Code Playgroud)

实际发生的是在每个级别隐式导入命名空间的每个先前级别.这是有道理的,因为使用点的嵌套命名空间语法与嵌套命名空间相同:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以在类Test中有两个隐含的usings:

using CompanyName;
using CompanyName.Foo;
Run Code Online (Sandbox Code Playgroud)

因此Foo解析为命名空间因此错误.

编辑好点.我从MSDN中挖出了这个:

namespace-or-type-name的含义确定如下:

  • 如果namespace-or-type-name由单个标识符组成:

    • 如果名称空间或类型名称出现在类或结构声明的主体内,则与此类或结构声明开始以及与每个封闭类或结构声明(如果有的话)继续,如果与给定名称的构件存在,是可访问的,并表示一个类型,然后namespace-or-type-name引用该成员.注意,非类型成员(常量,字段,方法,属性索引,操作符,实例构造,析构函数,和静态构造函数)被确定名称空间或类型名称的含义时忽略.

    • 否则,开始与其中名称空间或类型名称发生时,与每个封闭命名空间(如果有的话)继续进行,并且与全局命名空间结束的命名空间,评估下列步骤,直到找到实体:

    • 如果命名空间包含具有给定名称的命名空间成员,则命名空间或类型名称是指该成员,并根据该成员,被分类为命名空间或类型.

    • 否则,如果命名空间具有包含namespace-or-type-name出现位置的相应命名空间声明,则:

    • 如果命名空间声明包含一个使用别名指示给定的名称与一个导入的命名空间或类型关联,那么名称空间或类型名字引用该命名空间或类型.

    • 否则,如果该命名空间声明的使用名称空间指示引入的名称空间只包含一个类型与给定的名称,那么名称空间或类型名称指向那个类型.
      ...

(Bolding是我的)这意味着在解析时Foo,将它与CompanyName.Foo(第一个粗体位)匹配,然后将其与using指令(第二个粗体构建)匹配.