2013-01-03 7 views
5

Jestem refactoring kodu w aplikacji systemu Windows, a ja natknąłem się na pewnego rodzaju, że nie jestem pewien, że lubię: klasa ma kolor globalny zmienne, takie jak następujące:Strategie przekazywania kolorów wokół (unikanie ref?)

private Color myForegroundColor = Color.Azure; 
private Color myBackgroundColor = Color.Empty; 
// ...etc. 

Istnieje kilka z nich, i są one przekazywane przez około ref metod odpowiedzialnych za opracowanie niektórych części interfejsu użytkownika.

Domyślam się, że Color jest strukturą i że każdy kolor jest przekazywany przez ref w celu uniknięcia tworzenia nowych kopii za każdym razem, gdy wywoływana jest metoda. IE coś takiego:

// Avoid creating a copy of myForgroundColor inside SetUpButton(): 
MyHelperClass.SetUpButton(ref myForegroundColor); 

nie mogę oprzeć się wrażeniu, że to wykorzystanie ref całej tej klasie i pokrewnych zajęć jest zły. Czuje się jak "code smell", ale nie mogę powiedzieć dlaczego.

Widziałem kilka stanowisk na podobnych problemów, z zaleceniami jak „użyć klasy zawierającej kolorów, który jest następnie przekazywany jako typ wartości”, ale to nie jest do końca jasne, jak byłoby najlepiej Zrób to.

Co chciałbym zrobić, to stworzyć coś podobnego do poniższego:

public class ColorContainer 
{ 
    public UiSettingsContainer() 
    { 
     MyColor = Color.Black; 
     MyNextColor = Color.Blue; 
     // ..etc... 
    } 

    public Color MyColor { get; private set; } 
    // ...etc.... 
} 

ten pozwolił mi zachować kontrolę nad kolorami, ale implikacje dotyczące pamięci są trochę niejasne dla mnie; jeśli utworzyłem instancję tej klasy i przekazałem ją do metod wymagających informacji o zawartych kolorach, czy kopia color (z tym, że jest strukturą) nie zostałaby utworzona, gdy tylko metoda implementacji jej użyje?

mam rację zakładając, że ten kod będzie tworzyć nową kopię, a zatem mniej skuteczne ...

// Assumption: This creates a new copy of color in memory. 
public void SetSomeColor(Color col){ 
    someComponent.color = col; 
} 

// Calling it: 
SetSomeColor(myColorContainerInstance.MyColor); 

... niż tego kodu, która stałaby tylko wykorzystanie istniejącej struktury? :

// Question: Does this avoid creating a new copy of MyColor in memory? 
public void SetSomeColor(ColorContainer container){ 
    someComponent.color = container.MyColor; 
} 

// Calling it: 
SetSomeColor(myColorContainerInstance); 

obecnie jestem pochylony w kierunku rozwiązania podobnego do poniższego, w którym wnoszę kolory w oddzielnej klasie i reorganizacji kod trochę, ale nadal korzystać ref. W tym przypadku jednak, MyColor będą musiały być polem publicznego w ColorContainer, co oznacza, że ​​będę mieć mniejszą kontrolę nad tym, kto może go ustawić jego wartość:

// Assumption: This creates a new copy of color in memory. 
public void SetSomeColor(ref Color col){ 
    someComponent.color = col; 
} 

// Calling it: 
SetSomeColor(ref myColorContainerInstance.MyColor); 

Czy jest to dobre rozwiązanie, czy są lepsze strategie obsługiwać takie zasoby?

+0

Czy możesz nie tylko ustawić użytkownika jako statycznego/globalnego gościa? –

+7

Dlaczego chcesz uniknąć kopiowania obiektów "Kolorowych"? – delnan

+0

Jeśli 'SetSomeColor' nie jest oznaczony jako" wirtualny ", istnieje bardzo duża szansa, że ​​JIT będzie tak czy inaczej wstawiał metodę, co zapobiega kopiowaniu struktury, nawet jeśli argument nie jest oznaczony jako" ref ". –

Odpowiedz

4

To wszystko pachnie premature optimization, części 3 i 4 link konkretnie, więc ...

Innym rozwiązaniem byłoby po prostu usunąć pozycje literatury i skopiuj Color struct gdy jest potrzebne. Sama struktura nie jest zbyt duża (4 byte członków i 4 bool członków) i jeśli nie wywołasz kodu, który zmienia kolor kilka milionów razy na sekundę, wymagany czas i pamięć nie stanowią problemu.

+0

Dzięki za opinie, myślę, że masz rację. Tak naprawdę pomyślałem to samo, kiedy po raz pierwszy natknąłem się na ten kod. Moim jedynym prawdziwym powodem do rozważenia tego tutaj jest to, że stary/istniejący kod wszędzie używa 'ref', aby uniknąć duplikatów. Może to był wysiłek, aby zoptymalizować kod, który w rzeczywistości ma większe problemy gdzie indziej. Pomyśl, że pójdę z czymkolwiek, co wygląda najczysto i skoncentruję się na naprawie reszty aplikacji. – Kjartan

+1

Używanie ref byłoby 64-bitowe (przynajmniej w środowisku 64-bitowym) = 8 bajtów. Więc użycie ref mogłoby wymagać "kopiowania" nawet bardziej niż samej struktury, czyż nie? – StampedeXV

+0

@StampedeXV Nie, obie wartości mają 8 bajtów. – Rotem

1

Analogia "gniazd" od dawna jest jedną z moich ulubionych rzeczy tego typu.Każdy parametr metody (uważaj prawą rękę przypisań za parametry również) jest szczeliną. Każda szczelina musi być wypełniona czymś o prawidłowym rozmiarze i "kształcie" (typ), aby metoda mogła zostać wywołana (lub zlecenie do przetworzenia).

W przypadku, gdy twoja metoda wymaga ref Color, wypełniasz szczelinę wskaźnikiem o dowolnym rozmiarze do struktury Color w pamięci. Oczywiście nie mam na myśli wskaźnika w stylu C, ale wciąż jest to ten sam typ - jest to liczba wskazująca położenie zasobu, który ma być użyty, nawet jeśli nie jest reprezentowany w kodzie jako takim. W przypadku Color (bez ref) wypełniasz ją samą strukturą Color.

W zależności od platformy, dla której została skompilowana, wartość, którą przechodzisz (dla ref przejścia przez Kolor) będzie miała 32 lub 64 bity. Sam kolor struct (System.Windows.Media.Color) mający tylko 32 bity (lub 64 bity długości, jeśli używasz System.Drawing.Color) czyni to nieatrakcyjną propozycją - sprawiając, że przeciętny scenariusz jest dokładnie taki sam (pod względem liczby kopii wskaźniki i rozmiary rzeczy ładowanych na stos) jako przekazywanie struktury przez wartość - lepiej tylko w 64-bitowym parowaniu platformowym/32-bitowym, a gorzej tylko w 32-bitowym parowaniu platformowym/64-bitowym. Rzeczywista wartość struktury będzie nadal kopiowana do docelowego boksu nawet po próbie użycia tej samej instancji.

Teraz łączenie kolorów w jedną klasę (gdzie domyślnie jest przekazywana) zmienia nieco ten obraz. Jeśli twoja klasa zawiera powiedzmy 3 kolory, masz 96 bitów (lub 192 bitów) zawartych w niej danych kolorów, przekazując około 64 bitów informacji, aby znaleźć lokalizację prawidłowego "pakietu" dla tej informacji w pamięci . Kolory będą nadal kopiowane do miejsc docelowych, nawet po ich zapakowaniu; ale teraz dodaliśmy narzut z konieczności do ldfld (pole obciążenia)/call (wywołanie metody wstępnie rozwiązanej - dostęp do właściwości) + ldfld/callvirt (wywołanie metody rozwiązanej w czasie wykonywania - dostęp do właściwości) + ldfld w celu uzyskania wartości . Z punktu widzenia wydajności to naprawdę nie pomaga w ogóle, chyba że masz zamiar przekazać mnóstwo danych, a następnie nie używać go.

Krótko mówiąc - jeśli nie ma jakiegoś logicznego zestawu informacji o kolorach, które próbujesz osiągnąć, nie przejmuj się. Typy wartości na stosie są czyszczone natychmiast po zrzuceniu ramki stosu, więc dopóki twój program nie będzie działał w ~ 8 bajtów w stosunku do całkowitej pamięci twojego systemu, to naprawdę nie zyskujesz dzięki podejściu "wg". Klasa wrapper dla kolekcji kolorów prawdopodobnie sprawi, że kod będzie czystszy/lepiej obliczony, ale nie bardziej wydajny.

1

Pozyskiwanie struktur przez ref jest ogólnie rzeczą dobrą, chyba że jedno (1) chce semantyki wartości przesłanianej, lub (2) struktura jest mała i można żyć z semantyką typu pass-by-value.

Jeśli jedna osoba będzie często potrzebować kilku zmiennych (które mogą być same w strukturze) jako grupa, może być pomocne zadeklarowanie przezroczystego typu struktury, aby je zatrzymać, a następnie przekazanie go przez ref.

Należy zauważyć, że przekazywanie struct przez ref ma zasadniczo taki sam koszt jak przekazywanie odwołania do klasy według wartości. Napisanie klasy, która ma utrzymywać strukturę, aby uniknąć używania parametrów, nie jest odpowiednią wygraną. W niektórych przypadkach może to być przydatne typ

class MutableHolder<T> 
{ public T Value; } 

które mogłyby następnie zastosować semantykę odniesienie do dowolnego typu struct, ale chciałbym tylko zasugerować sposób, że jeśli ktoś musi utrzymywać odniesienie poza bieżącym zakresie. W przypadkach, gdy wystarczające jest ref, należy użyć ref.