2013-02-25 28 views
7

Tôi có một lớp học nhỏ gọi là Tank có một thành viên công khai được gọi là Location là một Rectangle (một struct). Khi tôi viết:Làm thế nào để làm việc với các loại giá trị trong các thuộc tính sử dụng C#?

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

mọi thứ hoạt động tốt và bể di chuyển.

Nhưng sau khi tôi đã thay đổi thành viên thành tài sản , Tôi không còn có thể sử dụng cú pháp này nữa. Nó không biên dịch, vì t.Location bây giờ là một thuộc tính (có chức năng) và trả về một bản sao tạm thời của vị trí (vì nó là một loại giá trị).

Cách duy nhất tôi có thể sử dụng Location tại là để làm một cái gì đó như thế này:

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

Có workaround có thể giúp tôi không phải viết mã xấu xí này, và sử dụng a+=10; cú pháp trực quan?

+3

Tôi sẽ đề nghị 'Tank.Offset (int x, int y)' làm phương án thay thế và giữ hình chữ nhật của bạn riêng tư. Mã bù đắp của bạn sẽ thay đổi vị trí của xe tăng. – FlyingStreudel

+9

Phong trào có vẻ giống như một cái gì đó bạn thực hiện trên xe tăng, do đó một phương pháp. Thay vì thay đổi vị trí tương đối bằng cách thao tác trực tiếp một thuộc tính/trường. – Marc

+1

Tôi đồng ý với hai ý kiến ​​trên. Nhưng nếu bạn _really_ muốn đi xung quanh nó mà không sử dụng các phương thức, bạn _could_ tạo các thuộc tính 'X' và' Y' riêng biệt, bản đồ xung quanh hình chữ nhật 'Vị trí 'chưa hoàn chỉnh, vì vậy bạn có' myTank.X + = 10'. –

Trả lời

3

Từ @ Servy
"cấu trúc không thay đổi", không, chúng không có. Họ nên, trong hầu hết trường hợp, nhưng họ không phải là vốn không thay đổi. Vấn đề cố hữu ở đây là thuộc tính trả về một bản sao của cấu trúc, không phải là tham chiếu đến cấu trúc. Nếu có một cú pháp trong C# cho ref trả về, thì điều này là có thể.

Về cơ bản lý do tại sao điều này sẽ không hoạt động là cấu trúc không thay đổi. Một khi họ đã thực hiện, đó là nó. Vì lý do này, không thể phân công lại một phần cấu trúc. Nó sẽ giống như cố gắng trao đổi chân của bạn. Bạn không thể. Đó là một phần của bạn, và bạn đã đến với nó!

Tôi nghĩ rằng điều duy nhất bạn sẽ có thể làm là thực hiện X của riêng bạn và Y thuộc tính, chẳng hạn rằng:

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

Bạn rõ ràng là cần phải gương này để Y là tốt, nhưng điều này sẽ cho phép những gì bạn muốn (nhưng không mong đợi nó được nhanh chóng hoặc hiệu quả!)

Trong khi điều này trả lời câu hỏi trực tiếp của bạn, tôi sẽ nêu ra một vài điểm về mô hình của bạn. Tôi sẽ xem xét không cố gắng để cập nhật các phong trào như thế này. Từ quan điểm của OO, xe tăng của bạn là đối tượng riêng của nó và cần phải quản lý vị trí riêng của nó. Cung cấp cho nó một hướng dẫn chuyển động, và sau đó có nó cập nhật vị trí riêng của nó.

ví dụ:

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

này cho phép bạn một chút tự do hơn và cho phép các bồn chứa để xác nhận bất cứ yêu cầu thực hiện trên nó dựa trên logic bạn đã đưa ra nó.

+2

"cấu trúc không thay đổi" tốt, không, chúng không. Chúng * phải là *, trong hầu hết các trường hợp, nhưng chúng không phải là bất biến. Vấn đề cố hữu ở đây là thuộc tính trả về một bản sao của cấu trúc, không phải là tham chiếu đến cấu trúc. Nếu có một cú pháp trong C# cho ref trả về, thì điều này là có thể. [Đây là Eric Lipperts bình luận về vấn đề này] (http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/) – Servy

+0

Tôi đã sửa Servy, cảm ơn vì bình luận! Bài đăng được cập nhật (có tín dụng) –

2

Sự cố này xảy ra khá thường xuyên khi bạn bắt đầu lập trình trong không gian 2D và 3D bằng cách sử dụng các thuộc tính. Nói chung, cách giải quyết tốt nhất là bổ sung thêm hai cấu trúc vectơ hoặc hai cấu trúc khác nhau sẽ cộng với nhau theo cách hợp lý (trong trường hợp của bạn, bạn sẽ bổ sung thêm một vector 2D và hình chữ nhật để bù đắp vị trí của nó) thêm hai hình chữ nhật với nhau).

Làm như vậy, cho phép bạn viết:

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

nào, trong khi vẫn còn hơi vụng về, cho phép bạn thực hiện thay đổi cho cả hai thành phần trong một hoạt động đơn lẻ. Lý tưởng nhất, vector được thêm vào sẽ là một vector tốc độ mà bạn sẽ sử dụng để cập nhật vị trí của xe tăng của bạn.

+0

Ý tưởng thú vị. –

+0

Nếu bạn quyết định thử sử dụng bất kỳ thư viện thiết kế 3D hoặc 2D nào, bạn sẽ thấy rằng hầu hết chúng sẽ thực hiện các thao tác vectơ cơ bản như cộng, trừ, nhân/chia vô hướng và phép nhân dấu chấm cho bạn. Nói chung là một ý tưởng hay để có chúng trên tay. –

+0

Tôi đang sử dụng hình chữ nhật XNA, không hỗ trợ toán tử + = – OopsUser

2

Tôi khuyên bạn nên tạo phương pháp để di chuyển xe tăng của mình.

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; 
    } 
} 

Cách sử dụng sẽ

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

Điều này có thể được thay đổi để sử dụng Vector2 hoặc bất cứ điều gì.

0

Sự khác biệt cốt lõi là thuộc tính được phân loại là hàm trong khi trường được phân loại là biến. Cú đá gọi hàm thành viên trong.

Công việc xung quanh là sử dụng một trường hoặc cửa hàng sao lưu thay vì một thuộc tính giống như bạn đã làm. Người ta nên tránh tạo các loại giá trị có thể thay đổi vì hành vi thường là surprising, khó dự đoán và/hoặc đôi khi hết sức không phù hợp.

Dưới đây là một số chi tiết hữu ích về nitty, các phần liên quan từ thông số giúp mô tả hành vi bạn đang gặp phải.

C# 4.0 phần 1.6.7.2

Một tập accessor tương ứng với một phương pháp với một tham số duy nhất có tên giá trị và không có kiểu trả về.

Trình truy cập nhận được tương ứng với phương pháp không tham số có giá trị trả về thuộc loại thuộc tính.

Bây giờ chuyển sang 7.5.5 Chức năng thành viên Invocation, phần có liên quan:

Nếu [các hàm thành viên] là một hàm thành viên dụ khai báo trong một loại giá trị :

Nếu [biểu thức thể hiện] không được phân loại là biến, thì một biến số cục bộ tạm thời của loại [biểu thức thể hiện] được tạo và vlue của [biểu thức thể hiện] là được gán cho biến. [biểu thức cá thể] sau đó được phân loại lại làm tham chiếu đến biến tạm thời đó. Biến tạm thời có thể truy cập được ở đây là trong [thành viên chức năng], nhưng không theo bất kỳ cách nào khác. Vì vậy, chỉ khi [biểu thức thể hiện] là một biến số đúng thì người gọi có thể quan sát các thay đổi mà [thành viên chức năng] thực hiện cho điều này.

0

Nếu một biến lớp học hoặc struct-type cho thấy một lĩnh vực giá trị gia loại, và loại giá trị cho thấy nội dung của nó như các lĩnh vực, hoạt động trên các lĩnh vực này có thể được thực hiện một cách hiệu quả như toán hạng vào loại biến xung quanh.NẾU kiểu giá trị được tiếp xúc như một tài sản, sau đó là tốt nhất người ta có thể làm thường là một cái gì đó như:

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

Không khủng khiếp tao nhã, nhưng tương đối rõ ràng và không quá khủng khiếp không hiệu quả. Một thay thế sẽ được để có bể phơi bày một phương pháp AdjustLocation, một cái gì đó như:

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

và có lẽ cũng

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

Những phương pháp này giả định rằng Location sở hữu sử dụng một lĩnh vực sao lưu tên _Location. Mã sau đó có thể làm điều gì đó như:

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

hoặc

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

Lưu ý rằng trong trường hợp thứ hai, không phải YSpeed, cũng không this, cũng không phải bất kỳ biến địa phương khác, không được sử dụng trong lambda; thay vào đó, YSpeed được chuyển thành thông số ref. Do đó, ngay cả khi mã trên chạy hàng triệu lần, hệ thống sẽ chỉ phải tạo một đại biểu mà sau đó có thể được sử dụng lại mỗi lần.

Nếu cấu trúc lớn, phương pháp trên có thể nhanh hơn phương pháp sử dụng biến tạm thời. Trong khi chi phí có thể lớn hơn chi phí sao chép một cấu trúc nhỏ, chi phí trên không phụ thuộc vào kích thước cấu trúc. Người ta có thể sử dụng hiệu quả các cấu trúc có kích thước vài kilobyte nếu sử dụng các cấu trúc như trên để tránh phải tạo các bản sao tạm thời.

Các vấn đề liên quan