2013-01-03 4 views
5

Ich Refactoring Code in einer alternden Windows-Anwendung, und ich bin auf ein Muster der Art, die ich bin mir nicht sicher, ich mag: Eine Klasse hat globale Farbe Variablen, wie folgt aus:Strategien für die Weitergabe von Farben (Vermeidung ref?)

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

Es gibt eine Reihe von ihnen, und sie durch ref werden bestimmte Teile der Benutzeroberfläche Methoden übergeben um die Verantwortung für die Einrichtung.

Ich nehme an, dass eine Color ist eine Struktur, und dass jede Farbe wird daher durch ref übergeben, um zu vermeiden, neue Kopien davon jedes Mal, wenn eine Methode aufgerufen wird. IE so etwas wie:

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

Ich kann das Gefühl nicht los, dass diese Nutzung von ref in dieser Klasse und verwandte Klassen schlecht ist. Es fühlt sich an wie ein "code smell", obwohl ich nicht wirklich sagen kann warum.

Ich habe ein paar Beiträge zu ähnlichen Problemen gesehen, mit Empfehlungen wie "verwenden Sie eine Klasse mit Farben, die dann als Werttyp übergeben wird", aber es ist nicht ganz klar, wie es am besten wäre mach das.

Was würde ich tun möchte, ist etwas ähnliches erstellen, um die folgenden:

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

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

Das würde mir die Kontrolle über die Farben behalten lassen, aber die Auswirkungen auf das Gedächtnis sind ein wenig zu mir unklar; Wenn ich eine Instanz dieser Klasse erstellt und diese an die Methoden weitergegeben habe, die Informationen über die enthaltenen Farben benötigen, würde nicht eine Kopie einer color (mit einer Struktur) erstellt werden, sobald eine Implementierungsmethode davon Gebrauch macht?

Bin ich in der Annahme richtig, dass dieser Code eine neue Kopie erstellen würde, und damit weniger effektiv ...

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

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

... als dieser Code, der nur Nutzung der bestehenden Struktur machen würde? :

// 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); 

ich ähnlich die folgenden zu einer Lösung, die derzeit bin Neigung, in denen ich die Farben in einer separaten Klasse sammeln und dem Code ein wenig, neu zu organisieren, aber unter Verwendung ref halten. In diesem Fall wird jedoch MyColor ein öffentliches Feld in ColorContainer, sein müssen, was bedeutet, dass ich weniger Kontrolle darüber, wer es ist Wert einstellen:

// 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); 

Ist dies eine gute Lösung, oder gibt es bessere Strategien Ressourcen wie diese zu behandeln?

+0

Kannst du diese Benutzereinstellung nicht einfach zu einem statischen/globalen Typ machen? –

+7

Warum sollten Sie das Kopieren von 'Color' Objekten vermeiden? – delnan

+0

Wenn 'SetSomeColor' nicht als' virtual' gekennzeichnet ist, besteht eine sehr gute Chance, dass das JIT die Methode trotzdem inlining, was verhindert, dass die Struktur kopiert wird, auch wenn das Argument nicht mit 'ref' gekennzeichnet ist. –

Antwort

4

Das Ganze riecht premature optimization, Teile 3 und 4 der Verbindung speziell, so ...

Eine andere Lösung wäre, nur den Refs zu entfernen, und kopieren Sie die Color struct wann immer benötigt wird. Die Struktur selbst ist nicht zu groß (4 byte Mitglieder und 4 bool Mitglieder), und solange Sie nicht den Code aufrufen, der die Farbe mehrere Millionen Mal pro Sekunde ändert, ist die benötigte Zeit und der erforderliche Speicher kein Problem.

+0

Danke für das Feedback, ich denke du hast Recht. Ich dachte eigentlich dasselbe, als ich zum ersten Mal auf diesen Code stieß. Mein einziger Grund, dies hier zu berücksichtigen, ist, dass der alte/existierende Code 'ref' überall verwendet, um Duplikate zu vermeiden. Vielleicht war es ein Versuch, Code zu optimieren, der anderswo größere Probleme hat. Ich denke, ich gehe einfach mit allem, was am saubersten aussieht, und konzentriere mich darauf, den Rest der Anwendung zu reparieren. – Kjartan

+1

Verwenden von ref wäre 64 Bit (mindestens in einer 64-Bit-Umgebung) = 8 Bytes. Also würde die Verwendung eines Verweises noch mehr "kopieren" als die Struktur selbst, oder? – StampedeXV

+0

@StampedeXV Nein, beide Werte sind 8 Bytes. – Rotem

1

Die "slots" Analogie ist seit langem einer meiner Favoriten für diese Art von Sache.Jeder Methodenparameter (betrachten Sie auch die rechte Hand von Zuweisungen als Parameter) ist ein Slot. Jeder Slot muss mit etwas von der richtigen Größe und "Form" (Typ) gefüllt werden, damit eine Methode aufgerufen werden kann (oder eine zu bearbeitende Zuweisung).

Für den Fall, dass Ihre Methode eine ref Color erfordert, füllen Sie den Steckplatz mit einem beliebigen Größenzeiger auf eine Color Struktur im Speicher. Natürlich meine ich nicht den C-Stil-Zeiger, aber es ist immer noch die gleiche Art von Sache - es ist eine Zahl, die den Speicherort der Ressource angibt, die Sie verwenden möchten, auch wenn sie nicht im Code dargestellt wird. Im Fall von Color (ohne Ref) füllen Sie es mit der Struktur selbst.

Abhängig von der Plattform, für die Sie kompiliert haben, ist der Wert, den Sie weitergeben (durch Ref-Weitergabe von Color), entweder 32 oder 64 Bit lang. Die Farbstruktur (System.Windows.Media.Color) selbst ist nur 32 Bit lang (oder 64 Bit lang, wenn Sie System.Drawing.Color verwenden) macht dies zu einem unattraktiven Vorschlag - so dass das durchschnittliche Szenario genau gleich ist (in Bezug auf die Anzahl der Kopien des Zeiger und Größen der Dinge, die auf den Stack geladen werden) als Übergabe der Struktur nach Wert - besser nur in der 64-Bit-Struktur/32-Bit-Plattform-Paarung und schlechter nur in der 32-Bit-Struktur/64-Bit-Plattform-Paarung. Der tatsächliche Wert der Struktur wird auch nach diesem Versuch in den Ziel-Slot kopiert, damit sie dieselbe Instanz verwendet.

Jetzt, Farben in einer Klasse zusammenfassen (wo durch Ref-Passing ist Standard) ändert dieses Bild ein wenig. Wenn Ihre Klasse drei Farben enthält, sind darin 96 Bits (oder 192 Bits) Farbdaten enthalten, die um maximal 64 Bits an Informationen herumreichen, um den Ort des korrekten "Pakets" für diese Information im Speicher zu finden . Die Farben werden auch nach dem Verpacken noch in ihre Ziel-Slots kopiert; aber jetzt haben wir einen Aufwand von ldfld (Feld laden)/call (rufen Sie eine vor-aufgelöste Methode - Eigenschaft Zugriff) + ldfld/callvirt (rufen Sie eine Laufzeit aufgelöst Methode - Eigenschaft Zugriff) + ldfld, um tatsächlich den Wert zu bekommen . Aus Performance-Sicht hilft Ihnen das überhaupt nicht, es sei denn, Sie beabsichtigen, viele Daten zu übertragen und sie dann nicht zu verwenden.

Die lange Geschichte kurz - es sei denn, es gibt einige logische Gruppierung von Farbinformationen, die Sie erreichen möchten, nicht stören. Die Werttypen auf dem Stapel werden sofort bereinigt, wenn der Stapelrahmen geöffnet wird. Wenn Ihr Programm also nicht um etwa 8 Byte unterhalb des Gesamtspeichers Ihres Systems läuft, gewinnen Sie wirklich nichts mit dem by ref-Ansatz. Die Wrapperklasse für eine Sammlung von Farben würde den Code wahrscheinlich sauberer/besser kalkuliert, aber nicht leistungsfähiger machen.

1

Passing Strukturen von ref ist in der Regel eine gute Sache, es sei denn, entweder (1) will man Semantik Pass-by-Wert, oder (2) die Struktur ist klein und man kann mit pass-by-Wert Semantik leben.

Wenn man häufig einige Variablen (die selbst Structs sein könnten) um eine Gruppe herum haben möchte, kann es hilfreich sein, einen transparenten Strukturtyp zu deklarieren, um sie zu halten, und diesen dann durch ref zu übergeben.

Beachten Sie, dass das Übergeben einer Struktur durch ref im Wesentlichen die gleichen Kosten verursacht wie das Übergeben einer Klassenreferenz nach Wert. Das Schreiben einer Klasse, um die Struktur zu halten, rein, so dass man vermeiden kann, ref Parameter zu verwenden, ist nicht geeignet, ein Leistungsgewinn zu sein. In einigen Fällen kann es nützlich sein, eine Art

class MutableHolder<T> 
{ public T Value; } 

die dann Referenzsemantik zu jedem Strukturtyp anwenden könnte, aber ich würde vorschlagen, nur tun, wenn man braucht eine Referenz außerhalb des aktuellen Bereichs bestehen bleiben. In Fällen, in denen ref ausreichen wird, sollte ref verwendet werden.

Verwandte Themen