Właściwość zwracająca tablicę. Czy aby na pewno ?

 

Postawione w tytule posta pytanie jest jak najbardziej na miejscu. Już teraz mogę zdradzić tajemnicę, że nigdy tablicy nie udostępniamy w postaci właściwości.

Na początek chciałbym krótko zarysować problem. W czasie tworzenia kodu okazało się, że bardzo ułatwi mi zachowanie go czytelnym utworzenie klasy przechowującej tablice stworzonych przeze mnie obiektów. Wygląda to następująco:

public partial class DiagramPart
{
    private Signal2D[] firstSignal;
    private Signal2D[] secondSignal;
    ...
}
Klasa ta posiada wiele pól przechowujących tablice stworzonych wcześniej przeze mnie obiektów Signal2D, które w sobie zawierają również tablice z danymi. Chciałby móc operować na tych tablicach obiektów w dalszej części programu. Istnieje zatem naturalna pokusa, aby udostępnić je w następujący sposób:
 
...
public Signal2D[] FirstSignal
{
    get{ return this.firstSignal; }
}
...

W momencie utworzenia takiego kodu, przypomniało mi się, że gdzieś kiedyś czytałem, że nie należy udostępniać w ten sposób całej tablicy, a jedynie jej poszczególne elementy. Przystąpiłem więc do poszukiwań wspomnianego źródła, jednak okazały się one bezskuteczne, chociaż wiele poradników zalecało użycie w takim przypadku indeksatorów. Ale przecież w takim wypadku mogę się dostać jedynie do jednej tablicy obiektów, a ja posiadam ich wiele. W trakcie poszukiwań rozwiały się natomiast wszelkie wątpliwości co do postępowania przedstawionego w powyższym kodzie. Źródło: http://msdn.microsoft.com/en-us/library/0fss9skc.aspx wyraźnie mówi o tym, że: “PropertiesShouldNotReturnArrays”.

Podane w artykule sposoby rozwiązania problemu to: utworzenie ReadOnlyCollection, Collection lub wyglądające najprzyjaźniej utworzenie metody zamiast właściwości. Dwa pierwsze sposoby, przedstawione w zacytowanym artykule, pozwolę sobie pominąć ze względu na to, że nie chcę zastępować tablicy żadną kolekcją. W dalszej części skupię się zatem na rozwiązaniu trzecim, czyli utworzeniu metod, których odpowiedniki w Javie określane są jako getter i setter.

W przypadku chęci utworzenia metody, pozwalającej na uzyskanie obiektu, możliwe jest utworzenie zarówno metody zwracającej pojedynczy element, jak i całej tablicy. W odniesieniu do kodu zamieszczonego na początku posta wyglądałoby to następująco:

//Getter for one element
public Signal2D GetFirstSignals(int i)
{
    return firstSignal[i].Clone();
}

//Getter for whole array
public Signal2D[] GetFirstSignals()
{
    Signal2D[] result = new Signal2D[firstSignal.Length];

    for(int i = 0; i < result.Length; i++)
    {
        result[i] = firstSignal[i].Clone();
    }

    //Do not use Array.Clone() method !

    return result;
}

Ponieważ użytkownik może zmieniać pobrane elementy tablicy/tablice, należy przy zwracaniu obiektu utworzyć jego kopię. W tym celu napisana została dla klasy Signal2D metoda Clone(), której implementacji w tym miejscu nie będę przedstawiał. Przy zwracaniu pojedynczego obiektu wykorzystywany jest przekazany parametr metody, który służy do pobrania obiektu o odpowiednim indeksie i sklonowania go. W drugim przypadku, czyli w momencie zwracania całej tablicy, wykonywana jest identyczna operacja, ale umieszczona w pętli. Przestrzegam przed pokusą skorzystania w tym momencie z metody systemowej Clone() wywoływanej dla tablicy, która jednocześnie wymaga rzutowania. Po użyciu tej metody wykonywana jest tzw. shallow copy, co powoduje, że kopiowane są tylko referencje do obiektów, a nie same obiekty.
Reklamy

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d blogerów lubi to: