2008-08-21 48 views
8

Tôi có một lớp "Status" trong C#, được sử dụng như thế này:thực thi yêu cầu chức năng gọi

Status MyFunction() 
{ 
    if(...) // something bad 
    return new Status(false, "Something went wrong") 
    else 
    return new Status(true, "OK"); 
} 

Bạn nhận được các ý tưởng. Tất cả cuộc gọi của MyFunction nên kiểm tra tình trạng trả về: Tuy nhiên

Status myStatus = MyFunction(); 
if (! myStatus.IsOK()) 
    // handle it, show a message,... 

gọi Lazy thể bỏ qua Trạng thái.

MyFunction(); // call function and ignore returned Status 

hoặc

{ 
    Status myStatus = MyFunction(); 
} // lose all references to myStatus, without calling IsOK() on it 

Có thể làm cho điều này là không thể? ví dụ. một ngoại lệ ném

Nói chung: bạn có thể viết một lớp C# mà bạn để gọi một chức năng nhất định không?

Trong phiên bản C++ của lớp Trạng thái, tôi có thể viết một bài kiểm tra trên một số bool bIsChecked riêng trong số destructor và đổ chuông khi ai đó không kiểm tra trường hợp này.

Tùy chọn tương đương trong C# là gì? Tôi đọc ở đâu đó rằng "Bạn không muốn một destructor trong lớp C#"

Phương thức Vứt bỏ của giao diện IDisposable một tùy chọn?

Trong trường hợp này, không có tài nguyên không được quản lý nào miễn phí. Ngoài ra, nó không được xác định khi GC sẽ vứt bỏ đối tượng. Khi nó cuối cùng được xử lý, bạn vẫn có thể biết vị trí và thời điểm bạn bỏ qua trường hợp Trạng thái cụ thể đó không? Từ khoá "đang sử dụng" không có tác dụng, nhưng một lần nữa, nó không phải là yêu cầu cho những người gọi lười.

+0

Trên ghi chú liên quan, tôi đã tìm thấy câu hỏi này: http://stackoverflow.com/questions/173670/why-is-there-no-raii-in-net – jan

Trả lời

2

Tôi khá chắc chắn bạn không thể nhận được hiệu ứng bạn muốn làm giá trị trả về từ phương thức. C# không thể làm một số điều C++ có thể. Tuy nhiên, một cách hơi xấu xí để có được một hiệu ứng tương tự như sau:

using System; 

public class Example 
{ 
    public class Toy 
    { 
     private bool inCupboard = false; 
     public void Play() { Console.WriteLine("Playing."); } 
     public void PutAway() { inCupboard = true; } 
     public bool IsInCupboard { get { return inCupboard; } } 
    } 

    public delegate void ToyUseCallback(Toy toy); 

    public class Parent 
    { 
     public static void RequestToy(ToyUseCallback callback) 
     { 
      Toy toy = new Toy(); 
      callback(toy); 
      if (!toy.IsInCupboard) 
      { 
       throw new Exception("You didn't put your toy in the cupboard!"); 
      } 
     } 
    } 

    public class Child 
    { 
     public static void Play() 
     { 
      Parent.RequestToy(delegate(Toy toy) 
      { 
       toy.Play(); 
       // Oops! Forgot to put the toy away! 
      }); 
     } 
    } 

    public static void Main() 
    { 
     Child.Play(); 
     Console.ReadLine(); 
    } 
} 

Trong ví dụ rất đơn giản, bạn sẽ có được một thể hiện của Toy bằng cách gọi Parent.RequestToy, và đi qua nó một đại biểu. Thay vì trả lại đồ chơi, phương pháp này ngay lập tức gọi cho đại biểu với đồ chơi, mà phải gọi cho PutAway trước khi nó trở về, hoặc phương pháp RequestToy sẽ ném một ngoại lệ. Tôi không đưa ra tuyên bố nào về sự khôn ngoan của việc sử dụng kỹ thuật này - thực ra trong tất cả các ví dụ "một cái gì đó đã sai", một ngoại lệ gần như chắc chắn là một đặt cược tốt hơn - nhưng tôi nghĩ nó đến gần như bạn có thể nhận được yêu cầu ban đầu của bạn.

+0

Tôi hơi bối rối vì sao tôi bị đánh dấu xuống. .. Tôi đã làm gì sai sao? – Weeble

+0

Bạn đã đăng một giải pháp tốt cho một vấn đề sai. Người hỏi không nên làm những gì anh ta thực sự cố gắng làm ở đây. – skolima

+0

Đó sẽ là một lý do chính đáng để đánh dấu câu hỏi * *, không phải câu trả lời và để lại nhận xét về lý do tại sao đó là một ý tưởng tồi. –

10

Tôi biết điều này không trực tiếp trả lời câu hỏi của bạn, nhưng nếu "có sự cố" trong chức năng của bạn (trường hợp bất ngờ), tôi nghĩ bạn nên ném ngoại lệ thay vì sử dụng mã trả về trạng thái.

Sau đó, hãy để người gọi bắt và xử lý ngoại lệ này nếu có thể hoặc cho phép nó phát sinh nếu người gọi không thể xử lý tình huống.

Trường hợp ngoại lệ được ném có thể thuộc loại tùy chỉnh nếu điều này là phù hợp.

Đối với được mong đợi kết quả thay thế, tôi đồng ý với đề xuất của @Jon Limjap. Tôi thích một kiểu bool trở lại và tiền tố tên phương pháp với "Thử", a la:

bool TryMyFunction(out Status status) 
{ 
} 
0

Bạn có thể ném một ngoại lệ bởi:

throw MyException; 


[global::System.Serializable] 
     public class MyException : Exception 
     { 
     // 
     // For guidelines regarding the creation of new exception types, see 
     // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp 
     // and 
     // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp 
     // 

     public MyException() { } 
     public MyException (string message) : base(message) { } 
     public MyException (string message, Exception inner) : base(message, inner) { } 
     protected MyException (
      System.Runtime.Serialization.SerializationInfo info, 
      System.Runtime.Serialization.StreamingContext context) 
      : base(info, context) { } 
    } 

Trường hợp ngoại lệ trên là hoàn toàn tùy biến để Các yêu cầu của bạn.

Một điều tôi muốn nói là, tôi sẽ để nó cho người gọi để kiểm tra mã trả lại, đó là trách nhiệm của họ mà bạn chỉ cung cấp phương tiện và giao diện. Ngoài ra, nó rất hiệu quả hơn để sử dụng mã trả về và kiểm tra trạng thái với câu lệnh if thay vì trhowing ngoại lệ. Nếu nó thực sự là một trường hợp Đặc biệt, thì có nghĩa là vứt bỏ đi ... nhưng nếu bạn không mở được thiết bị, thì có thể thận trọng hơn khi gắn mã trả lại.

0

Điều đó chắc chắn sẽ tốt hơn nếu bạn kiểm tra trình biên dịch thay vì thông qua biểu thức. :/ Không thấy bất kỳ cách nào để làm điều đó mặc dù ...

1

Sử dụng Trạng thái làm giá trị trả lại nhớ tôi về "ngày cũ" của lập trình C, khi bạn trả lại số nguyên dưới 0 nếu có gì đó không công việc.

Sẽ không tốt hơn nếu bạn ném một ngoại lệ khi (khi bạn đặt) đã xảy ra sự cố? Nếu một số "mã lười" không bắt ngoại lệ của bạn, bạn sẽ biết chắc chắn.

3

Ngay cả System.Net.WebRequest cũng ném một ngoại lệ khi mã trạng thái HTTP trả lại là mã lỗi. Cách điển hình để xử lý nó là quấn thử/nắm bắt xung quanh nó. Bạn vẫn có thể bỏ qua mã trạng thái trong khối catch.

Tuy nhiên, bạn có thể có thông số Hành động < Trạng thái> để người gọi bị buộc phải chuyển chức năng gọi lại chấp nhận trạng thái và sau đó kiểm tra xem họ có gọi nó không.

void MyFunction(Action<Status> callback) 
{ bool errorHappened = false; 

    if (somethingBadHappend) errorHappened = true; 

    Status status = (errorHappend) 
        ? new Status(false, "Something went wrong") 
        : new Status(true, "OK"); 
    callback(status) 

    if (!status.isOkWasCalled) 
    throw new Exception("Please call IsOK() on Status"). 
} 

MyFunction(status => if (!status.IsOK()) onerror()); 

Nếu bạn đang lo lắng về họ gọi IsOK() mà không làm gì cả, sử dụng biểu < Func < Status, bool >> thay vào đó và sau đó bạn có thể phân tích lambda để xem những gì họ làm với tình trạng:

void MyFunction(Expression<Func<Status,bool>> callback) 
{ if (!visitCallbackExpressionTreeAndCheckForIsOKHandlingPattern(callback)) 
    throw new Exception 
       ("Please handle any error statuses in your callback"); 


    bool errorHappened = false; 

    if (somethingBadHappend) errorHappened = true; 

    Status status = (errorHappend) 
        ? new Status(false, "Something went wrong") 
        : new Status(true, "OK"); 

    callback.Compile()(status); 
} 

MyFunction(status => status.IsOK() ? true : onerror()); 

Hoặc bỏ lớp tình trạng hoàn toàn và làm cho họ vượt qua trong một đại biểu cho sự thành công và một số khác cho một lỗi:

void MyFunction(Action success, Action error) 
{ if (somethingBadHappened) error(); else success(); 
} 

MyFunction(()=>;,()=>handleError()); 
7

Nếu y ou thực sự muốn yêu cầu người dùng để lấy kết quả của MyFunction, bạn có thể muốn làm mất hiệu lực nó để thay thế và sử dụng một ra hoặc biến ref, ví dụ:

void MyFunction(out Status status) 
{ 
} 

Nó có thể trông xấu xí nhưng ít nhất nó đảm bảo rằng một biến được chuyển vào hàm sẽ nhận kết quả bạn cần để nhận.

@Ian,

Vấn đề với trường hợp ngoại lệ là nếu nó là cái gì đó xảy ra một chút quá thường xuyên, bạn có thể chi tiêu quá nhiều tài nguyên hệ thống cho các ngoại lệ. Một ngoại lệ thực sự nên được sử dụng cho lỗi đặc biệt, không phải là các thư hoàn toàn mong đợi.

0

GCC có thuộc tính warn_unused_result lý tưởng cho loại điều này. Có lẽ các trình biên dịch của Microsoft có một cái gì đó tương tự.

0

@Paul bạn có thể làm điều đó tại thời gian biên dịch với Extensible C#.

1

Thay vì buộc ai đó kiểm tra trạng thái, tôi nghĩ bạn nên cho rằng lập trình viên nhận thức được rủi ro này khi không làm như vậy và có lý do để thực hiện hành động đó. Bạn không biết làm thế nào chức năng sẽ được sử dụng trong tương lai và đặt một giới hạn như thế chỉ hạn chế các khả năng.

0

Một mẫu có thể đôi khi hữu ích nếu đối tượng yêu cầu mã sẽ chỉ được sử dụng bởi một chuỗi (*) là để đối tượng giữ trạng thái lỗi và nói rằng nếu thao tác không thành công, đối tượng sẽ không sử dụng được cho đến khi trạng thái lỗi được đặt lại (các yêu cầu trong tương lai sẽ thất bại ngay lập tức, tốt nhất là bằng cách ném một ngoại lệ ngay lập tức bao gồm thông tin về cả lỗi trước và yêu cầu mới). Trong trường hợp mã gọi xảy ra để dự đoán một vấn đề, điều này có thể cho phép mã gọi điện thoại xử lý vấn đề rõ ràng hơn nếu một ngoại lệ bị ném; các vấn đề không được bỏ qua bởi mã gọi điện nói chung sẽ kết thúc kích hoạt một ngoại lệ khá sớm sau khi chúng xảy ra.

(*) Nếu tài nguyên sẽ được truy cập bởi nhiều luồng, hãy tạo đối tượng bao bọc cho mỗi chuỗi và yêu cầu của từng chuỗi sẽ đi qua trình bao bọc của riêng nó.

Mẫu này có thể sử dụng được ngay cả trong các ngữ cảnh có ngoại lệ và đôi khi rất thực tế trong những trường hợp như vậy. Nói chung, tuy nhiên, một số biến thể của mẫu thử/làm thường tốt hơn. Có phương pháp ném ngoại lệ về thất bại trừ khi người gọi chỉ rõ ràng (bằng cách sử dụng phương pháp TryXX) mà lỗi được mong đợi. Nếu người gọi nói thất bại được mong đợi nhưng không xử lý chúng, đó là vấn đề của họ. Người ta có thể kết hợp thử/làm với một lớp bảo vệ thứ hai bằng cách sử dụng sơ đồ trên, nhưng tôi không chắc liệu nó có đáng giá hay không.

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