2009-11-24 4 views
9

scheine ich viel in meinem Code mit dieser Art von Muster zu sein, ich weiß, dass es mehr als das wäre keine einfache Autoproperty ist:C# Auto Property - Ist diese "Muster" Best Practice?

public IList<BCSFilter> BCSFilters { get; set; } 

Der Code, den ich verwendet habe, ist dies:

private IList<BCSFilter> _BCSFilters; 

    /// <summary> 
    /// Gets or sets the BCS filters. 
    /// </summary> 
    /// <value>The BCS filters.</value> 
    public IList<BCSFilter> BCSFilters 
    { 
     get 
     { 
      if (_BCSFilters == null) 
      { 
       _BCSFilters = new List<BCSFilter>(); 
      } 

      return _BCSFilters; 
     } 
     set 
     { 
      _BCSFilters = value; 
     } 
    } 

Dies ist so, ich kann nur MainClass.BCSFilters tun und keine Sorge über die Instanziierung der Liste in den Verbraucher-Code. Ist dies ein "normales" Muster? Der richtige Weg?

Ich konnte nicht eine doppelte Frage

Antwort

31

Dies ist eine Technik, die ich viel selbst verwenden. Dies kann auch dazu beitragen, Speicherressourcen zu sparen, da das Objekt List <> nicht instanziiert wird, es sei denn, die Objekteigenschaft wird tatsächlich innerhalb des konsumierenden Codes verwendet. Dies verwendet eine "Lazy Loading" -Technik.

Auch die "Lazy Loading" -Technik, die Sie aufgelistet haben, ist nicht Thread Safe. Wenn gleichzeitig mehrere Aufrufe an die Eigenschaft erfolgen, könnten Sie mehrere Aufrufe haben, indem Sie die Eigenschaft auf ein neues List <> -Objekt setzen und dadurch vorhandene Listenwerte mit einem neuen, leeren Objekt <> überschreiben. Um die Get-Accessor zu machen Thread-sicher müssen Sie die Lock statement verwenden, etwa so:

private IList<BCSFilter> _BCSFilters; 

// Create out "key" to use for locking 
private object _BCSFiltersLOCK = new Object(); 

/// <summary> 
/// Gets or sets the BCS filters. 
/// </summary> 
/// <value>The BCS filters.</value> 
public IList<BCSFilter> BCSFilters 
{ 
    get 
    { 
     if (_BCSFilters == null) 
     { 
      // Lock the object before modifying it, so other 
      // simultaneous calls don't step on each other 
      lock(_BCSFiltersLOCK) 
      { 
       if (_BCSFilters == null) 
       } 
        _BCSFilters = new List<BCSFilter>(); 
       } 
      } 
     } 

     return _BCSFilters; 
    } 
    set 
    { 
     _BCSFilters = value; 
    } 
} 

Wenn Sie jedoch immer die Liste benötigen <> Objekt instanziiert ist es ein wenig einfacher zu erstellen, sie innerhalb des Objektkonstruktor und stattdessen die automatische Eigenschaft verwenden. Wie folgt aus:

public class MyObject 
{ 
    public MyObject() 
    { 
     BCSFilters = new List<BCSFilter>(); 
    } 

    public IList<BCSFilter> BCSFilters { get; set; } 
} 

Außerdem, wenn Sie die „set“ Accessor Öffentlichkeit verlassen dann die verzehrende Code wird die Eigenschaft auf Null setzen können, die anderen Verbraucher Code brechen kann. Eine gute Technik, um zu verhindern, dass der konsumierende Code den Eigenschaftswert auf Null setzen kann, besteht darin, den Mengenaccessor als privat festzulegen. Wie folgt aus:

public IList<BCSFilter> BCSFilters { get; private set; } 

Eine verwandte Technik ist stattdessen ein IEnumerable <> Objekt aus dem Eigentum zurückzukehren. Auf diese Weise können Sie die Liste <> intern jederzeit durch das Objekt ersetzen und der konsumierende Code wird nicht beeinträchtigt. Um IEnumerable <> zurückzugeben, können Sie einfach das einfache Objekt List <> direkt zurückgeben, da es die Schnittstelle IEnumerable <> implementiert. Wie folgt aus:

public class MyObject 
{ 
    public MyObject() 
    { 
     BCSFilters = new List<BCSFilter>(); 
    } 

    public IEnumerable<BCSFilter> BCSFilters { get; set; } 
} 
+0

+1 vereinbart. füge es nicht dort hinzu, wo es nicht notwendig ist. –

+0

Danke Chris für eine klare und präzise Antwort –

+0

Der einzige Nachteil ist, dass diese Initialisierung nicht threadsicher ist, siehe auch Rob Levines Antwort. (Wäre gut, wenn Sie dies in Ihre Antwort integrieren.) – peterchen

0

Wir verwenden dieses Muster an meinem Arbeitsplatz finden. Es ist praktisch, weil Sie mögliche NULL-Referenz-Ausnahmen im konsumierenden Code vermeiden und den konsumierenden Code einfacher halten.

3

Ihr Ansatz ist die faul init Version von

public class xyz 
{ 
    public xyz() 
    { 
     BCSFilters = new List<BCSFilter>(); 
    } 

    public IList<BCSFilter> BCSFilters { get; set; } 
} 
0

Ja, das völlig normal ist ;-)

Solche faul Schöpfung ist nicht ungewöhnlich und macht guten Sinn. Die einzige Einschränkung ist, dass Sie vorsichtig sein müssen, wenn Sie das Feld überhaupt referenzieren.

Edit: Aber ich muss mit Chris und den anderen gehen: es ist ein (viel) besseres Muster, eine Auto-Eigenschaft zu verwenden und die Sammlung im Konstruktor zu initialisieren.

0

Es ist ein gutes Muster. Autoproperties ist nur eine Abkürzung für das einfachste und wohl am häufigsten anzutreffende Szenario mit Eigenschaften, und für einige Szenarien ist es einfach nicht sinnvoll, es zu verwenden. Was Sie tun könnten, ist, stattdessen BCSFilters im Konstruktor zu instanziieren. Auf diese Weise können Sie autoproperties verwenden und Sie müssen sich weiterhin keine Gedanken über Null-Referenz-Exceptions machen.

5

Es ist das richtige Muster solange das, was Sie wollen, ist:

  • externer Code erlauben, die gesamte Liste (instance.BCSFilters = null)
  • hat die Liste auf magische Weise auf Lese erstellt zu ersetzen. Es ist jedoch schwierig, da Sie es dem Benutzer erlauben, es auf Null (in der Menge) zu setzen, aber Sie erlauben nicht, dass es null bleibt (da ein späteres get zu einer leeren Liste führt).

Sie wollen auch den IList in Nur-Lese-Modus belichten (mit faulen init wenn Sie möchten), so dass die Benutzer können nur Elemente darin hinzufügen oder entfernen, ohne sich um die Liste zu überschreiben zu können. Wenn Sie viele Aufgaben haben, kann es aber auch zu Kopieren kommen.

Normalerweise habe ich nur einen Getter meiner IList Mitglieder, und ich kann sogar IEnumerable aussetzen oder eine Kopie in der get zurückkehren (aber Sie würden spezifische Add provid haben und Methoden entfernen, so YMMV)

+0

+1 - das mühsame Instanziieren einer Liste ist in Ordnung, aber es gibt nicht viele gute Gründe dafür, Code außerhalb der Klasse zuzulassen, um die gesamte Listeninstanz zu ersetzen. –

2

Diese ist ein Beispiel für die Lazy Load pattern. Es ist ein akzeptiertes Muster und absolut gültig.

Sie könnten in C# automatische Eigenschaften verwenden und der Eigenschaft in einem Konstruktor eine Instanz zuweisen. Der Vorteil des Lazy-Load-Musters besteht darin, dass Sie die Eigenschaft erst initialisieren, wenn sie aufgerufen wird. Dies kann in Situationen nützlich sein, in denen die Initialisierung teuer ist.

Ich bevorzuge automatische Eigenschaften mit Konstruktorinitialisierung, weil die Syntax prägnanter ist und es gibt weniger Eingabe , es sei denn, Initialisierung ist teuer, in diesem Fall Lazy Load funktioniert gut.

+0

Danke Dariom für den Link, ich dachte, es muss eine Art Muster gewesen sein ... jetzt weiß ich, dass es Lazy Load war –

10

Ihr Muster ist eine völlig sinnvolle Lazy-Load-Muster, aber beachten Sie, dass es nicht Thread sicher ist.

Wenn zwei Threads zum ersten Mal sehr dicht auf diese Eigenschaft zugegriffen haben, würde Ihr Nullprüfungsmuster die Bedingung nicht verhindern, bei der ein Thread null ergibt, aber vor eine Chance erhält, die Liste zu initialisieren, der zweite Thread auch ergibt null. In dieser Situation werden beide die Liste initialisieren.

Darüber hinaus besteht die Möglichkeit, dass bei der Rückgabe der Eigenschaft ein Thread eine Kopie der Liste hat, während die zweite eine andere hat.

Kein Problem in einer Single-Thread-Umgebung, aber auf jeden Fall etwas zu beachten.

+0

Danke Rob, Wir verwenden derzeit kein Multi-Threading, aber ich stimme zu, wenn wir über diese Dinge nachdenken können so früh wie möglich wird es später Herzschmerzen retten –

5

FYI, eine etwas prägnanter Weise, die genau die gleiche Sache zu tun, sind Sie bereits könnte wie folgt aussehen tun:

private IList<BCSFilter> _BCSFilters; 

public IList<BCSFilter> BCSFilters 
{ 
    get 
    { 
     return _BCSFilters ?? (_BCSFilters = new List<BCSFilter>()); 
    } 
    set 
    { 
     _BCSFilters = value; 
    } 
} 
+0

Oh ja. Coalesce-Operator vereinfacht die Dinge erheblich. +1 –

3

es einen anderen Trick ist :)

faul, um zu verwenden, um von .Net 4 . dies bei Mark Seemann Blog

ich gesehen habe, denke ich:

public class Order 
{ 
    public Order() 
    { 
     _customerInitializer = new Lazy<Customer>(() => new Customer()); 
    } 

    // other properties 

    private Lazy<Customer> _customerInitializer; 
    public Customer Customer 
    { 
     get 
     { 
      return _customerInitializer.Value; 
     } 
    } 

    public string PrintLabel() 
    { 
     string result = Customer.CompanyName; // ok to access Customer 
     return result + "\n" + _customerInitializer.Value.Address; // ok to access via .Value 
    } 
} 

Mitteilung th bei "_customerInitializer" könnte niemals null sein, also ist es sehr genau dasselbe zu verwenden. und es könnte auch thread sicher sein! der Konstruktor kann eine Überladung mit dem LazyThreadSafetyMode Enum bekommen! http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode.aspx