Có lạm dụng việc sử dụng IDisposable và “sử dụng” như một phương tiện để đạt được “hành vi có phạm vi” vì sự an toàn ngoại lệ không?


112

Một cái gì đó tôi thường sử dụng trong C ++ là cho phép một lớp Axử lý một điều kiện vào và ra trạng thái cho một lớp khác B, thông qua hàm Atạo và hàm hủy, để đảm bảo rằng nếu thứ gì đó trong phạm vi đó ném ra một ngoại lệ, thì B sẽ có trạng thái đã biết khi phạm vi đã được thoát. Đây không phải là RAII thuần túy theo như từ viết tắt, nhưng nó là một mô hình đã được thiết lập.

Trong C #, tôi thường muốn làm

class FrobbleManager
{
    ...

    private void FiddleTheFrobble()
    {
        this.Frobble.Unlock();
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
        this.Frobble.Lock();
    }
}

Cần phải làm như thế này

private void FiddleTheFrobble()
{
    this.Frobble.Unlock();

    try
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
    finally
    {
        this.Frobble.Lock();
    }
}

nếu tôi muốn đảm bảo Frobbletrạng thái khi FiddleTheFrobbletrở lại. Mã sẽ đẹp hơn với

private void FiddleTheFrobble()
{
    using (var janitor = new FrobbleJanitor(this.Frobble))
    {            
        Foo();                  // Can throw
        this.Frobble.Fiddle();  // Can throw
        Bar();                  // Can throw
    }
}

nơi FrobbleJanitorvẻ khoảng thích

class FrobbleJanitor : IDisposable
{
    private Frobble frobble;

    public FrobbleJanitor(Frobble frobble)
    {
        this.frobble = frobble;
        this.frobble.Unlock();
    }

    public void Dispose()
    {
        this.frobble.Lock();
    }
}

Và đó là cách tôi muốn làm. Bây giờ thực tế bắt kịp, vì những gì tôi muốn để sử dụng đòi hỏi rằng FrobbleJanitorđược sử dụng với using . Tôi có thể coi đây là một vấn đề xem xét mã, nhưng có điều gì đó đang làm phiền tôi.

Câu hỏi: Những điều trên có bị coi là lạm dụng usingIDisposablekhông?


23
+1 cho riêng lớp và tên phương thức :-). Công bằng mà nói, câu hỏi thực tế.
Joey

2
@Johannes: Vâng, tôi có thể hiểu tại sao - hầu hết mọi người chỉ loạng choạng tay chân, nhưng tôi phải phản đối điều đó. ;-) Và nhân tiện, +1 cho điểm giống tên của bạn.
Johann Gerell

Có lẽ bạn có thể sử dụng phạm vi lock (this) {..} để đạt được kết quả tương tự trong ví dụ này?
oɔɯǝɹ

1
@ oɔɯǝɹ: Bạn sử dụng lock () để ngăn truy cập đồng thời vào nội dung nào đó từ các luồng khác nhau. Không liên quan gì đến kịch bản tôi mô tả.
Johann Gerell

1
IScopeable không có finalizer trong phiên bản tiếp theo của C # :)
Jon Raynor

Câu trả lời:


33

Tôi không nhất thiết phải nghĩ như vậy. IDisposable về mặt kỹ thuật có nghĩa là để được sử dụng cho những điều mà có nguồn tài nguyên không được quản lý, nhưng sau đó chỉ sử dụng chỉ là một cách gọn gàng để thực hiện một mô hình phổ biến của try .. finally { dispose }.

Một người theo chủ nghĩa thuần túy sẽ tranh luận 'có - đó là lạm dụng', và theo nghĩa thuần túy thì nó là như vậy; nhưng hầu hết chúng ta không viết mã theo quan điểm thuần túy, mà từ quan điểm bán nghệ thuật. Theo tôi, việc sử dụng cấu trúc 'using' theo cách này khá là nghệ thuật.

Bạn có thể nên dán một giao diện khác lên trên IDisposable để đẩy nó ra xa hơn một chút, giải thích cho các nhà phát triển khác tại sao giao diện đó lại ngụ ý IDisposable.

Có rất nhiều lựa chọn thay thế khác để làm điều này nhưng cuối cùng, tôi không thể nghĩ ra cách nào sẽ gọn gàng như thế này, vì vậy hãy bắt đầu!


2
Tôi tin rằng đó cũng là một nghệ thuật sử dụng "sử dụng". Tôi nghĩ rằng bài đăng của Samual Jack liên kết đến nhận xét từ Eric Gunnerson xác nhận việc triển khai "sử dụng" này. Tôi có thể thấy những điểm xuất sắc được thực hiện từ cả Andras và Eric về điều này.
IAbstract

1
Tôi đã nói chuyện với Eric Gunnerson về điều này một thời gian trước và theo anh ấy, đó là ý định của nhóm thiết kế C # rằng việc sử dụng sẽ biểu thị phạm vi. Trên thực tế, anh ta đã mặc định rằng nếu họ thiết kế bằng cách sử dụng trước câu lệnh khóa, thậm chí có thể không có câu lệnh khóa. Nó sẽ là một khối sử dụng với một cuộc gọi Màn hình hoặc một cái gì đó. CẬP NHẬT: Vừa nhận ra câu trả lời tiếp theo là của Eric Lippert, người cũng thuộc nhóm ngôn ngữ. ;) Có lẽ bản thân đội C # không hoàn toàn đồng ý về điều này? Bối cảnh của cuộc thảo luận của tôi w / Gunnerson là lớp TimedLock: bit.ly/lKugIO
Haacked

2
@Haacked - Thật thú vị phải không; nhận xét của bạn hoàn toàn chứng minh quan điểm của tôi; rằng bạn đã nói chuyện với một anh chàng trong nhóm và anh ấy đã hết lòng vì điều đó; sau đó chỉ ra Eric Lippert bên dưới, từ cùng một nhóm không đồng ý. Theo quan điểm của tôi; C # cung cấp một từ khóa khai thác một giao diện và cũng tình cờ mang lại một mẫu mã đẹp mắt, hoạt động trong rất nhiều trường hợp. Nếu chúng ta không nên lạm dụng nó, thì C # nên tìm một cách khác để thực thi điều đó. Dù bằng cách nào, các nhà thiết kế có lẽ cần phải có được những con vịt của họ liên tiếp ở đây! Trong khi chờ đợi: using(Html.BeginForm())thưa ông? không phiền nếu tôi làm như vậy, thưa ông! :)
Andras Zoltan

71

Tôi coi đây là sự lạm dụng câu lệnh using. Tôi biết rằng tôi là thiểu số trong vị trí này.

Tôi coi đây là một sự lạm dụng vì ba lý do.

Đầu tiên, bởi vì tôi mong đợi rằng "using" được sử dụng để sử dụng một tài nguyênloại bỏ nó khi bạn hoàn thành nó . Thay đổi trạng thái chương trình không sử dụng một tài nguyên và thay đổi lại nó không phải là loại bỏ bất cứ thứ gì. Vì vậy, "sử dụng" để đột biến và khôi phục trạng thái là một sự lạm dụng; mã gây hiểu lầm cho người đọc bình thường.

Thứ hai, bởi vì tôi mong đợi "using" được sử dụng với mục đích lịch sự, không cần thiết . Lý do bạn sử dụng "using" để loại bỏ một tệp khi bạn hoàn thành nó không phải vì cần phải làm như vậy, mà vì nó lịch sự - ai đó có thể đang đợi để sử dụng tệp đó, vì vậy nói "xong bây giờ ”là điều đúng đắn về mặt đạo đức phải làm. Tôi hy vọng rằng tôi sẽ có thể cấu trúc lại "cách sử dụng" để tài nguyên đã sử dụng được lưu giữ lâu hơn và được xử lý sau đó, và tác động duy nhất của việc làm đó là hơi gây bất tiện cho các quy trình khác . Một khối "using" có tác động ngữ nghĩa đến trạng thái chương trình bị lạm dụng vì nó ẩn một đột biến quan trọng, bắt buộc của trạng thái chương trình trong một cấu trúc có vẻ như nó ở đó vì sự thuận tiện và lịch sự, không cần thiết.

Và thứ ba, hành động của chương trình của bạn được xác định bởi trạng thái của nó; nhu cầu vận dụng trạng thái một cách cẩn thận chính là lý do tại sao chúng ta có cuộc trò chuyện này ngay từ đầu. Hãy xem xét cách chúng tôi có thể phân tích chương trình gốc của bạn.

Khi bạn mang nó đến một cuộc xem xét mã trong văn phòng của tôi, câu hỏi đầu tiên tôi sẽ hỏi là "có thực sự chính xác để khóa phần mềm nếu một ngoại lệ được ném ra?" Rõ ràng là từ chương trình của bạn rằng điều này sẽ tích cực khóa lại khả năng đóng băng bất kể điều gì xảy ra. Có đúng không? Một ngoại lệ đã được ném ra. Chương trình ở trạng thái không xác định. Chúng tôi không biết liệu Foo, Fiddle hay Bar đã ném, tại sao họ lại ném, hoặc những đột biến nào mà họ thực hiện sang trạng thái khác chưa được làm sạch. Bạn có thể thuyết phục tôi rằng trong tình huống khủng khiếp đó, việc khóa lại luôn là điều đúng đắn?

Có thể là có, có thể là không. Quan điểm của tôi là với mã như nó được viết ban đầu, người đánh giá mã biết đặt câu hỏi . Với mã sử dụng "using", tôi không biết để đặt câu hỏi; Tôi giả sử rằng khối "using" phân bổ một tài nguyên, sử dụng nó một chút và xử lý nó một cách lịch sự khi nó được hoàn thành, không phải là dấu ngoặc nhọn đóng của khối "using" làm thay đổi trạng thái chương trình của tôi trong một khoảng thời gian đặc biệt khi tùy ý nhiều các điều kiện nhất quán của chương trình đã bị vi phạm.

Việc sử dụng khối "using" để có hiệu ứng ngữ nghĩa làm cho chương trình này trở nên phân mảnh:

}

vô cùng ý nghĩa. Khi tôi nhìn vào chiếc nẹp đóng đơn đó, tôi không nghĩ ngay rằng "chiếc nẹp đó có những tác dụng phụ ảnh hưởng sâu rộng đến tình trạng toàn cầu của chương trình của tôi". Nhưng khi bạn lạm dụng "sử dụng" như thế này, thì bỗng nhiên nó xảy ra.

Điều thứ hai tôi sẽ hỏi nếu tôi thấy mã gốc của bạn là "điều gì sẽ xảy ra nếu một ngoại lệ được đưa ra sau khi Mở khóa nhưng trước khi nhập thử?" Nếu bạn đang chạy một hợp ngữ không được tối ưu hóa, trình biên dịch có thể đã chèn một lệnh no-op trước khi thử và có thể xảy ra ngoại lệ hủy bỏ luồng khi no-op. Điều này hiếm khi xảy ra, nhưng nó xảy ra trong đời thực, đặc biệt là trên các máy chủ web. Trong trường hợp đó, mở khóa xảy ra nhưng khóa không bao giờ xảy ra, bởi vì ngoại lệ đã được ném trước khi thử. Hoàn toàn có thể mã này dễ gặp sự cố này và thực sự nên được viết

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

Một lần nữa, có thể nó có, có thể nó không, nhưng tôi biết đặt câu hỏi . Với phiên bản "đang sử dụng", nó cũng dễ gặp phải vấn đề tương tự: một ngoại lệ hủy bỏ chuỗi có thể được ném ra sau khi Frobble bị khóa nhưng trước khi vùng được bảo vệ thử liên quan đến việc sử dụng được nhập. Nhưng với phiên bản "using", tôi cho rằng đây là phiên bản "vậy thì sao?" tình hình. Thật không may nếu điều đó xảy ra, nhưng tôi cho rằng "using" chỉ ở đó để lịch sự, không làm thay đổi trạng thái chương trình cực kỳ quan trọng. Tôi giả sử rằng nếu một số ngoại lệ hủy bỏ luồng khủng khiếp xảy ra vào đúng thời điểm thì, ồ, bộ thu gom rác sẽ dọn dẹp tài nguyên đó cuối cùng bằng cách chạy trình hoàn thiện.


18
Mặc dù tôi sẽ trả lời câu hỏi theo cách khác, tôi nghĩ đó là một bài đăng được tranh luận tốt. Tuy nhiên, tôi đặt vấn đề với yêu cầu của bạn rằng đó usinglà một vấn đề lịch sự hơn là đúng đắn. Tôi tin rằng rò rỉ tài nguyên là vấn đề về tính đúng đắn, mặc dù chúng thường nhỏ đến mức không phải là lỗi có mức độ ưu tiên cao. Do đó, usingcác khối đã có tác động ngữ nghĩa lên trạng thái chương trình và những gì chúng ngăn chặn không chỉ là "sự bất tiện" đối với các quy trình khác mà có thể là một môi trường bị hỏng. Điều đó không có nghĩa là loại tác động ngữ nghĩa khác nhau mà câu hỏi ngụ ý cũng phù hợp.
kvb

13
Rò rỉ tài nguyên là vấn đề về tính đúng đắn. Nhưng việc không sử dụng "using" không làm rò rỉ tài nguyên; tài nguyên sẽ được dọn sạch cuối cùng khi trình hoàn thiện chạy. Việc dọn dẹp có thể không nhanh chóng và kịp thời như bạn muốn, nhưng nó sẽ diễn ra.
Eric Lippert

7
Bài đăng tuyệt vời, đây là hai xu của tôi :) Tôi nghi ngờ "lạm dụng" nhiều usinglà bởi vì đó là một thợ dệt định hướng khía cạnh của một người nghèo trong C #. Những gì nhà phát triển thực sự muốn là một cách để đảm bảo rằng một số mã chung sẽ chạy ở cuối khối mà không cần phải sao chép mã đó. C # không hỗ trợ AOP ngày hôm nay (MarshalByRefObject & PostSharp không tồn tại). Tuy nhiên, sự chuyển đổi của trình biên dịch đối với câu lệnh using làm cho nó có thể đạt được AOP đơn giản bằng cách định hướng lại ngữ nghĩa của việc xử lý tài nguyên xác định. Trong khi không phải là một thực hành tốt, đôi khi hấp dẫn để vượt lên .....
LBushkin

11
Mặc dù tôi thường đồng ý với quan điểm của bạn, nhưng có một số trường hợp khi lợi ích của việc tái mục đích usingquá hấp dẫn để từ bỏ. Ví dụ tốt nhất mà tôi có thể nghĩ đến là theo dõi và báo cáo thời gian mã: using( TimingTrace.Start("MyMethod") ) { /* code */ } Một lần nữa, đây là AOP - Start()ghi lại thời gian bắt đầu trước khi khối bắt đầu, Dispose()ghi lại thời gian kết thúc và ghi lại hoạt động. Nó không thay đổi trạng thái chương trình và không có khả năng xảy ra ngoại lệ. Nó cũng hấp dẫn bởi vì nó tránh được khá nhiều hệ thống ống nước và nó được sử dụng thường xuyên như một khuôn mẫu để giảm bớt ngữ nghĩa khó hiểu.
LBushkin

6
Thật buồn cười, tôi luôn nghĩ việc sử dụng như một phương tiện hỗ trợ RAII. Đối với tôi, có vẻ khá trực quan khi coi khóa là một loại tài nguyên.
Brian

27

Nếu bạn chỉ muốn một số mã rõ ràng, có phạm vi, bạn cũng có thể sử dụng lambdas, á la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

nơi .SafeExecute(Action fribbleAction)phương pháp kết thúc tốt đẹp try- catch- finallykhối.


Trong ví dụ đó, bạn đã bỏ lỡ trạng thái nhập cảnh. Hơn nữa, chắc chắn bạn quấn try / cuối cùng, nhưng bạn không gọi bất cứ điều gì ở cuối cùng, vì vậy nếu một cái gì đó trong lambda ném bạn không có ý tưởng những gì nhà nước bạn đang ở.
Johann Gerell

1
Ah! Ok, tôi đoán ý bạn là đó SafeExecutelà một Fribblephương thức gọi Unlock()Lock()trong lần thử / cuối cùng. Tôi xin lỗi. Tôi ngay lập tức xem SafeExecutenhư một phương pháp mở rộng chung chung và do đó đề cập đến việc thiếu trạng thái nhập và xuất. Lỗi của tôi.
Johann Gerell

1
Hãy rất cẩn thận với apporach này. Có thể có một số phần mở rộng thời gian tồn tại ngoài ý muốn rất nguy hiểm trong trường hợp những người dân địa phương bị bắt!
jason

4
Yuk. Tại sao ẩn thử / bắt / cuối cùng? Làm cho mã của bạn ẩn để người khác đọc.
Frank Schwieterman,

2
@Yukky Frank: Tôi không muốn "giấu" gì cả, đó là yêu cầu của người hỏi. :-P ... Điều đó nói rằng, yêu cầu cuối cùng là vấn đề "Đừng lặp lại chính mình". Bạn có thể có rất nhiều phương thức yêu cầu cùng một "khung" soạn sẵn về việc thu thập / giải phóng một thứ gì đó một cách rõ ràng và bạn không muốn áp đặt điều đó cho người gọi (hãy nghĩ đến việc đóng gói). Ngoài ra, người ta có thể đặt tên cho các phương thức như .SafeExecute (...) rõ ràng hơn để giao tiếp đủ tốt những gì chúng làm.
herzmeister

25

Eric Gunnerson, người trong nhóm thiết kế ngôn ngữ C #, đã đưa ra câu trả lời này cho khá nhiều câu hỏi tương tự:

Doug hỏi:

re: Một câu lệnh khóa có thời gian chờ ...

Tôi đã thực hiện thủ thuật này trước đây để xử lý các mẫu phổ biến trong nhiều phương pháp. Thường khóa mua lại , nhưng có một số khác . Vấn đề là nó luôn có cảm giác giống như một vụ hack vì đối tượng không thực sự dùng một lần quá nhiều như " call-back-at-the-end-of-scope-could ".

Doug,

Khi chúng tôi quyết định [sic] câu lệnh using, chúng tôi quyết định đặt tên nó là "using" thay vì một cái gì đó cụ thể hơn để xử lý các đối tượng để nó có thể được sử dụng cho chính xác tình huống này.


4
Chà câu trích dẫn đó sẽ nói lên những gì anh ấy đang đề cập đến khi anh ấy nói "chính xác là kịch bản này", vì tôi nghi ngờ anh ấy đang đề cập đến câu hỏi tương lai này.
Frank Schwieterman,

4
@Frank Schwieterman: Tôi đã hoàn thành phần trích dẫn. Rõ ràng, những người từ nhóm C # đã nghĩ rằng usingtính năng này không bị giới hạn trong việc xử lý tài nguyên.
paercebal

11

Đó là một con dốc trơn trượt. IDisposable có một hợp đồng, một hợp đồng được hỗ trợ bởi người hoàn thiện. Một bản hoàn thiện là vô dụng trong trường hợp của bạn. Bạn không thể buộc khách hàng sử dụng câu lệnh using, chỉ khuyến khích họ làm như vậy. Bạn có thể buộc nó bằng một phương pháp như sau:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

Nhưng điều đó có xu hướng hơi khó xử nếu không có lamdas. Điều đó nói rằng, tôi đã sử dụng IDisposable như bạn đã làm.

Tuy nhiên, có một chi tiết trong bài đăng của bạn khiến điều này trở nên nguy hiểm gần với kiểu chống đối. Bạn đã đề cập rằng những phương pháp đó có thể ném một ngoại lệ. Đây không phải là điều mà người gọi có thể bỏ qua. Anh ấy có thể làm ba điều về điều đó:

  • Không làm gì cả, ngoại lệ không thể khôi phục được. Trường hợp bình thường. Gọi Mở khóa không thành vấn đề.
  • Nắm bắt và xử lý ngoại lệ
  • Khôi phục trạng thái trong mã của anh ấy và để ngoại lệ chuyển qua chuỗi cuộc gọi.

Hai cách sau yêu cầu người gọi viết một khối thử một cách rõ ràng. Bây giờ câu lệnh using cản trở. Nó có thể khiến khách hàng hôn mê và khiến họ tin rằng lớp học của bạn đang ở trạng thái ổn định và không cần phải làm thêm công việc nào. Điều đó hầu như không bao giờ chính xác.


Tôi nghĩ rằng trường hợp bắt buộc được đưa ra bởi "herzmeister der Welten" ở trên - ngay cả khi OP có vẻ không thích ví dụ này.
Benjamin Podszun

Vâng, tôi đã hiểu anh ấy SafeExecutelà một phương pháp mở rộng chung. Bây giờ tôi thấy rằng nó có thể có nghĩa là một Fribblephương thức gọi Unlock()Lock()cuối cùng là thử / cuối cùng. Lỗi của tôi.
Johann Gerell

Bỏ qua một ngoại lệ không có nghĩa là nó không thể khôi phục được. Nó có nghĩa là nó sẽ được xử lý ở trên trong ngăn xếp. Ví dụ, các ngoại lệ có thể được sử dụng để thoát khỏi một chuỗi một cách duyên dáng. Trong trường hợp này, bạn phải giải phóng chính xác các tài nguyên của mình, bao gồm cả các ổ khóa bị khóa.
paercebal

7

Một ví dụ thực tế là BeginForm của ASP.net MVC. Về cơ bản bạn có thể viết:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

hoặc là

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

Html.EndForm gọi Dispose và Dispose chỉ xuất ra </form> thẻ. Điều tốt về điều này là các dấu ngoặc vuông {} tạo ra một "phạm vi" có thể nhìn thấy được giúp bạn dễ dàng xem những gì bên trong biểu mẫu và những gì không.

Tôi sẽ không lạm dụng nó, nhưng về cơ bản IDisposable chỉ là một cách để nói "Bạn PHẢI gọi hàm này khi bạn hoàn thành nó". MvcForm sử dụng nó để đảm bảo biểu mẫu được đóng, Stream sử dụng nó để đảm bảo luồng đã đóng, bạn có thể sử dụng nó để đảm bảo đối tượng được mở khóa.

Cá nhân tôi sẽ chỉ sử dụng nó nếu hai quy tắc sau là đúng, nhưng chúng được đặt ra bởi tôi một cách tùy tiện:

  • Dispose phải là một hàm luôn phải chạy, vì vậy không nên có bất kỳ điều kiện nào ngoại trừ Null-Checks
  • Sau Dispose (), đối tượng sẽ không thể sử dụng lại được. Nếu tôi muốn một đối tượng có thể tái sử dụng, tôi muốn cung cấp cho nó các phương thức mở / đóng hơn là loại bỏ. Vì vậy, tôi ném ra một lỗi không hợp lệ khi cố gắng sử dụng một đối tượng được xử lý.

Cuối cùng, đó là tất cả về kỳ vọng. Nếu một đối tượng triển khai IDisposable, tôi cho rằng nó cần thực hiện một số thao tác dọn dẹp, vì vậy tôi gọi nó. Tôi nghĩ rằng nó thường đánh bại khi có chức năng "Tắt máy".

Điều đó đang được nói, tôi không thích dòng này:

this.Frobble.Fiddle();

Khi FrobbleJanitor "sở hữu" Frobble bây giờ, tôi tự hỏi liệu có tốt hơn không nếu thay vào đó gọi Fiddle bằng anh ấy Frobble trong Janitor?


Về "Tôi không thích dòng này" của bạn - tôi thực sự đã nghĩ sơ qua về việc làm theo cách đó khi xây dựng câu hỏi, nhưng tôi không muốn làm lộn xộn ngữ nghĩa của câu hỏi của mình. Nhưng tôi đồng ý với bạn. Đại loại. :)
Johann Gerell

1
Được ủng hộ vì đã cung cấp một ví dụ từ chính Microsoft. Lưu ý rằng có một ngoại lệ cụ thể được đưa ra trong trường hợp bạn đề cập: ObjectDisposedException.
Antitoon

4

Chúng tôi sử dụng rất nhiều mẫu này trong cơ sở mã của mình và tôi đã thấy nó ở khắp nơi trước đây - tôi chắc chắn rằng nó cũng phải được thảo luận ở đây. Nói chung, tôi không thấy có gì sai khi làm điều này, nó cung cấp một mô hình hữu ích và không gây hại thực sự.


4

Nói thêm về điều này: Tôi đồng ý với hầu hết ở đây rằng điều này là mong manh, nhưng hữu ích. Tôi muốn chỉ bạn đến System.Transaction.TransactionScope lớp , lớp này thực hiện điều gì đó giống như bạn muốn.

Nói chung, tôi thích cú pháp, nó loại bỏ rất nhiều thứ lộn xộn khỏi thịt thực. Mặc dù vậy, hãy cân nhắc đặt tên hay cho lớp trợ giúp - có thể là ... Phạm vi, như ví dụ ở trên. Tên nên cho thấy nó gói gọn một đoạn mã. * Phạm vi, * Khối hoặc một cái gì đó tương tự nên làm điều đó.


1
"Người gác cổng" xuất phát từ một bài báo C ++ cũ của Andrei Alexandrescu về những người bảo vệ phạm vi, thậm chí trước khi ScopeGuard đến với Loki.
Johann Gerell

@Benjamin: Tôi thích lớp TransactionScope, chưa từng nghe nói về nó trước đây. Có lẽ vì tôi đang ở trong đất Net CF, mà nó không bao gồm ... :-(
Johann Gerell

4

Lưu ý: Quan điểm của tôi có thể bị sai lệch từ nền tảng C ++ của tôi, vì vậy giá trị câu trả lời của tôi nên được đánh giá dựa trên sự thiên vị có thể có đó ...

Điều gì nói lên Đặc tả ngôn ngữ C #?

Trích dẫn Đặc điểm ngôn ngữ C # :

8.13 Câu lệnh using

[...]

Một tài nguyên là một lớp học hoặc struct mà cụ System.IDisposable, trong đó bao gồm một phương pháp parameterless duy nhất tên là Dispose. Mã đang sử dụng một tài nguyên có thể gọi Dispose để chỉ ra rằng tài nguyên đó không còn cần thiết nữa. Nếu Dispose không được gọi, thì việc xử lý tự động cuối cùng sẽ xảy ra như một hệ quả của việc thu gom rác.

Tất nhiên, mã đang sử dụng một tài nguyên là mã bắt đầu bằng usingtừ khóa và đi cho đến khi phạm vi được đính kèm với using.

Vì vậy, tôi đoán điều này là Ok vì Khóa là một tài nguyên.

Có lẽ từ khóa usingđã được chọn sai. Có lẽ nó nên được gọi như vậy scoped.

Sau đó, chúng ta có thể coi hầu hết mọi thứ là tài nguyên. Một xử lý tệp. Một kết nối mạng ... Một chủ đề?

Một chủ đề ???

Sử dụng (hoặc lạm dụng) using từ khóa?

Nó sẽ sáng bóng đến (ab) sử dụng các usingtừ khóa để đảm bảo công việc của thread được kết thúc trước khi thoát khỏi phạm vi?

Herb Sutter dường như nghĩ rằng nó sáng bóng , vì anh ấy đưa ra một cách sử dụng thú vị của mẫu IDispose để đợi công việc của một sợi kết thúc:

http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095

Đây là mã, được sao chép từ bài viết:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

Trong khi mã C # cho đối tượng Active không được cung cấp, nó được viết C # sử dụng mẫu IDispose cho mã có phiên bản C ++ được chứa trong hàm hủy. Và bằng cách xem xét phiên bản C ++, chúng ta thấy một trình hủy sẽ đợi luồng nội bộ kết thúc trước khi thoát, như được hiển thị trong phần trích xuất khác của bài viết này:

~Active() {
    // etc.
    thd->join();
 }

Vì vậy, theo như Herb được biết, nó sáng bóng .


3

Tôi tin rằng câu trả lời cho câu hỏi của bạn là không, đây sẽ không phải là sự lạm dụng IDisposable .

Cách tôi hiểu về IDisposablegiao diện là bạn không được phép làm việc với một đối tượng khi nó đã được xử lý (ngoại trừ việc bạn được phép gọi nóDispose phương thức thường xuyên nếu bạn muốn).

Vì bạn tạo một đối tượng mới FrobbleJanitor rõ ràng mỗi khi bạn truy cập usingcâu lệnh, bạn không bao giờ sử dụng cùng một FrobbeJanitorđối tượng hai lần. Và vì mục đích của nó là quản lý một đối tượng khác,Dispose có vẻ thích hợp với nhiệm vụ giải phóng tài nguyên ("được quản lý") này.

(Btw., Mã mẫu tiêu chuẩn thể hiện việc triển khai đúng cách Disposehầu như luôn luôn gợi ý rằng các tài nguyên được quản lý cũng nên được giải phóng, chứ không chỉ các tài nguyên không được quản lý như các bộ xử lý hệ thống tệp.)

Điều duy nhất mà cá nhân tôi lo lắng là nó không rõ ràng hơn về những gì đang xảy ra using (var janitor = new FrobbleJanitor())với một try..finallykhối rõ ràng hơn nơi các Lock& Unlockhoạt động được hiển thị trực tiếp. Nhưng cách tiếp cận nào được thực hiện có lẽ phụ thuộc vào sở thích cá nhân.


1

Nó không lạm dụng. Bạn đang sử dụng chúng để làm gì. Nhưng bạn có thể phải xem xét cái khác tùy theo nhu cầu của bạn. Ví dụ: nếu bạn đang chọn 'tính nghệ thuật' thì bạn có thể sử dụng 'using' nhưng nếu đoạn mã của bạn được thực thi nhiều lần, thì vì lý do hiệu suất, bạn có thể sử dụng cấu trúc 'try' .. 'last'. Bởi vì 'using' thường liên quan đến sáng tạo của một đối tượng.


1

Tôi nghĩ bạn đã làm đúng. Quá tải Dispose () sẽ là một vấn đề mà cùng một lớp sau này đã thực hiện dọn dẹp mà nó thực sự phải làm và thời gian tồn tại của quá trình dọn dẹp đó sẽ khác sau đó là thời điểm bạn mong đợi giữ khóa. Nhưng vì bạn đã tạo một lớp riêng biệt (FrobbleJanitor) chỉ chịu trách nhiệm khóa và mở khóa Frobble, mọi thứ được tách rời đủ để bạn sẽ không gặp phải vấn đề đó.

Tuy nhiên, tôi sẽ đổi tên FrobbleJanitor, có thể thành một cái gì đó giống như FrobbleLockSession.


Về việc đổi tên - Tôi đã sử dụng "Người dọn dẹp" vì lý do chính mà tôi xử lý Khóa / Mở khóa. Tôi nghĩ rằng nó có vần điệu với các lựa chọn tên khác. ;-) Nếu tôi sử dụng phương pháp này như một mẫu trong toàn bộ cơ sở mã của mình, tôi chắc chắn sẽ bao gồm một cái gì đó mô tả chung chung hơn, như bạn ngụ ý với "Phiên". Nếu đây là ngày C ++ cũ, có lẽ tôi sẽ sử dụng FrobbleUnlockScope.
Johann Gerell
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.