Verwenden statischer Klassen als Namespaces

21

Ich habe andere Entwickler gesehen, die statische Klassen als Namespaces verwendeten

public static class CategoryA
{
    public class Item1
    {
        public void DoSomething() { }
    }
    public class Item2
    {
        public void DoSomething() { }
    }
}

public static class CategoryB
{
    public class Item3
    {
        public void DoSomething() { }
    }
    public class Item4
    {
        public void DoSomething() { }
    }
}

Um die inneren Klassen zu instanziieren, sieht es wie folgt aus

CategoryA.Item1 item = new CategoryA.Item1();

Das Grundprinzip ist, dass Namespaces mit dem Schlüsselwort "using" ausgeblendet werden können. Bei Verwendung der statischen Klassen müssen jedoch die Klassennamen der äußeren Ebene angegeben werden, wodurch die Namespaces effektiv erhalten bleiben.

Microsoft rät in den Richtlinien davon ab . Ich persönlich denke, es wirkt sich auf die Lesbarkeit aus. Was sind deine Gedanken?

user394128
quelle
1
Es liegt beim Implementierer, ob er die in der Implementierung vorhandenen Namespaces benötigt. Warum sollten Sie die Verwendung der Namespaces (und der gefälschten Namespaces) erzwingen?
Craige
Kann für Gruppierungszwecke sein? Als gäbe es viele Unterklassen und indem man die Unterklassen zu statischen Klassen gruppiert. Das macht die Absicht klar?
user394128
Würde das Einfügen der Unterklassen in einen Subnamespace nicht auch die Absicht klar machen? Dies ist auch die Art von Problem, das durch Dokumentation gelöst wird.
Craige

Antworten:

16

Die Verwendung statischer Klassen als Namespaces widerspricht dem Zweck, Namespaces zu haben.

Der Hauptunterschied ist hier:

Wenn Sie definieren CategoryA, CategoryB<wie Namespaces und wenn die Anwendung verwendet zwei Namensräume:

CategoryA::Item1 item = new CategoryA::Item1(); 
CategoryB::Item1 item = new CategoryB::Item1();

Wenn es sich bei CategoryAoder CategoryBum eine statische Klasse und nicht um einen Namespace handelt, entspricht die Verwendung für die Anwendung fast der oben beschriebenen.

Wenn Sie es jedoch als Namespace definieren und die Anwendung nur 1 Namespace verwendet (ohne Berücksichtigung CategoryB), kann die Anwendung in diesem Fall tatsächlich Folgendes verwenden

using namespace CategoryA; 
Item1 item = new Item1(); 

Aber wenn Sie CategoryAeine statische Klasse definieren, ist die obige undefiniert! Man muss CategoryA.somethingjedes Mal schreiben .

Namespaces sollten verwendet werden, um Namenskonflikte zu vermeiden, während die Klassenhierarchie verwendet werden sollte, wenn die Klassengruppierung für das Systemmodell relevant ist.

Dipan Mehta
quelle
Auch wenn jemand keine Namespaces verwenden möchte, auch wenn er Klassen verwendet, kann das: using Item1 = CategoryA.Item1(Ich denke, das funktioniert für verschachtelte Klassen genauso wie für Namespaces)
George Duckett
1
In C ++ funktioniert ADL nicht mit dem Klassenbereich. Es funktioniert mit Namespace-Bereich. Also in C ++, ist der Namensraum nicht nur natürliche Wahl, aber richtig und idiomatische Wahl auch .
Nawaz
Ich denke, es ist die Absicht von OP, zu vermeiden, den Namespace durch das using-Schlüsselwort zu verbergen. Er möchte Benutzer zwingen, es jedes Mal zu schreiben. Vielleicht ist sein Namensraum wievar s = new HideMe.MyPhoneNumberIs12345678.TheRealUsefulClass()
Gqqnbig
5

Was hier vor sich geht, ist sicherlich kein idiomatischer C # -Code.

Vielleicht versuchen diese anderen, das Modulmuster zu implementieren - dies würde Ihnen Funktionen, Aktionen usw. sowie Klassen im 'Namespace' (dh in diesem Fall statische Klasse) ermöglichen?

FinnNk
quelle
Ich stimme zu, sieht komisch aus.
Marko
4

Zuallererst sollte sich jede Klasse in einem Namespace befinden, damit Ihr Beispiel tatsächlich eher so aussieht:

SomeNamespace.CategoryA.Item1 item = new SomeNamespace.CategoryA.Item1();

Trotzdem sehe ich keinen Vorteil dieser Art von Code-Gymnastik, wenn man einen solchen Namensraum genauso gut definieren kann:

namespace SomeNamespace.CategoryA { ... }

Wenn Sie vielleicht eines Tages darüber nachdenken, statische Methoden auf der höheren Ebene zu speichern, ist dies möglicherweise sinnvoll, entspricht aber nicht den Vorstellungen von C # -Erstellern und ist möglicherweise für andere Benutzer weniger lesbar - ich würde viel Zeit verschwenden Überlegen Sie, warum Sie dies getan haben, und überlegen Sie, ob mir eine Quelldatei fehlt, die Erklärungen liefert.

zmilojko
quelle
Warum sind Aufzählungen die Ausnahme von der Regel ?
sq33G
Das ist eine andere Frage :) Aber ich würde immer noch keine statische Klasse definieren, nur um darin Aufzählungen zu definieren.
zmilojko
1

Namespaces in Java, JavaScript und .NET sind nicht vollständig und erlauben nur das Speichern von Klassen, andere Dinge wie Konstanten oder globale Methoden jedoch nicht.

Viele Entwickler verwenden die Tricks "statische Klassen" oder "statische Methoden", auch wenn einige Leute dies nicht empfehlen.

umlcat
quelle
0

Ich weiß, dass dies eine alte Frage ist, aber ein sehr triftiger Grund, eine Klasse als Namespace zu verwenden (ein nicht statischer Grund), ist, dass C # die Definition von parametrischen oder generischen Namespaces nicht unterstützt. Ich habe zu genau diesem Thema hier einen Blogeintrag verfasst : http://tyreejackson.com/generics-net-part5-generic-namespaces/ .

Das Wesentliche dabei ist, dass bei der Verwendung von Generika zum Abstrahieren großer Mengen von Boilerplate-Code manchmal mehrere generische Parameter zwischen verwandten Klassen und Schnittstellen geteilt werden müssen. Der herkömmliche Weg, dies zu tun, besteht darin, die generischen Parameter, Einschränkungen und alles in jeder Schnittstelle und Klassensignatur neu zu definieren. Im Laufe der Zeit kann dies zu einer Zunahme von Parametern und Einschränkungen führen, ganz zu schweigen davon, dass die verwandten Typen ständig qualifiziert werden müssen, indem die Typparameter von einem Typ an die Typargumente des verwandten Typs weitergeleitet werden.

Durch die Verwendung einer äußeren generischen Klasse und das Verschachteln der zugehörigen Typen kann der Code drastisch getrocknet und seine Abstraktion vereinfacht werden. Man kann dann die parametrische Namespace-Klasse mit einer konkreten Implementierung ableiten, die alle konkreten Details liefert.

Hier ist ein triviales Beispiel:

public  class   Entity
                <
                    TEntity, 
                    TDataObject, 
                    TDataObjectList, 
                    TIBusiness, 
                    TIDataAccess, 
                    TIdKey
                >
        where   TEntity         : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TIdKey>, subclassed
        where   TDataObject     : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TIdKey>.BaseDataObject, subclassed
        where   TDataObjectList : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TIdKey>.BaseDataObjectList, subclassed
        where   TIBusiness      : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TIdKey>.IBaseBusiness
        where   TIDataAccess    : Entity<TEntity, TDataObject, TDataObjectList, TIBusiness, TIDataAccess, TIdKey>.IBaseDataAccess
{

    public class    BaseDataObject
    {
        public TIdKey Id { get; set; }
    }

    public class BaseDataObjectList : Collection<TDataObject> {}

    public interface IBaseBusiness
    {

        TDataObject     LoadById(TIdKey id);
        TDataObjectList LoadAll();
        void            Save(TDataObject item);
        void            Save(TDataObjectList items);
        void            DeleteById(TIdKey id);
        bool            Validate(TDataObject item);
        bool            Validate(TDataObjectList items);

    }

    public interface IBaseDataAccess
    {

        TDataObject     LoadById(TIdKey id);
        TDataObjectList LoadAll();
        void            Save(TDataObject item);
        void            Save(TDataObjectList items);
        void            DeleteById(TIdKey id);

    }

}

So verwendet:

public  class   User 
:
                Entity
                <
                    User, 
                    User.DataObject, 
                    User.DataObjectList, 
                    User.IBusiness, 
                    User.IDataAccess, 
                    Guid
                >
{
    public class DataObject : BaseDataObject
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public class DataObjectList : BaseDataObjectList {}

    public interface IBusiness : IBaseBusiness
    {
        void DeactivateUserById(Guid id);
    }

    public interface IDataAcccess : IBaseDataAccess {}
}

Verbrauchen Sie die Derivate wie folgt:

public class EntityConsumer
{
    private User.IBusiness       userBusiness;
    private Permission.IBusiness permissionBusiness;

    public EntityConsumer(User.IBusiness userBusiness, Permission.IBusiness permissionBusiness) { /* assign dependencies */ }

    public void ConsumeEntities()
    {
        var users       = new User.DataObjectList();
        var permissions = this.permissionBusiness.LoadAll();

        users.Add
        (new User.DataObject()
        {
            // Assign property values
        });

        this.userBusiness.Save(users);

    }
}

Der Vorteil beim Schreiben der Typen auf diese Weise liegt in der erhöhten Typensicherheit und der geringeren Umwandlung von Typen in abstrakte Klassen. Es ist das Äquivalent von ArrayListvs List<T>in größerem Maßstab.

Tyree Jackson
quelle