Thuộc tính hoặc trình lập chỉ mục không được chuyển dưới dạng tham số out hoặc ref


86

Tôi gặp lỗi ở trên và không thể giải quyết nó. Tôi đã tìm kiếm một chút nhưng không thể thoát khỏi nó.

Tình huống:

Tôi có lớp BudgetAllocate có thuộc tính là ngân sách thuộc loại kép.

Trong dataAccessLayer của tôi,

Trong một trong các lớp học của mình, tôi đang cố gắng thực hiện điều này:

double.TryParse(objReader[i].ToString(), out bd.Budget);

Đó là lỗi này:

Thuộc tính hoặc trình chỉ mục không được chuyển dưới dạng tham số ra hoặc tham số tại thời điểm biên dịch.

Tôi thậm chí đã thử điều này:

double.TryParse(objReader[i].ToString().Equals(DBNull.Value) ? "" : objReader[i].ToString(), out bd.Budget);

Mọi thứ khác đều hoạt động tốt và có các tham chiếu giữa các lớp.


Trong bd.Budget bd là một đối tượng của lớp BudgetAllocate. Xin lỗi tôi đã quên.
Pratik




Chỉ phát hiện ra điều này làm việc với một kiểu người dùng có các trường được xác định mà tôi dự kiến ​​sẽ DataGridđiền từ đó đến tìm hiểu nó chỉ tự động với các thuộc tính. Việc chuyển sang thuộc tính đã phá vỡ một số tham số ref mà tôi đang sử dụng trên các trường của mình. Phải xác định các biến cục bộ để thực hiện phân tích cú pháp.
jxramos 19/1217

Câu trả lời:


37

bạn không thể sử dụng

double.TryParse(objReader[i].ToString(), out bd.Budget); 

thay thế bd.Budget bằng một số biến.

double k;
double.TryParse(objReader[i].ToString(), out k); 

11
tại sao phải sử dụng một biến phụ ??
Pratik

6
@pratik Bạn không thể truyền vào một thuộc tính dưới dạng tham số out vì không có gì đảm bảo rằng thuộc tính thực sự có bộ định giá, vì vậy bạn cần biến bổ sung.
matt 23/12/10

23
@ mjd79: Suy luận của bạn không chính xác. Trình biên dịch biết liệu có một bộ thiết lập hay không. Giả sử có một người thiết lập; nó có nên được phép không?
Eric Lippert

21
@dhinesh, tôi nghĩ OP đang tìm kiếm câu trả lời về lý do tại sao anh ấy không thể làm điều đó, không chỉ những gì anh ấy phải làm. Đọc câu trả lời từ Hans Passant và nhận xét từ Eric Lippert.
slugster 23/12/10

2
@dhinesh Lý do "thực sự" khiến anh ấy không thể làm điều đó là vì anh ấy đang sử dụng C # chứ không phải VB mà KHÔNG cho phép điều này. Tôi đến từ thế giới VB (rõ ràng là vậy?) Và tôi thường ngạc nhiên về những hạn chế bổ sung mà C # áp đặt.
SteveCinq

149

Những người khác đã cung cấp cho bạn giải pháp, nhưng tại sao điều này lại cần thiết: một thuộc tính chỉ là đường cú pháp cho một phương thức .

Ví dụ: khi bạn khai báo một thuộc tính được gọi Namevới getter và setter, trình biên dịch thực sự tạo ra các phương thức được gọi là get_Name()set_Name(value). Sau đó, khi bạn đọc và ghi vào thuộc tính này, trình biên dịch sẽ chuyển các thao tác này thành các lệnh gọi đến các phương thức được tạo đó.

Khi bạn xem xét điều này, rõ ràng là tại sao bạn không thể chuyển một thuộc tính làm tham số đầu ra - bạn thực sự sẽ truyền một tham chiếu đến một phương thức , thay vì tham chiếu đến một đối tượng, một biến , đó là những gì một tham số đầu ra mong đợi.

Một trường hợp tương tự cũng tồn tại đối với người lập chỉ mục.


19
Suy luận của bạn là đúng cho đến tận bit cuối cùng. Tham số out đang mong đợi một tham chiếu đến một biến , không phải một đối tượng .
Eric Lippert

@EricLippert nhưng không phải một biến cũng là một đối tượng hay tôi đang thiếu cái gì?
meJustAndrew

6
@meJustAndrew: Một biến hoàn toàn không phải là một đối tượng . Một biến là một vị trí lưu trữ . Vị trí lưu trữ chứa (1) tham chiếu đến đối tượng kiểu tham chiếu (hoặc null) hoặc (2) giá trị của đối tượng kiểu giá trị. Đừng nhầm lẫn vật chứa với thứ chứa trong đó.
Eric Lippert

6
@meJustAndrew: Hãy xem xét một đối tượng, chẳng hạn như một ngôi nhà. Hãy xem xét một mảnh giấy có ghi địa chỉ của ngôi nhà trên đó. Hãy xem xét một ngăn kéo có chứa mảnh giấy đó. Cả ngăn kéo và tờ giấy đều không phải là ngôi nhà .
Eric Lippert

69

Đây là một trường hợp trừu tượng bị rò rỉ. Thuộc tính thực sự là một phương thức, các trình truy cập getset cho một indexer được biên dịch thành các phương thức get_Index () và set_Index. Trình biên dịch thực hiện một công việc tuyệt vời là che giấu sự thật đó, nó sẽ tự động dịch một nhiệm vụ cho một thuộc tính sang phương thức set_Xxx () tương ứng chẳng hạn.

Nhưng điều này sẽ xuất hiện khi bạn truyền một tham số phương thức bằng tham chiếu. Điều đó yêu cầu trình biên dịch JIT chuyển một con trỏ đến vị trí bộ nhớ của đối số được truyền vào. Vấn đề là, không có một, việc gán giá trị của một thuộc tính yêu cầu gọi phương thức setter. Phương thức được gọi không thể cho biết sự khác biệt giữa một biến được truyền và một thuộc tính được truyền vào và do đó không thể biết liệu một cuộc gọi phương thức có được yêu cầu hay không.

Đáng chú ý là điều này thực sự hoạt động trong VB.NET. Ví dụ:

Class Example
    Public Property Prop As Integer

    Public Sub Test(ByRef arg As Integer)
        arg = 42
    End Sub

    Public Sub Run()
        Test(Prop)   '' No problem
    End Sub
End Class

Trình biên dịch VB.NET giải quyết vấn đề này bằng cách tự động tạo mã này cho phương thức Run, được thể hiện bằng C #:

int temp = Prop;
Test(ref temp);
Prop = temp;

Đó là giải pháp thay thế bạn cũng có thể sử dụng. Không hoàn toàn chắc chắn tại sao nhóm C # không sử dụng cách tiếp cận tương tự. Có thể vì họ không muốn ẩn các cuộc gọi getter và setter có thể tốn kém. Hoặc hành vi hoàn toàn không thể phân biệt được mà bạn sẽ nhận được khi setter có các tác dụng phụ làm thay đổi giá trị thuộc tính, chúng sẽ biến mất sau khi gán. Sự khác biệt cổ điển giữa C # và VB.NET, C # là "không có gì ngạc nhiên", VB.NET là "làm cho nó hoạt động nếu bạn có thể".


15
Bạn đúng khi không muốn tạo ra các cuộc gọi đắt tiền. Một lý do thứ yếu là ngữ nghĩa copy-in-copy-out có ngữ nghĩa khác với ngữ nghĩa tham chiếu và sẽ không nhất quán nếu có hai ngữ nghĩa khác nhau một cách tinh tế để chuyển ref. (Điều đó nói rằng, có một số tình huống hiếm hoi trong đó cây biểu thức đã biên dịch thực hiện copy-in-copy-out, thật không may.)
Eric Lippert

2
Điều thực sự cần thiết là có nhiều chế độ truyền tham số hơn, để trình biên dịch có thể thay thế "sao chép vào / sao chép ra" nếu thích hợp, nhưng nhiễu trong trường hợp không.
supercat

9

Đặt tham số out vào một biến cục bộ và sau đó đặt biến thành bd.Budget:

double tempVar = 0.0;

if (double.TryParse(objReader[i].ToString(), out tempVar))
{
    bd.Budget = tempVar;
}

Cập nhật : Trực tiếp từ MSDN:

Thuộc tính không phải là biến và do đó không thể được chuyển dưới dạng tham số.


1
@ E.vanderSpoel Rất may là tôi đã gỡ bỏ nội dung, gỡ bỏ liên kết.
Adam Houldsworth

8

Có thể quan tâm - bạn có thể viết của riêng bạn:

    //double.TryParse(, out bd.Budget);
    bool result = TryParse(s, value => bd.Budget = value);
}

public bool TryParse(string s, Action<double> setValue)
{
    double value;
    var result =  double.TryParse(s, out value);
    if (result) setValue(value);
    return result;
}

5

Đây là một bài đăng rất cũ, nhưng tôi đang cố gắng chấp nhận, bởi vì có một cách thuận tiện hơn để làm điều này mà tôi không biết.

Nó được gọi là khai báo nội tuyến và có thể luôn có sẵn (như khi sử dụng câu lệnh) hoặc nó có thể đã được thêm với C # 6.0 hoặc C # 7.0 cho những trường hợp như vậy, không chắc chắn, nhưng dù sao cũng hoạt động như một sự quyến rũ:

Inetad của điều này

double temp;
double.TryParse(objReader[i].ToString(), out temp);
bd.Budget = temp;

dùng cái này:

double.TryParse(objReader[i].ToString(), out double temp);
bd.Budget = temp;

2
Tôi sẽ sử dụng trả về để kiểm tra xem quá trình phân tích cú pháp có thành công hay không trong trường hợp nhập không hợp lệ.
MarcelDevG

1

Vậy Ngân sách là một tài sản, đúng không?

Thay vào đó, trước tiên hãy đặt nó thành một biến cục bộ, rồi đặt giá trị thuộc tính cho nó.

double t = 0;
double.TryParse(objReader[i].ToString(), out t); 
bd.Budget = t;

Cảm ơn. Nhưng tôi có thể biết tại sao lại như vậy không?
Pratik

0

Thông thường, khi tôi đang cố gắng thực hiện việc này, đó là vì tôi muốn đặt thuộc tính của mình hoặc để nó ở giá trị mặc định. Với sự trợ giúp của câu trả lời nàydynamiccác kiểu, chúng ta có thể dễ dàng tạo một phương thức mở rộng chuỗi để giữ cho nó một cách đơn giản và dễ dàng.

public static dynamic ParseAny(this string text, Type type)
{
     var converter = TypeDescriptor.GetConverter(type);
     if (converter != null && converter.IsValid(text))
          return converter.ConvertFromString(text);
     else
          return Activator.CreateInstance(type);
}

Sử dụng như vậy;

bd.Budget = objReader[i].ToString().ParseAny(typeof(double));

// Examples
int intTest = "1234".ParseAny(typeof(int)); // Result: 1234
double doubleTest = "12.34".ParseAny(typeof(double)); // Result: 12.34
decimal pass = "12.34".ParseAny(typeof(decimal)); // Result: 12.34
decimal fail = "abc".ParseAny(typeof(decimal)); // Result: 0
string nullStr = null;
decimal failedNull = nullStr.ParseAny(typeof(decimal)); // Result: 0

Không bắt buộc

Một lưu ý nhỏ, nếu đó là một, SQLDataReaderbạn cũng có thể sử dụng GetSafeString(các) tiện ích mở rộng để tránh ngoại lệ trống từ người đọc.

public static string GetSafeString(this SqlDataReader reader, int colIndex)
{
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

public static string GetSafeString(this SqlDataReader reader, string colName)
{
     int colIndex = reader.GetOrdinal(colName);
     if (!reader.IsDBNull(colIndex))
          return reader.GetString(colIndex);
     return string.Empty;
}

Sử dụng như vậy;

bd.Budget = objReader.GetSafeString(i).ParseAny(typeof(double));
bd.Budget = objReader.GetSafeString("ColumnName").ParseAny(typeof(double));
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.