2013-02-25 9 views
7

Ich habe eine kleine Klasse namens Tank hat ein öffentliches Mitglied namens Location, die eine Rectangle (ein Struct) ist. Wenn ich schreibe:Wie mit Werttypen in C# mit Eigenschaften arbeiten?

Tank t = new Tank(); 
t.Location.X+=10; 

alles funktioniert gut, und der Tank bewegt sich.

Aber nachdem ich das Mitglied geändert eine Eigenschaft zu sein, ich nicht mehr diese Syntax verwenden kann. Es kompiliert nicht, weil t.Location jetzt eine Eigenschaft ist (was eine Funktion ist) und eine temporäre Kopie des Standorts zurückgibt (weil es ein Werttyp ist).

Der einzige Weg, ich Location jetzt verwenden kann, ist so etwas wie dies zu tun:

k = t.Location 
k.X +=10; 
t.Location = k; 

Gibt es eine Abhilfe, die mir helfen kann, nicht diesen hässlichen Code zu schreiben, und die intuitive a+=10; Syntax verwenden?

+3

Ich würde vorschlagen, Tank.Offset (int x, int y) 'als Alternative und halten Sie Ihr Rechteck privat. Ihr Offset-Code würde den Standort des Tanks ändern. – FlyingStreudel

+9

Bewegung scheint etwas zu sein, das du am Tank ausführst, daher eine Methode. Anstatt die relative Position zu ändern, indem Sie direkt eine Eigenschaft/ein Feld manipulieren. – Marc

+1

Ich stimme den obigen beiden Kommentaren zu. Aber wenn Sie _really_ wollten, um es zu umgehen, ohne Methoden zu verwenden, _ _ sollten _ separate _ X'- und 'Y'-Eigenschaften erstellen, die nur um das untergeordnete' Location'-Rechteck herum kartografieren, also würden Sie 'myTank.X + = 10' haben. –

Antwort

3

Von @ Servy
"Strukturen sind unveränderlich" gut, nein, sie sind nicht. Sie sollten in den meisten Fällen sein, aber sie sind nicht von Natur aus unveränderlich. Das inhärente Problem hierbei ist, dass die Eigenschaft eine Kopie der Struktur und keine Referenz auf die Struktur zurückgibt. Wenn es eine Syntax in C# für Ref-Returns gäbe, wäre dies möglich.

Grundlegend warum dies nicht funktioniert ist, dass Strukturen unveränderlich sind. Sobald sie gemacht sind, ist es das. Aus diesem Grund ist es nicht möglich, eine Struktur teilweise neu zuzuweisen. Es wäre, als würde man versuchen, sein Bein auszuwechseln. Du kannst nicht. Es ist ein Teil von dir und du bist damit gekommen!

Ich denke, das einzige, was Sie tun können wirst ist Ihre eigene X zu implementieren und Y-Attribute, so dass:

public double LocationX 
{ 
    get 
    { 
     return Location.X; 
    } 
    set 
    { 
     Location = new Rectangle(value,Location.Y); 
    } 
} 

Sie müssen natürlich, dies auch zu Y spiegeln, aber Dies sollte erlauben, was Sie wollen (aber nicht erwarten, dass es schnell oder effizient!)

Während dies Ihre unmittelbare Frage beantwortet, würde ich ein paar Punkte über Ihr Modell erheben. Ich würde nicht versuchen, die Bewegung so zu aktualisieren. Aus Sicht der OO ist Ihr Panzer ein eigenes Objekt und sollte seine eigene Position verwalten. Geben Sie ihm eine Bewegungsanweisung, und aktualisieren Sie dann seine eigene Position.

z:

Tank.MoveRelative(10,0); 
Tank.MoveAbsolute(100,100); 

dies ermöglicht es Ihnen ein wenig mehr Freiheit und ermöglicht es, den Tank, alle Anfragen an sie gestellten zur Validierung basiert auf Logik, die Sie gegeben haben.

+2

"Strukturen sind unveränderlich" gut, nein, sind sie nicht. Sie sollten in den meisten Fällen * sein, aber sie sind nicht von Natur aus unveränderlich. Das inhärente Problem hierbei ist, dass die Eigenschaft eine Kopie der Struktur und keine Referenz auf die Struktur zurückgibt. Wenn es eine Syntax in C# für Ref-Returns gäbe, wäre dies möglich. [Hier sind Eric Lipperts Kommentare zu diesem Thema] (http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/) – Servy

+0

Ich stehe korrigierte Servy, danke für den Kommentar! Post aktualisiert (mit Kredit) –

2

Dieses Problem tritt häufig auf, wenn Sie mit Eigenschaften in 2D und 3D programmieren. Im Allgemeinen besteht die beste Problemumgehung darin, eine Addition zwischen zwei Vektorstrukturen oder zwei unterschiedlichen Strukturen zu implementieren, die sich logisch addieren würden (in Ihrem Fall würden Sie die Addition zwischen einem 2D-Vektor und Ihrem Rechteck implementieren, um seine Position zu versetzen) addiere zwei Rechtecke zusammen).

Dabei können Sie schreiben:

myTank.Location += new Vector2(10, 0); 

, die zwar immer noch etwas klobig, Sie Änderungen für beide Komponenten in einem einzigen Arbeitsgang machen können. Idealerweise wäre der hinzugefügte Vektor ein Geschwindigkeitsvektor, den Sie verwenden würden, um den Standort Ihres Panzers zu aktualisieren.

+0

Interessante Idee. –

+0

Wenn Sie sich jemals für die Verwendung von 3D- oder 2D-Design-Bibliotheken entscheiden, werden Sie feststellen, dass die meisten von Ihnen einfache Vektoroperationen wie Addition, Subtraktion, Skalarmultiplikation/-teilung und Punktmultiplikation für Sie implementieren. Es ist generell eine gute Idee, sie zur Hand zu haben. –

+0

Ich verwende ein XNA-Rechteck, das den + = Operator nicht unterstützt – OopsUser

2

Ich würde vorschlagen, eine Methode, um Ihren Tank zu bewegen.

public class Tank 
{ 
    private Rectangle _location; 

    public int X { get { return _location.X; } } 
    public int Y { get { return _location.Y; } } 

    public Tank(int width, int height /* other params */) 
    { 
     _location = new Rectangle(0, 0, width, height); 
    } 

    public Tank Move(Point offset) 
    { 
     _location.X += offset.X; 
     _location.Y += offset.Y; 

     return this; 
    } 
} 

Verbrauch würde

sein
var tank = new Tank(1, 1); 
tank.Move(new Point(1, 1)).Move(new Point(1, 1)); //Tank would have X: 2, Y: 2 

Dies kann geändert werden Vector2 oder was auch immer zu verwenden.

0

Der Hauptunterschied besteht darin, dass eine Eigenschaft als eine Funktion klassifiziert wird, während ein Feld als eine Variable klassifiziert wird. Der Aufruf von Funktionsmembers wird gestartet.

Eine Umgehung ist die Verwendung eines Felds oder eines Sicherungsspeichers anstelle einer Eigenschaft, genau wie Sie es getan haben. Man sollte vermeiden, veränderbare Werttypen zu erstellen, weil das Verhalten oft surprising ist, schwer vorhersehbar und/oder manchmal sogar inkonsistent.

Hier sind einige wichtige Details, relevante Abschnitte aus der Spezifikation, die helfen, das Verhalten zu beschreiben, das Sie erleben.

C# 4.0 Abschnitt 1.6.7.2

Ein Satz Accessor entspricht ein Verfahren mit einem einzelnen Parameter Wert und keinen Rückgabetyp genannt.

Ein get-Accessor entspricht einer parameterlosen Methode mit einem Rückgabewert des Eigenschaftstyps.

Nun schalten Sie auf 7.5.5 Funktion Mitglied Invocation, der entsprechende Abschnitt:

Wenn [das Funktionselement] eine Instanz Funktionselement in einem Wert Typ deklariert ist:

Wenn [der Instanzausdruck] nicht als Variable klassifiziert ist, wird eine temporäre lokale Variable vom Typ [Instanzexpression] erstellt, und der Wert von [Instanzausdruck] ist zugewiesen Variable. [der Instanzausdruck] wird dann als Referenz auf diese temporäre Variable reklassifiziert. Die temporäre Variable ist als innerhalb von [dem Funktionselement] zugänglich, aber auf keine andere Weise. Nur wenn [der Instanzausdruck] eine echte Variable ist, ist es dem Anrufer möglich, die Änderungen zu beobachten, die [das Funktionsmember] an diesem vorgenommen hat.

0

Wenn eine Klasse oder Struktur-Typ Variable ein Wert-Typ-Feld aussetzt, und dieser Wert Typ macht seinen Inhalt als Felder, Operationen auf diesen Feldern können so effizient wie Operanden auf dem umgebenden Variablentyp durchgeführt werden.Wenn der Wert Typ als Eigenschaft ausgesetzt ist, dann tun das Beste, was man in der Regel so etwas wie:

var temp = t.Location; 
temp.X += 4; 
t.Location = temp; 

Nicht besonders elegant, aber relativ klar und nicht zu schrecklich ineffizient. Eine Alternative wäre der Tank wie ein Verfahren AdjustLocation, etwas aussetzen zu haben:

delegate void ActByRef<T1>(ref T1 p1); 
void ActOnLocation(ActByRef<Point> proc) 
    { proc(ref _Location); } 

und wahrscheinlich auch

delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2); 
void ActOnLocation<PT1>(ActByRef<Point, PT1>, ref PT1 param1) 
    { proc(ref _Location, ref param1); } 

Diese Methoden gehen davon aus, dass die Location Eigenschaft ein Trägerfeld _Location namens verwendet. Code könnte dann so etwas wie:

// Add 5 to X 
myTank.ActOnLocation((ref Point loc) => loc.X += 5); 

oder

// Add YSpeed to Y 
myTank.ActOnLocation((ref Point loc, ref int param) => loc.Y += param, ref YSpeed); 

Beachten Sie, dass im letzteren Fall, weder YSpeed noch this, noch irgendeine andere lokale Variable, nicht innerhalb der Lambda verwendet wird; Stattdessen wird YSpeed als ref Parameter übergeben. Aus diesem Grund muss das System, selbst wenn der obige Code millionenfach ausgeführt wird, nur einen Delegierten generieren, der dann jedes Mal wiederverwendet werden kann.

Wenn die Struktur groß wäre, könnte der obige Ansatz schneller sein als der Ansatz mit einer temporären Variablen. Während der Overhead wahrscheinlich größer ist als die Kosten für das Kopieren einer kleinen Struktur, ist der Overhead unabhängig von der Strukturgröße. Man könnte effizient Strukturen verwenden, die mehrere Kilobyte groß sind, wenn man Konstrukte wie die obigen verwendet, um zu vermeiden, dass man temporäre Kopien machen muss.