web-dev-qa-db-de.com

'CompanyName.Foo' ist ein 'Namespace', wird jedoch wie ein 'Typ' verwendet.

Anpassung der Frage

Ich stelle diese Frage wieder auf, weil ich gerade heute wieder auf diesen Fehler gestoßen bin, und ich bin immer noch völlig verwirrt, warum der C # -Compiler nach Kollisionen zwischen Namespaces und Typen in Kontexten sucht, in denen es keinen Sinn macht, dass ein Namespace existiert.

Wenn ich habe...

public Foo MyFoo { get; set; }

... warum sollte der Compiler darauf achten, dass Foo sowohl ein Namespace als auch ein Typ ist? Können Sie eine Eigenschaft als Namespace anstelle eines Typs deklarieren?

Welche Logik steckt hinter dem Compiler-Fehler "Namespace Used Type"? Vor welchem ​​Problem rettet mich das?

[Und wie markiere ich Eric Lippert? :)]


Ursprüngliche Frage


Das Problem

Ich habe ein Projekt "Foo" mit dem Standardnamensraum CompanyName.Foo. Ich habe eine Datenbank, die auch "Foo" genannt wird.

Wenn ich SqlMetal.exe in der Datenbank ausführte, wird eine Klasse CompanyName.Foo.Models.Foo generiert.

Wenn ich dann versuche, eine Eigenschaft mit dieser Klasse als Typ zu erstellen, wie hier ...

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

... ich bekomme den Fehler: 

'CompanyName.Foo' ist ein 'Namespace', wird jedoch wie ein 'Typ' verwendet.

Ich bin gezwungen zu tun ...

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

Fragen:

  1. Warum tritt dieser Fehler auf? Meine Eigenschaftsdeklaration enthält nicht CompanyName. Warum ist das ein Problem? Einfach ausgedrückt: Foo != CompanyName.Foo. Um sicherzugehen, habe ich meine gesamte Lösung nach namespace Foo durchsucht und null Treffer gefunden (wenn ich tatsächlich einen Namespace Foo verwendet hätte, könnte ich verstehen, einen Fehler zu bekommen).

  2. [antwortet] Gibt es einen Weg, um die Qualifikation Foo jedes Mal zu ändern, wenn ich sie verwenden möchte?

  3. [answers] Gibt es eine Möglichkeit, SqlMetal zu veranlassen, der Klasse einen anderen Namen als Foo zu geben (ohne den Namen meiner Datenbank zu ändern)? Ich kann den Namespace mit einem Schalter ändern, aber ich kenne keine Möglichkeit, den tatsächlichen Klassennamen zu ändern.

Update

Ich suche immer noch eine Antwort auf (1).

OK W. genagelt (2) und (3).

Verwendungen

Es wurde eine Anfrage für alle meine using-Anweisungen gemacht:

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

wie tagge ich Eric Lippert?

Wenn Sie auf etwas aufmerksam gemacht werden möchten, können Sie den Link "Kontakt" in meinem Blog verwenden.

Ich bin immer noch völlig verwirrt, warum der C # -Compiler in Kontexten, in denen das Vorhandensein eines Namespaces keinen Sinn macht, nach Kollisionen zwischen Namespaces und Typen sucht.

In der Tat sind die Regeln hier schwierig. Zufälligerweise habe ich vor zwei Wochen eine Reihe von Blog-Artikeln über einige dieser Probleme geschrieben und gepostet, die in engem Zusammenhang mit diesem Problem stehen. Sie werden Anfang März live gehen. Schauen Sie sich den Blog für Details an.

UPDATE: Die oben genannten Artikel sind hier:

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

Warum tritt dieser Fehler auf?

Lassen Sie mich die Frage in mehrere Fragen umformulieren.

Welche Abschnitte der Spezifikation rechtfertigen die Entstehung dieses Fehlers?

Ich denke, das wurde bereits in anderen Antworten zufriedenstellend behandelt. Der Typauflösungsalgorithmus ist sehr gut spezifiziert. Aber um es zusammenzufassen: innerhalb von etwas mit dem richtigen Namen zu sein, "bindet enger" als mit von etwas mit dem richtigen Namen das außerhalb . Wenn du sagst:

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

das ist das gleiche wie

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

Nun müssen wir die Bedeutung von DEF bestimmen. Wir gehen von innen nach außen. Gibt es einen Typparameter von GHI namens DEF? Schauen Sie sich den Container an. Gibt es ein DEF-Mitglied namens DEF? Schauen Sie sich den Container an. Gibt es ein Mitglied von ABC namens DEF? YES. Wir sind fertig; Wir haben die Bedeutung von DEF bestimmt, es ist ein Namespace. Wir entdecken die Bedeutung von DEF , bevor wir fragen: "Hat XYZ ein DEF-Mitglied?"

Welche Gestaltungsprinzipien beeinflussen dieses Design?

Ein Gestaltungsprinzip lautet: "Namen bedeuten dasselbe, egal wie Sie sie verwenden". Die Sprache befolgt diesen Grundsatz nicht zu 100%. Es gibt Situationen, in denen derselbe Name verwendet werden kann, um auf zwei verschiedene Dinge im selben Code zu verweisen. Im Allgemeinen streben wir jedoch eine Situation an, in der es dasselbe bedeutet, wenn Sie "Foo" zweimal im selben Kontext sehen. (Siehe meinen Artikel über The Color Color Problem für einige Details dazu sowie meine Artikel über Feststellen von Verstößen gegen die "einfachen Namen" -Regeln .)

Ein Konstruktionsprinzip ist "kein Backtracking". Wir sagen in C # nie: "Ich sehe, dass Sie einen Namen verwendet haben, um auf etwas zu verweisen, das in diesem Zusammenhang nicht legal ist. Lassen Sie mich das Ergebnis der Namensbindung aufgeben und von vorne beginnen und nach etwas suchen, das möglicherweise funktioniert."

Ein größeres Prinzip, das dem "No Backtracking" -Prinzip zugrunde liegt, ist, dass C # keine "Ratet mal, was der Benutzer damit gemeint hat" -Sprache ist. Sie haben ein Programm geschrieben, in dem die bestmögliche Bindung eines Bezeichners einen Namespace identifizierte, als ein Typ erwartet wurde. Es gibt zwei Möglichkeiten. Möglichkeit eins: Sie haben einen Fehler gemacht, über den Sie informiert werden möchten, damit Sie Maßnahmen ergreifen können, um ihn zu korrigieren. Möglichkeit zwei: Sie wollten, dass eine weniger gute Bindung diejenige ist, die wir wählen, und deshalb sollten wir unter allen möglichen weniger guten Bindungen raten, um herauszufinden, welche Sie sind wahrscheinlich gemeint.

Das ist ein gutes Designprinzip in Sprachen wie JScript. Bei JScript geht es darum, sich durchzumischen, wenn der Entwickler etwas Verrücktes tut. C # ist keine solche Sprache. Das Feedback, das wir von unseren Entwicklern erhalten, ist . Sagen Sie mir, wann etwas kaputt ist, damit ich es beheben kann .

Die Sache mit "no backtracking" ist, dass es die Sprache viel leichter verständlich macht. Angenommen, Sie haben so etwas Chaos:

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 { }
    }
  }
}

Ermitteln Sie den Basistyp von MNO. Ohne Rückverfolgung sagen wir "DEF ist ABC.DEF". Daher ist GHI ABC.DEF.GHI. Daher ist JKL ABC.DEF.GHI.JKL, was nicht existiert, Fehler. Sie müssen den Fehler beheben, indem Sie einen Typnamen angeben, mit dessen Hilfe der Compiler die von Ihnen beabsichtigte DEF identifiziert.

Was müssten wir tun, wenn wir zurückkehren würden? Wir bekommen diesen Fehler und ziehen uns dann zurück. Enthält XYZ eine DEF? Ja. Enthält es einen WHI? Ja. Enthält es eine JKL? Nein. Wieder zurück. Enthält QRS eine DEF.GHI.JKL? Ja.

Das funktioniert , aber können wir logischerweise daraus schließen, dass es funktioniert, dass es derjenige ist, den der Benutzer gemeint hat ?

Wer zum Teufel weiß in dieser verrückten Situation? Wir haben alle Arten von guten Bindungen da drin, die dann sehr spät im Spiel schlecht wurden. Die Vorstellung, dass wir nach vielen Sackgassen auf die gewünschte Antwort gestoßen sind, scheint höchst verdächtig.

Das Richtige dabei ist, nicht mehrere Male zurückzuverfolgen und alle Arten von schlechteren Bindungen für jede Stufe der Suche auszuprobieren. Das Richtige ist zu sagen: "Kumpel, die bestmögliche Übereinstimmung für diese Suche ergibt unsinnige Ergebnisse. Geben Sie mir bitte etwas, mit dem ich weniger mehrdeutig arbeiten kann."

Eine unglückliche Tatsache beim Schreiben einer Sprache, in der der Compiler absichtlich sich lautstark beschwert, wenn die beste Übereinstimmung etwas ist, das nicht funktioniert, ist, dass Entwickler häufig sagen: "Nun, sicher, in general Ich möchte, dass der Compiler alle meine Fehler - oder vielmehr alle Fehler meiner Mitarbeiter - aufzeigt, aber für diesen spezifischen Fall , Ich weiß was ich tue, also bitte, Compiler, tue was ich meine, nicht was ich sage . "

Das Problem ist, Sie können es nicht in beide Richtungen haben. Sie können nicht beide Compiler haben, die starre Regeln durchsetzen, die es sehr wahrscheinlich machen, dass verdächtiger Code aggressiv als fehlerhaft identifiziert wird und verrückten Code über Compiler-Heuristiken zulassen, die herausfinden " was ich wirklich gemeint habe ", wenn Sie etwas schreiben, das der Compiler zu Recht als mehrdeutig oder falsch ansieht.

Eine Lektion darüber, wie viele professionelle Entwickler die Auswirkungen eines Sprachentwurfs, bei dem Fehler aggressiv erkannt werden, vehement ablehnen, anstatt zu vermuten, dass der Entwickler das schlechtere Ergebnis ausgewählt hat, finden Sie unter die 116 Kommentare zu diesem Artikel zu einem kleiner und eher unwichtiger Aspekt der Überlastungslösung :

(Beachten Sie, dass ich nicht mehr auf Kommentare zu diesem Thema antworte. Ich habe meine Position mehr als zehn Mal erläutert. Wenn all diese Erklärungen nicht überzeugend sind, liegt das daran, dass ich kein sehr guter Überzeuger bin.)

Und schließlich, wenn Sie wirklich testen möchten, wie die Regeln für die Namensauflösung in C # funktionieren, probieren Sie dieses kleine Rätsel aus . Fast jeder versteht es falsch oder versteht es aus den falschen Gründen. Die Antwort ist hier .

72
Eric Lippert
  1. Der Konflikt liegt zwischen dem Namespace CompanyName.Foo und CompanyName.Foo.Models.Foo und nicht Foo. Ich bin mir nicht ganz sicher, warum/warum der Compiler nicht beide unterscheiden kann.

  2. Sie können versuchen, Namespace-Alias ​​ zu verwenden, um die vollständige Qualifizierung zu verkürzen. Foo
    z.B. using coyModels = CompanyName.Foo.Models

  3. Aus der reference scheint es, als könnten Sie /context:<type> und /namespace:<name> verwenden, um die Datenkontextklasse (anstelle des Tabellennamens) und den Namespace anzugeben.

11
o.k.w

C # -Compiler kann nicht kompiliert werden, wenn zwischen einer Klasse und einem Namespace mit demselben Namen eine Mehrdeutigkeit besteht. Leider müssen Sie die Klasse nur explizit benennen oder die Datenbank umbenennen. In Ihrem Fall kam der Compiler nicht einmal zum Konflikt, er starb, nachdem Foo als Namespace aufgelöst wurde.

Wann immer du so etwas hast:

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 {
    }
}

Was tatsächlich passiert, ist, dass jede vorhergehende Ebene des Namespaces auf jeder Ebene implizit importiert wird. Dies ist sinnvoll, da die verschachtelte Namespacesyntax, die dot verwendet, mit der Verschachtelung von Namespaces identisch ist:

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
        }
    }
}

Innerhalb der Klasse Test gibt es also zwei implizite usings:

using CompanyName;
using CompanyName.Foo;

Daher wird Foo in den Namespace aufgelöst, daher der Fehler.

EDIT Guter Punkt. Ich habe dies aus MSDN ausgegraben:

Die Bedeutung eines Namespace-oder-Typnamens wird wie folgt festgelegt: 

  • Wenn der Namespace-oder-Typname aus einem einzelnen Bezeichner besteht:

    • Wenn der Namespace-oder-Typname in .__ angezeigt wird. der Körper einer Klasse oder Struktur Deklaration, dann mit dem Start Klassen- oder Strukturdeklaration und mit jeder umschließenden Klasse fortfahren oder struct-Deklaration (falls vorhanden), wenn a Ein Member mit dem angegebenen Namen existiert, ist zugänglich und bezeichnet einen Typ, dann Der Namespace-oder-Typname bezieht sich auf dieses Mitglied Beachten Sie, dass kein Typ Member (Konstanten, Felder, Methoden, Eigenschaften, Indexer, Operatoren, Instanzkonstruktoren, Destruktoren, und statische Konstruktoren) werden ignoriert bei der Bestimmung der Bedeutung eines Namespace-oder-Typname.

    • Ansonsten beginnend mit dem Namespace, in dem die Namespace-oder-Typname tritt auf mit jedem umschließenden .__ fortfahren. Namespace (falls vorhanden) und endet mit der globale Namespace, der folgende Schritte werden ausgewertet, bis eine Entität .__ ist. gelegen:

    • Wenn der Namespace einen Namespace-Member mit der angegebenen .__ enthält. name, dann der Namespace-oder-Typname bezieht sich auf dieses Mitglied und abhängig davon auf dem Mitglied wird als .__ klassifiziert. Namensraum oder einen Typ.

    • Andernfalls, wenn der Namespace eine entsprechende Namespace-Deklaration, die das .__ einschließt. Ort wo die Namespace-oder-Typname auftritt, dann:

    • Wenn die Namespace-Deklaration eine .__ enthält. using-Alias-Direktive, die der angegebene Name mit einem importierten Namespace oder Typ, dann die namespace-oder-type-name bezieht sich auf das Namespace oder Typ.

    • Andernfalls, wenn die Namespaces von der .__ importiert werden. using-Namespace-Direktiven von Die Namespace-Deklaration enthält genau einen Typ mit dem angegebenen Namen, dann die namespace-oder-type-name bezieht sich auf das Art.
      ...

(Fettdruck gehört mir) Dies bedeutet, dass bei der Auflösung von Foo ein Abgleich gegen CompanyName.Foo (erstes fettes Bit) vor dem Abgleich gegen die using-Direktive (zweiter Fettdruck) erfolgt.

5
Igor Zevaka

Ich hatte dieses Problem aufgetaucht, als ich auf eine Klasse in einer separaten Klassenbibliothek verwies, deren Typ denselben Namen wie der Stamm des Namespaces hatte. Beim Verweisen auf diesen Typ in einem separaten Konsolenanwendungsprojekt gab es anfangs kein Problem. Alles wurde gut kompiliert. Die Referenz aus einem Windows-Dienstprojekt erzeugte jedoch die is a 'namespace' but is used like a 'type'.-Nachricht. Es stellte sich heraus, dass das Ziel-Framework von Windows Service Project auf ".NET Framework 4 Client Profile" gesetzt war. Wenn Sie dies in ".NET Framework 4" ändern, wurde der Fehler behoben. Hoffentlich hilft dies jemandem in einer ähnlichen Situation.

1
David Marchelya

Ich bin neu in c # und bin mit diesem Fehler in Berührung gekommen, als ich eine # -Anwendung dekompiliert habe, als Projekt speicherte, der Versuch, sofort neu zu kompilieren ... warum die Anwendung überhaupt kompilieren konnte, liegt mir jedoch nicht. Das Problem und die Lösung ist recht einfach: Standardmäßig verwendet c # beim Hinzufügen einer neuen Klasse denselben Namen für einen Namespace wie für die Klasse innerhalb des Namespaces !!!!! Das ist schlecht, denn ohne einen versteckten Bezeichner, der explizit sagt, auf welchen Namespace oder Typ Sie sich beziehen, kann der Compiler den Unterschied nicht feststellen !!!!! Doh! weiter so c # !!!! ... DIE LÖSUNG: Anstatt tausend Dinge umzubenennen und alle Korrekturen zu überprüfen, führen Sie das Projekt aus. Wenn Sie die Fehlerliste vor sich haben, klicken Sie nacheinander auf die einzelnen Probleme, um zu den einzelnen Problemen zu gelangen. Wenn Sie sich beim fraglichen "foo" befinden, geben Sie nach dem "foo" einen Punkt (.) Ein, so dass Folgendes angezeigt wird: foo. .. Dies sollte das Menü der darin enthaltenen Klassen anzeigen. Doppelklicken Sie in dieser Liste auf "foo" (oder geben Sie einfach den Namen erneut ein), indem Sie das ursprüngliche "foo" in "foo.foo" ändern. Tun Sie dies für jeden Fehler und jedes gelöste Problem !!! Voila !!!! Ich habe dies für eine gesamte Anwendung mit komplexen Namen gemacht, und es hat super funktioniert! Viel Spaß beim Codieren! - Tim H.

1
Tim H.

warum kannst du es nicht einfach tun?

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

Da Sie die Punktnotation verwendet haben, um Company und Foo voneinander zu trennen, erstellen Sie implizit einen Company-Namespace mit einem verschachtelten Foo-Namespace und nicht Company.Foo, wie Sie glauben. 

Deshalb funktioniert das nicht:

namespace Company.Foo
{
}

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

Am nächsten an Foo liegt der verschachtelte Foo-Namespace im Namespace Company. Sie können dies jedoch tun:

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 */
}

Bearbeiten

Igor sagte dasselbe, war aber technischer. 

0
scottm

Dies geschieht auch, wenn Sie Komponententests erstellen, wenn Sie über einen Namensraum und eine Klasse mit demselben Namen verfügen. Was Sie niemals tun sollten, wie von Eric Lippert hier erklärt:

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

0
Jason Axelson