wake-up-neo.com

Warum erlaubt C # keine Nicht-Mitglied-Funktionen wie C++?

C # erlaubt nicht das Schreiben von Nicht-Member-Funktionen, und jede Methode sollte Teil einer Klasse sein. Ich hielt dies für eine Einschränkung in allen CLI-Sprachen. Aber ich habe mich geirrt und festgestellt, dass C++/CLI Nicht-Mitgliederfunktionen unterstützt. Wenn er kompiliert wird, erstellt der Compiler die Methode als Member einer nicht benannten Klasse.

Folgendes sagt der C++/CLI-Standard:

[Hinweis: Nicht-Mitgliederfunktionen werden von der CLI als Mitglieder einer nicht benannten Klasse behandelt. In C++/CLI-Quellcode können solche Funktionen jedoch nicht explizit mit diesem Klassennamen qualifiziert werden. Endnote]

Die Codierung von Nichtmitgliederfunktionen in Metadaten ist nicht angegeben. [Hinweis: Dies verursacht keine Interop-Probleme, da solche Funktionen für die Öffentlichkeit nicht sichtbar sind. Endnote]

Meine Frage ist also, warum implementiert C # so etwas nicht? Oder sollte es Ihrer Meinung nach keine Nicht-Member-Funktionen geben und jede Methode zu einer Klasse gehören? 

Meiner Meinung nach ist die Unterstützung von Nicht-Mitglieder-Funktionen vorhanden, und es hilft, die Schnittstelle der Klasse "Verschmutzung" zu vermeiden. 

Irgendwelche Gedanken ..?

63
Navaneeth K N

Diesen Blogeintrag anzeigen:

http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx

(...)

Ich werde gefragt "Warum implementiert C # Feature X nicht?" die ganze Zeit. Die Antwort ist immer die gleiche: Weil nie jemand diese Funktion entworfen, spezifiziert, implementiert, getestet, dokumentiert und ausgeliefert hat. Alle sechs dieser Dinge sind notwendig, um ein Feature zu realisieren. Alle kosten viel Zeit, Mühe und Geld. Features sind nicht billig und wir bemühen uns sehr, sicherzustellen, dass wir nur solche Features ausliefern, die unseren Benutzern den bestmöglichen Nutzen bieten, angesichts unseres begrenzten Zeit-, Aufwands- und Geldbudgets.

Ich verstehe, dass eine solche allgemeine Antwort die spezifische Frage wahrscheinlich nicht anspricht.

In diesem speziellen Fall war der klare Nutzervorteil in der Vergangenheit nicht groß genug, um die damit verbundenen Komplikationen der Sprache zu rechtfertigen. Durch die Beschränkung, wie verschiedene Sprachentitäten ineinander verschachtelt werden, beschränken wir (1) die legalen Programme auf einen gemeinsamen, leicht verständlichen Stil und (2) ermöglichen die Definition von "Identifier Lookup" -Regeln, die nachvollziehbar, spezifizierbar, implementierbar und überprüfbar sind und dokumentierbar.

Durch das Beschränken von Methodenkörpern auf eine Struktur oder Klasse, die sich immer in einer Struktur oder Klasse befindet, ist es einfacher, die Bedeutung eines nicht qualifizierten Bezeichners zu verstehen, der in einem Aufrufkontext verwendet wird. Ein solches Element ist immer ein aufrufbares Mitglied des aktuellen Typs (oder eines Basistyps). 

(...)

und dieses Anschlussposting:

http://blogs.msdn.com/ericlippert/archive/2009/06/24/it-already-is-a-scripting-language.aspx

(...)

Wie bei allen Entwurfsentscheidungen müssen wir, wenn wir mit einer Reihe konkurrierender, überzeugender, wertvoller und nicht nachvollziehbarer Ideen konfrontiert sind, einen brauchbaren Kompromiss finden. Wir machen das nicht, außer durch unter Berücksichtigung aller Möglichkeiten , was wir in diesem Fall tun.

(Hervorhebung aus dem Originaltext)

86
Eric Lippert

C # erlaubt es nicht, weil Java es nicht zulässt. 

Ich kann mir mehrere Gründe vorstellen, warum die Java-Designer dies wahrscheinlich nicht zugelassen haben

  • Java wurde entworfen, um einfach zu sein. Sie haben versucht, eine Sprache ohne zufällige Verknüpfungen zu erstellen, so dass Sie im Allgemeinen nur eine einfache Möglichkeit haben, alles zu tun, auch wenn andere Ansätze sauberer oder prägnanter wären. Sie wollten die Lernkurve minimieren, und das Lernen "Eine Klasse kann Methoden enthalten" ist einfacher als "Eine Klasse kann Methoden enthalten, und Funktionen können außerhalb von Klassen existieren".
  • Oberflächlich betrachtet sieht es weniger objektorientiert aus. (Alles, was nicht Teil eines Objekts ist, kann offensichtlich nicht objektorientiert sein. Kann es? Natürlich sagt C++ ja, aber C++ war an dieser Entscheidung nicht beteiligt.)

Wie ich bereits in Kommentaren gesagt habe, denke ich, dass dies eine gute Frage ist, und es gibt viele Fälle, in denen Nichtmitgliedsfunktionen vorzuziehen wären. (Dieser Teil ist meistens eine Antwort auf alle anderen Antworten, die sagen "Du brauchst es nicht")

In C++, wo Nicht-Mitgliedsfunktionen zulässig sind, werden sie aus verschiedenen Gründen häufig bevorzugt:

  • Es unterstützt die Verkapselung. Je weniger Methoden Zugriff auf die privaten Mitglieder einer Klasse haben, desto leichter kann diese Klasse umgebaut oder verwaltet werden. Die Kapselung ist ein wichtiger Bestandteil von OOP.
  • Code kann viel einfacher wiederverwendet werden, wenn er nicht Teil einer Klasse ist. Zum Beispiel definiert die C++ - Standardbibliothek std::find oder std :: sort` als Nicht-Member-Funktionen, sodass sie für jeden Typ von Sequenzen wiederverwendet werden können, sei es Arrays, Sets, verknüpfte Listen oder (für std :: find, zumindest) Streams. Die Wiederverwendung von Code ist ebenfalls ein wichtiger Bestandteil von OOP.
  • Es gibt uns eine bessere Entkopplung. Die find-Funktion muss nicht über die LinkedList-Klasse Bescheid wissen, um daran arbeiten zu können. Wenn es als Member-Funktion definiert worden wäre, wäre es ein Member der LinkedList-Klasse, die die beiden Konzepte im Wesentlichen zu einem großen Blob zusammenfasst.
  • Erweiterbarkeit. Wenn Sie akzeptieren, dass das Interface einer Klasse nicht nur "alle öffentlichen Mitglieder" ist, sondern auch "alle Nicht-Member-Funktionen, die für die Klasse zuständig sind", wird es möglich, das Interface einer Klasse zu erweitern, ohne dass Sie sie bearbeiten müssen kompiliere sogar die Klasse selbst neu. 

Die Möglichkeit, Nicht-Mitglied-Funktionen zu haben, hat ihren Ursprung in C (wo Sie keine andere Wahl hatten), aber in modernem C++ ist es ein wichtiges Merkmal für sich, nicht nur aus Gründen der Rückwärtskompatibilität, sondern aus Gründen der Vereinfachung , sauberer und wiederverwendbarer Code.

Tatsächlich scheint C # viel später dasselbe zu erkennen. Warum wurden Ihrer Meinung nach Erweiterungsmethoden hinzugefügt? Sie sind ein Versuch, dies zu erreichen, während die einfache Java-ähnliche Syntax beibehalten wird. Lambdas sind ebenfalls interessante Beispiele, da auch sie im Wesentlichen kleine Funktionen sind, die frei definiert sind und nicht als Mitglieder einer bestimmten Klasse definiert werden. Ja, das Konzept der Nichtmitgliederfunktionen ist nützlich, und die Designer von C # haben dasselbe erkannt. Sie haben gerade versucht, das Konzept durch die Hintertür zu schleichen.

http://www.ddj.com/cpp/184401197 und http://www.gotw.ca/publications/mill02.htm sind zwei Artikel, die von C++ - Experten zu diesem Thema geschrieben wurden.

38
jalf

Nicht-Member-Funktionen sind eine gute Sache weil sie die Verkapselung verbessern und die Kopplung zwischen den Typen reduzieren. Die meisten modernen Programmiersprachen wie Haskell und F # unterstützen kostenlose Funktionen.

11

Was ist der Vorteil von nicht jeder Methode in eine benannte Klasse zu setzen? Warum würde eine Nicht-Member-Funktion die Schnittstelle der Klasse "verschmutzen"? Wenn Sie es nicht als Teil der öffentlichen API einer Klasse wünschen, machen Sie es entweder nicht öffentlich oder fügen Sie es nicht in diese Klasse ein. Sie können immer eine andere Klasse erstellen.

Ich kann mich nicht erinnern, jemals eine Methode schreiben zu wollen, die ohne geeigneten Umfang herumschwimmt - abgesehen von anonymen Funktionen (die natürlich nicht die gleichen sind).

Kurz gesagt, ich sehe keinen Vorteil in Nicht-Member-Funktionen, aber ich sehe Vorteile in Bezug auf Konsistenz, Benennung und Dokumentation, wenn alle Methoden in eine entsprechend benannte Klasse gestellt werden.

9
Jon Skeet
  • Wenn der gesamte Code innerhalb von Klassen liegt, können leistungsfähigere Reflektionsfunktionen verwendet werden.
  • Es ermöglicht die Verwendung von statischen Intializern, mit denen die von statischen Methoden innerhalb einer Klasse benötigten Daten initialisiert werden können.
  • Dadurch werden Namenskollisionen zwischen Methoden vermieden, indem sie explizit in einer Unit eingeschlossen werden, die nicht von einer anderen Kompilierungseinheit hinzugefügt werden kann.
3
Yuliy

Die CLS (Common Language Specification) sagt aus, dass Sie keine Nicht-Mitglieder-Funktionen in einer Bibliothek haben sollten, die dem CLS entspricht. Es ist wie ein zusätzlicher Satz von Einschränkungen zusätzlich zu den grundlegenden Einschränkungen der CLI (Common Language Interface).

Es ist möglich, dass eine zukünftige Version von C # die Möglichkeit enthält, eine using-Direktive zu schreiben, die den Zugriff auf die statischen Member einer Klasse ohne die Klassennamensqualifizierung ermöglicht:

using System.Linq.Enumerable; // Enumerable is a static class

...

IEnumerable<int> range = Range(1, 10); // finds Enumerable.Range

Dann müssen Sie das CLS und die vorhandenen Bibliotheken nicht ändern.

Diese Blogbeiträge zeigen eine Bibliothek für die funktionale Programmierung in C #. Sie verwenden einen Klassennamen, der nur einen Buchstaben lang ist, um das Rauschen zu reduzieren, das durch die Anforderung verursacht wird, statische Methodenaufrufe zu qualifizieren. Beispiele wie dieses würden ein wenig schöner gemacht, wenn using-Direktiven auf Klassen abzielen könnten.

3

Seit Java haben die meisten Programmierer leicht akzeptiert, dass jede Methode Mitglied einer Klasse ist. Ich mache keine nennenswerten Hindernisse und schränke den Begriff der Methode ein, was eine Sprache einfacher macht.

In der Tat schließen Klassenexemplare object und objectexers state aus, sodass das Konzept der Klasse, die nur statische Methoden enthält, etwas absurd aussieht.

2

Bedenken Sie Folgendes: C++ ist eine viel kompliziertere Sprache als C #. Und obwohl sie syntaktisch ähnlich sind, sind sie semantisch sehr verschieden. Sie würden nicht glauben, dass es sehr schwierig sein würde, eine solche Änderung vorzunehmen, aber ich konnte sehen, wie es sein könnte. ANTLR hat eine gute Wiki-Seite namens Was macht ein Sprachproblem schwer? das ist gut für Fragen wie diese. In diesem Fall:

Kontextsensitiver Lexer? Sie können sich nicht entscheiden, welchem ​​Vokabularsymbol Sie entsprechen möchten, wenn Sie nicht wissen, welche Art von Satz Sie analysieren.

Anstatt sich nur um Funktionen zu kümmern, die in Klassen definiert sind, müssen wir uns um Funktionen kümmern, die außerhalb von Klassen definiert sind. Konzeptionell gibt es keinen großen Unterschied. In Bezug auf das Lexisieren und Parsen des Codes besteht nun das Problem, dass Sie sagen müssen: "Wenn eine Funktion außerhalb einer Klasse liegt, gehört sie zu dieser unbenannten Klasse. Wenn sie jedoch innerhalb der Klasse liegt, gehört sie dazu Klasse."

Auch wenn der Compiler auf eine Methode wie diese stößt:

public void Foo()
{
    Bar();
}

... muss nun die Frage beantwortet werden "Befindet sich Bar in dieser Klasse oder ist es eine globale Klasse?"

Weiterleiten oder externe Referenzen? Das heißt, mehrere Durchgänge erforderlich? Pascal verfügt über eine "Vorwärts" -Referenz für die Verarbeitung von Intra-File-Prozedurreferenzen, aber Verweise auf Prozeduren in anderen Dateien über die USES-Klauseln usw. erfordern eine besondere Behandlung.

Dies ist eine weitere Sache, die Probleme verursacht. Denken Sie daran, dass C # keine Vorwärtsdeklarationen erfordert. Der Compiler führt einen einzigen Durchlauf durch, um festzustellen, welche Klassen benannt werden und welche Funktionen diese Klassen enthalten. Jetzt müssen Sie sich Gedanken machen, Klassen und Funktionen zu finden, bei denen Funktionen innerhalb oder außerhalb einer Klasse sein können. Dies ist etwas, über das sich ein C++ - Parser keine Gedanken machen muss, da er alles in der richtigen Reihenfolge analysiert.

Versteht mich jetzt nicht falsch, es könnte wahrscheinlich in C # geschehen, und ich würde wahrscheinlich eine solche Funktion verwenden. Aber ist es wirklich die Mühe wert, diese Hindernisse zu überwinden, wenn Sie einen Klassennamen vor einer statischen Methode eingeben könnten?

1
Jason Baker

Ich denke, Sie müssen wirklich klären, wozu Sie statische Nichtmitgliedsmethoden erstellen möchten.

Zum Beispiel könnten einige der Dinge, für die Sie sie benötigen, mit Extension Methods behandelt werden.

Eine andere typische Verwendung (einer Klasse, die nur statische Methoden enthält) ist in einer Bibliothek. In diesem Fall ist das Erstellen einer Klasse in einer Assembly, die vollständig aus statischen Methoden besteht, wenig schädlich. Sie hält sie zusammen und vermeidet Namenskollisionen. Schließlich gibt es in Math statische Methoden, die demselben Zweck dienen.

Sie sollten das Objektmodell von C++ nicht unbedingt mit C # vergleichen. C++ ist weitgehend (aber nicht perfekt) kompatibel mit C, das überhaupt kein Klassensystem hatte - daher musste C++ diese Programmiersprache aus dem C-Erbe unterstützen, nicht für einen bestimmten Konstruktionsimperativ.

1
Cade Roux

Freie Funktionen sind sehr nützlich, wenn Sie sie mit der Eingabe von Enten kombinieren. Die gesamte C++ - STL basiert darauf. Daher bin ich sicher, dass C # freie Funktionen einführt, wenn es ihnen gelingt, echte Generika hinzuzufügen. 

Wie auch in der Wirtschaft geht es beim Sprachdesign auch um Psychologie. Wenn Sie über kostenlose Funktionen in C # Appetit auf echte Generika erzeugen und nicht liefern, würden Sie C # töten. Dann würden alle C # -Entwickler zu C++ wechseln und niemand möchte, dass dies geschieht, nicht die C # -Community und ganz bestimmt nicht die, die in C++ investieren.

0

Es ist zwar wahr, dass Sie eine Klasse benötigen (z. B. eine statische Klasse mit dem Namen FreeFunctions), um solche Funktionen zu speichern. Sie können jedoch using static FreeFunctions; am Anfang einer Datei platzieren, die die Funktionen von ihr benötigt, ohne Ihren Code mit FreeFunctions.-Qualifikationsmerkmalen zu verschwenden. Ich bin nicht sicher, ob es tatsächlich einen Fall gibt, in dem dies nachweislich unterlegen ist, wenn die Funktionsdefinitionen nicht in einer Klasse enthalten sein müssen.

0
Dylan Nicholson