2009-05-27 52 views
8

Tôi đang làm việc trong C# và tôi bắt đầu chơi với các thuộc tính. Một điều tôi không biết là cách tốt nhất/nơi để đặt logic cho các bộ truy cập thiết lập các thuộc tính lớp và cách xử lý các lỗi.C# Properties - Đặt câu hỏi

Ví dụ, nói rằng tôi có điều này (cơ bản) lớp:

class Person 
{ 
    private int _Age = 18; 

    public Person() 
    { 

    } 

    public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      _Age = value; 
     } 
    } 
} 

Bây giờ nói rằng tôi có một yêu cầu về quyền sở hữu Age, 0 < Tuổi < 100. Tôi nên đặt logic cho việc này?

Tôi có nên đặt nó trong tài sản không?

public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (value < 0 || value > 99) 
       // handle error 
      else 
       _Age = Convert.ToInt32(value); 
     } 
    } 

hoặc thông qua lớp học đang tạo đối tượng Person?

static void Main(string[] args) 
{ 
    Person him = new Person(); 
    int NewAge = -10; 

    if (NewAge < 0 || NewAge > 100) 
     // handle error 
    else 
     him.Age = NewAge; 
} 

Bây giờ nếu có vấn đề gì với NewAge (nó không đáp ứng được ràng buộc của tôi)? Tôi có nên tạo một ngoại lệ tùy chỉnh và ném nó không? Tôi có nên in một thông báo cho biết nguồn cung cấp có hợp lệ không?

Tôi đã thực hiện một số Googling và tôi không thể tìm thấy bất kỳ điều gì có câu trả lời đầy đủ cho câu hỏi của tôi. Tôi cần một cuốn sách: -/

+3

Cũng lưu ý rằng setter của bạn không cần 'Convert.ToInt32 (giá trị) '... vì thuộc tính là 'int',' giá trị' cũng sẽ là 'int'. – jerryjvl

+0

Đây có phải là chương trình phòng thủ không? Hay tôi đang dùng nó cho đến nay? –

+0

Bạn đang dùng nó quá xa. Không có khả năng rằng điều bạn nhận được không phải là một int. –

Trả lời

23

Sử dụng trình thiết lập thuộc tính, có lý do đó (thêm chức năng vào trường).

Nếu vượt quá giá trị phạm vi, bạn có thể ném ArgumentOutOfRangeException hoặc chỉ đặt giá trị tối thiểu (hoặc tối đa), nhưng điều này tùy thuộc vào yêu cầu quy trình của bạn.

+3

+1 cho đề xuất để ném ArgumentOutOfRangeException và không chỉ Exception –

+0

Tôi sẽ thêm một chút sắc thái này trong nói rằng setter tài sản nói chung là nơi thích hợp, ... nhưng CHỈ cho những điều bạn chắc chắn nhất định phải luôn luôn đúng . Kiểm tra có thể có ngoại lệ (tha thứ chơi chữ) có lẽ nên đi vào một phương pháp xác nhận thay vào đó, để người sử dụng của lớp có thể xác định xem lỗi ra là thích hợp. Lý do chính để nói điều này là nó không phải là không hợp lý trong trường hợp chung để cho phép tuổi trên 99 hoặc 100 (mà câu hỏi ban đầu không cho phép vì lý do nào) – jerryjvl

+0

Tôi sẽ: 1) ghi rõ hành vi và yêu cầu của tài sản này (để người dùng biết cách sử dụng nó) và 2) đặt một thông điệp rõ ràng về trường hợp ngoại lệ được ném 3) điều này sẽ tạo ra một mã sạch (không rải rác bằng các phương pháp xác nhận sẽ gây nhầm lẫn cho các nhà phát triển trong tương lai) –

3

Đặt vào tài sản. Đó là một trong những mục đích chính của tài sản!

Hãy xem xét rằng bạn sẽ đặt trách nhiệm ở mức thấp nhất có thể. Ví dụ bạn đưa ra không yêu cầu bất kỳ thông tin nào khác ngoài thông số giá trị để đưa ra quyết định. Nó thậm chí không phụ thuộc vào các thành viên khác của cùng một lớp. Không có lý do gì để phần còn lại của lớp biết được hiệu lực hoạt động của thuộc tính Age như thế nào, và chắc chắn không có lý do gì cho bất kỳ đoạn mã nào khác biết nó.

6

Bạn sẽ muốn đặt logic trong setter và ném một ngoại lệ nếu nó không đáp ứng yêu cầu. Tuy nhiên, bạn cũng sẽ muốn tạo một phương thức tĩnh IsValidAge hoặc một cái gì đó để các lớp tạo ra một Person có thể kiểm tra giá trị độ tuổi thay vì chỉ nhìn thấy nếu nó ném một ngoại lệ. Ngoài ra, bạn có thể có các thuộc tính MinAge và MaxAge để mã gọi có thể kiểm tra xem độ tuổi mà chúng sắp đặt có nằm giữa nó hay không.

Không tạo loại ngoại lệ tùy chỉnh, sử dụng ArgumentOutOfRangeException hoặc một cái gì đó tương tự.

3

Bạn có thể muốn xem giao diện IDataErrorInfo.

Bằng cách triển khai giao diện này trong lớp học của mình, bạn sẽ mở lớp này sang các cơ chế khác có thể hưởng lợi từ thông tin lỗi bổ sung.

0

Sử dụng trình thiết lập và sử dụng ArgumentOutOfRangeException, như những người khác đã nói.Đôi khi bạn sẽ gọi một phương thức khác, thường là một cái gì đó như OnAgeChanged (int age), nơi bạn có thể thực hiện xác thực (có thể là một cuộc gọi đến phương thức khác để bạn có thể sử dụng nó ở bất kỳ đâu) và gọi trình xử lý sự kiện nếu kết nối, có thể có logic khác để áp dụng tùy thuộc vào cách bạn đang sử dụng đối tượng của bạn. Điều đó có thể không cần thiết trong kịch bản của bạn, nhưng nó khá phổ biến để làm điều đó cho các thuộc tính. Đặc biệt nếu bạn định cập nhật biểu mẫu - biểu mẫu sẽ móc nối vào sự kiện AgeChanged để tự cập nhật.

1

Tôi sẽ chơi chủ trương của quỷ và đưa ra câu trả lời tại sao bạn KHÔNG muốn đặt nó trong setter. Có rất nhiều trường hợp khi bạn muốn có thể đặt giá trị của thuộc tính Độ tuổi của bạn và sau đó hỏi xem đối tượng đó có hợp lệ theo nghĩa toàn diện hay không.

Ví dụ, giữ tài sản của bạn đơn giản:

public int Age { get; set; } 

Sau đó, khi một giá trị không hợp lệ được thông qua tại bạn có thể có một số chức năng IsValid mà chỉ nếu đối tượng trong câu hỏi là ok. Điều này có thể cực kỳ hữu ích vì bạn có thể thực hiện xác nhận phức tạp hơn ngoài hạn chế Độ tuổi đơn giản.

bool IsValid() 
{ 
    if (Age < 0 || Age > 99) 
    return false; 
} 

Đối với một cái gì đó đơn giản như thế này không có rất nhiều lợi ích, nhưng xem xét cũng là bạn có thể sử dụng trong lớp kiên trì của bạn, do đó bạn có thể đảm bảo rằng bất kỳ đối tượng đó là KHÔNG hợp lệ sẽ không bao giờ được tiếp tục tồn. Trong những trường hợp đó bạn không nhất thiết muốn ném một ngoại lệ.

Cũng xem xét việc này:

DateTime StartDate { get; set; } 
DateTime EndDate { get; set;} 

bool IsValid() 
{ 
    return StartDate > EndDate  
} 

Đây chỉ là giả, nhưng bạn sẽ có được quan điểm của tôi. Đây là điều mà bạn không thể làm bên trong một setter, hoặc ít nhất, không theo cách có thể duy trì được.

+0

Đây là một điểm thú vị xem. Mặc dù tôi đồng ý rằng có một hàm IsValid riêng biệt có thể hữu ích, tôi không hiểu tại sao tôi được phép tạo điều kiện mà bản thân mô hình có thể không hợp lệ. Bắt lỗi sớm nhất có thể làm giảm đau đầu trong tương lai không? Điều gì xảy ra khi mã liên tục quên gọi IsValid? Đó là một điều quan trọng cần nhớ. –

11

tôi muốn thực hiện nó như thế này:

public int Age 
{ 
    get 
    { 
     return _Age; 
    } 
    set 
    { 
     if (IsValidAge(value)) 
      _Age = value; 
     else 
      throw new ArgumentOutOfRangeException("value", string.Format("value should be between {0} and {1} inclusive.", MinAge, MaxAge)); 
    } 
} 

private bool IsValidAge(int age) 
{ 
    return (age >= MinAge && age <= MaxAge); 
} 

Một số điều cần lưu ý:

  • Không thay đổi giá trị của họ thay vì ném một ngoại lệ, đây là hành vi bất ngờ.
  • Khuôn khổ .NET sẽ ném đối số * ngoại lệ trong các trình cài đặt, vì vậy, tôi khuyên bạn nên thực hiện theo cách này. Trong trường hợp này ArgumentOutOfRangeException là hoàn hảo, IMO.
  • Khi đề cập đến đối số trong thông báo ngoại lệ và tài liệu xml, tiêu chuẩn là gọi đối số "giá trị", không phải là tên của thuộc tính của bạn.
  • Tôi muốn giới thiệu MinAge và MaxAge là các khối riêng tư trong lớp học của bạn, không rơi vào bẫy thông báo lỗi mã hóa có ranh giới phạm vi trong chúng, không có gì tệ hơn là được nói "5 không hợp lệ, vui lòng nhập một số 1 và 10 "khi ai đó thay đổi thông số kỹ thuật sau này nhưng quên cập nhật chuỗi.
+0

Một số điểm tốt trong câu trả lời này. –

+0

Tôi đồng ý với việc triển khai như thế này. Bạn chắc chắn nên thực hiện xác thực trên thiết lập và nó phải là cuộc gọi bên ngoài. Tôi cũng khuyên bạn nên chuyển nó đến một nơi nào đó dễ tiếp cận hơn nếu đó là thứ mà bạn có thể muốn sử dụng ở nơi khác. – JonBWalsh

0

Nếu bạn đang thực hiện thiết kế thừa (nghĩa là trạng thái của đối tượng là hợp lệ tại tất cả các điểm trong suốt thời gian của đối tượng), thì bạn có thể đặt và kiểm tra điều này trong hàm tạo. Trong thiết kế thành phần, bạn muốn kiểm tra điều này trong phương thức sử dụng thuộc tính, ví dụ:

class Person 
{ 
    public int calculateTimeToExpiration() 
    { 
    if (Age < 0 || Age > 100) 
    //throw 
    } 
} 

Chính khoảng trống là mã máy khách và không phải là nơi tốt cho logic nghiệp vụ.

0

Cảm ơn câu trả lời! Trong trường hợp bạn tò mò ở đây là những gì tôi thay đổi ví dụ nhỏ của tôi (mặc dù nó là chi tiết hơn bây giờ) để (dựa trên các ý kiến).

class Person 
{ 
    private string _FirstName = "Joe"; 
    private string _LastName = "Smith"; 
    private int _Age = 18; 

    private const int MinAge = 1; 
    private const int MaxAge = 99; 

    public Person() 
    { 

    } 

    public Person(string FirstName, string LastName, int Age) 
    { 
     this.FirstName = FirstName; 
     this.LastName = LastName; 
     this.Age = Age; 
    } 

    public string FirstName 
    { 
     get 
     { 
      return _FirstName; 
     } 
     set 
     { 
      _FirstName = value; 
     } 
    } 

    public string LastName 
    { 
     get 
     { 
      return _LastName; 
     } 
     set 
     { 
      _LastName = value; 
     } 
    } 

    public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (IsValidAge(value)) 
       throw new ArgumentOutOfRangeException("value","Please enter a positive age less than 100."); 
      else 
       _Age = value; 
     } 
    } 

    private bool IsValidAge(int age) 
    { 
     return (age < MinAge || age > MaxAge); 
    } 

    public override string ToString() 
    { 
     if (Age == 1) 
      return "My name is " + FirstName + " " + LastName + " and I am " + Age + " year old."; 
     else 
      return "My name is " + FirstName + " " + LastName + " and I am " + Age + " years old."; 
    } 
} 

static void Main(string[] args) 
    { 
     Person him, her; 

     try 
     { 
      him = new Person("Joe Bob", "McGruff", 1); 
      Console.WriteLine(him); 
     } 
     catch (ArgumentOutOfRangeException range) 
     { 
      Console.WriteLine(range.Message); 
     } 

     try 
     { 
      her = new Person(); 
      her.Age = -5; 
      Console.WriteLine(her); 
     } 
     catch (ArgumentOutOfRangeException range) 
     { 
      Console.Write(range.Message); 
     } 

     Console.ReadKey(); 
    } 
+0

Tôi sẽ thay đổi logic trong phương pháp IsValidAge của bạn. Bạn đang gọi phương thức "IsValid", bạn đang trả về true vì không hợp lệ. Ngoài ra, không cần phải Convert.ToInt trong setter của bạn. Giá trị sẽ tự động là một int. Hãy nhớ rằng, các thuộc tính chỉ là cú pháp cú pháp cho các phương thức. Dưới mui xe nó được điều chỉnh thành một khoảng trống riêng tư set_Age (int x) {..} trong đó x -> giá trị. Do đó, KHÔNG CÓ CÁCH cho giá trị KHÔNG là int. – BFree

0

câu trả lời của bạn cập nhật là tốt nhưng bạn nên dọn dẹp một phần nhỏ trong setter của bạn/xác nhận

public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (!IsValidAge(value)) 
       throw new ArgumentOutOfRangeException("Age","Please enter a positive age less than 100."); 

      _Age = value; 
     } 
    } 

    private bool IsValidAge(int age) 
    { 
     return (age > MinAge && age < MaxAge); 
    }