2009-03-06 3 views
7

Beim Einrichten einiger Eigenschaften des Referenztyps für ein Projekt, an dem ich gerade arbeite, kam ich über einige Eigenschaften, die ordnungsgemäß initialisiert werden mussten, und sollte nie null sein. Ich habe einige Möglichkeiten gesehen, damit umzugehen, und kann nicht wirklich feststellen, ob es irgendwelche wesentlichen Nachteile bei den wichtigsten Möglichkeiten gibt, mit denen ich dies zu tun habe. Ich würde gerne die Meinung der Community über die beste Art und Weise, wie man damit umgehen soll, und welche möglichen Nachteile die einzelnen Methoden haben könnten, bekommen.Umgang mit C# -Eigenschaften, die nicht null sein sollten

eine einfache Klasse Da habe ich mehrere Möglichkeiten gesehen eine Eigenschaft, nie eine Null-Version dieser Klasse in einer Eigenschaft

public class MyClass 
{ 
    //Some collection of code 
} 

Option 1 dafür, dass handhaben muss - Initialisieren der Sicherungsspeicher

public class OtherClass1 
    { 
     private MyClass _mC = new MyClass(); 
     public MyClass MC 
     { 
      get { return _mC; } 
      set { _mC = value; } 
     } 
    } 

Option 2 - Initialisieren Sie die Eigenschaft im Konstruktor

public class OtherClass2 
    { 
     public MyClass MC { get; set; }  

     public OtherClass2() 
     { 
      MC = new MyClass(); 
     } 
    } 

Opti 3 - Griff Initialisierung im Getter benötigt als

public class OtherClass3 
    { 
     private MyClass _mC; 
     public MyClass MC 
     { 
      get 
      { 
       if (_mC == null) 
        _mC = new MyClass(); 
       return _mC; 
      } 
      set { _mC = value; } 
     } 
    } 

Ich bin sicher, es gibt andere Möglichkeiten, aber diese sind diejenigen, die in den Sinn kommen, und ich habe gesehen. Ich versuche hauptsächlich zu bestimmen, ob es eine gut etablierte Best Practice zu diesem Thema gibt oder ob es ein bestimmtes Problem mit einem der oben genannten Punkte gibt.

Cheers,

Steve

+2

Hier ist ein kompaktere "Get" -Syntax für Option 3: 'get {return _mC ?? (_mC = neue MyClass); } ' – Mas

Antwort

6

Die beste Option, wenn Sie wirklich entfernt, sich mit nur einer neuen Instanz bekommen: nur bieten Konstrukteuren alle erforderlichen Werte, die nehmen, und validieren diesem Punkt an.

+0

Jon. Danke für den Kommentar. In der Situation, die dies ausgelöst hat, gibt es in einem Silverlight ViewModel ObservableCollection-Eigenschaften, die LEER sein können, aber nicht null sein können, da wir niemals die anfängliche Instanz zerstören, nur Inhalte hinzufügen und entfernen. Ich kann damit davon kommen, mein eigenes zu machen. –

+0

Ich bin auch neugierig auf Ihre Meinung zur Verwendung des Vorschlagswert-Attributs. –

+0

Das Standardwert-Attribut wird nur vom Designer verwendet, soweit ich weiß. Es kann auch durch Serialisierung usw. verwendet werden - aber nicht durch normalen Code "nur eine Instanz erstellen". –

1

eine Frage paraphrasiert ich vor ein paar Tagen geschrieben, aber ich denke, dass es hilfreich sein kann, Code Regeln durchzusetzen und sicherzustellen, dass Nulls ein Re nicht verwendet, wo man sie nicht wollen:

Microsoft nur Code Contracts freigegeben, Ein Tool, das in Visual Studio integriert ist und Ihnen ermöglicht, Verträge für Ihren .Net-Code zu definieren und die Laufzeitüberprüfung und zu erhalten.

Beobachten Sie die video on Channel 9, die zeigt, wie es verwendet wird.

Vorerst ist es ein Add-on, aber es wird ein Teil der Basisklassenbibliothek in .NET 4.0

0

Unter der Annahme, es gibt keine Nebenwirkungen in Bezug auf, wenn _mc instanziiert (dh alle anderen Faktoren gleich sind) sein , Bevorzuge ich Option 3, da dies den Overhead einer zusätzlichen Instanz von MyClass auf dem Heap in dem Fall speichert, in dem der Getter von MC niemals aufgerufen wird.

2

Soweit ich weiß, gibt es nicht eine bewährte Best Practice hier aus einem einfachen Grund: jede Ihrer Optionen hat eine andere Leistung/Speicher-Profil. Die erste Option eignet sich für einen Verweis auf ein Objekt, von dem Sie wissen, dass es in einer Klasse instanziiert werden muss, von der Sie sicher sind, dass es verwendet wird. Ehrlich gesagt, nehme ich diesen Ansatz nie, weil ich denke, dass # 2 einfach angemessener ist; nur ein Gefühl, dass das ist, was ein Konstruktor für ist.

Die letzte Option ist geeignet, wenn Sie nicht sicher sind, ob eine Option verwendet wird. Es ermöglicht Ihnen, die Ressource nur bei Bedarf aufzunehmen.

BTW, diese Frage ist richtig "nebenan" zu einer Reihe von anderen Fragen wie die angemessene Verwendung des Singleton-Muster, die Verwendung von abstrakten Klassen oder Schnittstellen für Ihr verzögertes Objekt, etc., die für Sie nützlich sein könnten um mehr Einsicht zu erlangen.

Update: Es scheint mir, dass es mindestens einen Fall gibt, in dem die Initialisierung einer Instanz in der Klassendefinition angemessen ist (Ihre Option # 1). Wenn die Instanz statisch sein wird, dann ist dies der einzige geeignete Ort - initialisieren:

private static readonly DateTime firstClassDate = DateTime.Parse("1/1/2009 09:00:00 AM"); 

ich daran gedacht, wenn die obige Codezeile in einigen Unit-Tests Erstellen ich heute schreibe (die Nur-Lese- ist optional, aber in meinem Fall angemessen).

0

Option (3) hat den Vorteil, dass das Objekt nicht zugewiesen wird, bis es benötigt wird. Es kann leicht als verzögerungsgeladenes Objekt angepasst werden. Wenn Sie also ein Objekt aus einer Datenbank laden, halten Sie Fremdschlüssel zum Laden bereit das vollständige untergeordnete Objekt, wenn erforderlich)

0

Optionen 1 & 2 sind syntaktisch unterschiedlich, aber im Wesentlichen äquivalent. Option 3 ist ein fauler Init-Ansatz, den ich sehr konsequent verwende.
Ich denke, dass sie alle ihren Nutzen haben, und es hängt davon ab, was Sie brauchen.

0

Zunächst einmal sollte die Eigenschaft bei der Initialisierung niemals null oder niemals null sein? Ich vermute, dass du Ersteres meintest. In diesem Fall muss dein Setter-Code verhindern, dass Nullen gesetzt werden.

Das Grundmuster hier ist, dass der äußere Klassenstatus ungültig ist, wenn das innere Klassenfeld keine gültige Zuweisung hat. In diesem Fall sollte der Setter nicht nur das Feld vor Null verteidigen, sondern der Konstruktor sollte sicherstellen, dass er auf den korrekten Wert initialisiert wird.

Ihr Code bedeutet, dass die äußere Klasse die innere Klasse ohne weitere Eingabe aus dem konsumierenden Code instanziieren kann. In der realen Welt benötigt die äußere Klasse weitere Informationen von außen, um entweder eine existierende Instanz der inneren Klasse oder genügend Informationen zu erhalten, um eine solche zu erhalten.

+0

Danke für den Kommentar. Sie haben Ihre Annahme richtig. Ich habe nur einen Beispielcode zusammengeschmissen und habe mich nicht darum gekümmert, den Setter überprüfen zu lassen. In diesem Fall wäre das jedoch wichtig gewesen. –

0

Option 1 ist der Vanille-Weg. Seit den alten Zeiten hatten wir keine automatisch implementierten Eigenschaften (mit dem {get; set;} sintax), so dass sie das Standardverhalten angeben konnten.

Wenn Auto-Implemented eingeführt wird, steigt, da Sie nicht direkt das Feld verwenden, das den Standardwert (_mC) speichert, eine ziemlich gute Frage: "Wo initialisiere ich es?"

  • Option 2 wird aufgerufen eifrig Laden: sobald die Klasse erstellt wird.
  • Option 3 heißt lazy loading: nur so schnell wie es nötig ist.

Ich habe gesehen, dass die allgemein akzeptierte Art und Weise ist die Option 2: eager loading, aber ich glaube, dass dies nur Code zu minimieren ist, die vollständig akzeptabel ist. Das Problem tritt auf, wenn Sie mehrere Konstruktoren mit mehreren Signaturen haben und am Ende alle auf eine void Initialize() Methode zeigen.

Ich bevorzuge Option 3, da es sowohl deklarativer pro Feld/Eigenschaft ist und es Speicher optimiert ist.

Werfen Sie einen Blick auf EntityFramework mit den verschiedenen Geschmacksrichtungen: Code-First oder Database-First und Sie werden bemerken, wie für eingebaute Eigenschaften verwendet eager Lader, während für Navigationseigenschaften es favorisiert (standardmäßig angepasst werden kann) Lader.

Berücksichtigen Sie auch, was in diesem Lader vor sich geht. In Entity Framework bedeutet dies, dass jedes Mal, wenn Sie initialisieren, eine Reise zur Datenbank stattfindet und einen Teil davon abfragt. Sie könnten von Ihrem DBA kurz angebunden werden, mehrere simultane Sitzungen zu haben und einige Transaktionen in einzelne Nutzlasten zu zentralisieren, daher könnten einige Neuverkabelungen für eifrige Lader ins Spiel kommen ... obwohl Sie am Ende eine große Menge an Anfragen abfragen werden Daten und verlangsamen Sie Ihre UX.

In -Code-First Sie das folgende Beispiel sehen kann:

public class Blog 
{ 
    public int BlogId { get; set; } 
    public string Name { get; set; } 
    public string Url { get; set; } 
    public string Tags { get; set; } 

    public virtual ICollection<Post> Posts { get; set; } 
} 

In der obigen Beiträge Sammlung als virtuelle erklärt, wie es zur Laufzeit abhängig von Ihren Einstellungen geändert werden. Wenn Sie es bis zum eifrigen Laden gesetzt ist, wird es tun es als Option 2, während, wenn Sie es zu faul gesetzt ist, wird es zu Option 3

Hoffnung ähnlich modifiziert werden, die hilfreich war