Sử dụng phương pháp Hoàn thiện / Loại bỏ trong C #


381

C # 2008

Tôi đã làm việc này được một thời gian và tôi vẫn còn bối rối về việc sử dụng các phương thức hoàn thiện và loại bỏ trong mã. Câu hỏi của tôi dưới đây:

  1. Tôi biết rằng chúng ta chỉ cần một bộ hoàn thiện trong khi xử lý các tài nguyên không được quản lý. Tuy nhiên, nếu có các tài nguyên được quản lý thực hiện các cuộc gọi đến các tài nguyên không được quản lý, liệu nó có còn cần phải thực hiện một bộ hoàn thiện không?

  2. Tuy nhiên, nếu tôi phát triển một lớp không sử dụng bất kỳ tài nguyên không được quản lý nào - trực tiếp hoặc gián tiếp, tôi có nên thực hiện IDisposableđể cho phép các máy khách của lớp đó sử dụng 'câu lệnh sử dụng' không?

    Liệu có khả thi khi triển khai IDis Dùng chỉ để cho phép các máy khách của lớp bạn sử dụng câu lệnh sử dụng không?

    using(myClass objClass = new myClass())
    {
        // Do stuff here
    }
    
  3. Tôi đã phát triển mã đơn giản dưới đây để chứng minh việc sử dụng Hoàn tất / loại bỏ:

    public class NoGateway : IDisposable
    {
        private WebClient wc = null;
    
        public NoGateway()
        {
            wc = new WebClient();
            wc.DownloadStringCompleted += wc_DownloadStringCompleted;
        }
    
    
        // Start the Async call to find if NoGateway is true or false
        public void NoGatewayStatus()
        {
            // Start the Async's download
                // Do other work here
            wc.DownloadStringAsync(new Uri(www.xxxx.xxx));
        }
    
        private void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            // Do work here
        }
    
        // Dispose of the NoGateway object
        public void Dispose()
        {
            wc.DownloadStringCompleted -= wc_DownloadStringCompleted;
            wc.Dispose();
            GC.SuppressFinalize(this);
        }
    }
    

Câu hỏi về mã nguồn:

  1. Ở đây tôi chưa thêm trình hoàn thiện, và thông thường, trình hoàn thiện sẽ được gọi bởi GC và trình hoàn thiện sẽ gọi là Dispose. Khi tôi không có bộ hoàn thiện, khi nào tôi gọi phương thức Vứt bỏ? Nó có phải là khách hàng của lớp phải gọi nó không?

    Vì vậy, lớp của tôi trong ví dụ được gọi là NoGateway và khách hàng có thể sử dụng và loại bỏ lớp như thế này:

    using(NoGateway objNoGateway = new NoGateway())
    {
        // Do stuff here   
    }
    

    Phương thức Vứt bỏ sẽ được tự động gọi khi thực thi đến hết khối sử dụng, hay máy khách phải gọi thủ công phương thức vứt bỏ? I E

    NoGateway objNoGateway = new NoGateway();
    // Do stuff with object
    objNoGateway.Dispose(); // finished with it
    
  2. Tôi đang sử dụng WebClientlớp học trong NoGatewaylớp học của tôi . Bởi vì WebClientthực hiện IDisposablegiao diện, điều này có nghĩa là WebClientgián tiếp sử dụng tài nguyên không được quản lý? Có một quy tắc cứng và nhanh để làm theo điều này? Làm thế nào để tôi biết rằng một lớp sử dụng tài nguyên không được quản lý?


1
mẫu thiết kế phức tạp này có thực sự cần thiết để giải quyết vấn đề tái phát tài nguyên này không?
zinking

Câu trả lời:


422

Mẫu IDis Dùng được đề xuất ở đây . Khi lập trình một lớp sử dụng IDis Dùng một lần, thông thường bạn nên sử dụng hai mẫu:

Khi triển khai một lớp kín không sử dụng các tài nguyên không được quản lý, bạn chỉ cần thực hiện một phương thức Vứt bỏ như với các triển khai giao diện thông thường:

public sealed class A : IDisposable
{
    public void Dispose()
    {
        // get rid of managed resources, call Dispose on member variables...
    }
}

Khi thực hiện một lớp chưa được khám phá, hãy làm như thế này:

public class B : IDisposable
{    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }   
        // get rid of unmanaged resources
    }

    // only if you use unmanaged resources directly in B
    //~B()
    //{
    //    Dispose(false);
    //}
}

Lưu ý rằng tôi đã không tuyên bố một người hoàn thiện trong B; bạn chỉ nên thực hiện một bộ hoàn thiện nếu bạn có tài nguyên không được quản lý thực tế để xử lý. CLR xử lý các đối tượng có thể hoàn thiện khác với các đối tượng không thể hoàn thành, ngay cả khi SuppressFinalizeđược gọi.

Vì vậy, bạn không nên khai báo bộ hoàn thiện trừ khi bạn phải, nhưng bạn cung cấp cho những người thừa kế của lớp bạn một cái móc để gọi cho bạn Disposevà tự thực hiện một bộ hoàn thiện nếu họ sử dụng trực tiếp các tài nguyên không được quản lý:

public class C : B
{
    private IntPtr m_Handle;

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // get rid of managed resources
        }
        ReleaseHandle(m_Handle);

        base.Dispose(disposing);
    }

    ~C() {
        Dispose(false);
    }
}

Nếu bạn không sử dụng trực tiếp các tài nguyên không được quản lý ( SafeHandlevà bạn bè không được tính, vì họ tuyên bố các công cụ hoàn thiện của riêng họ), thì đừng triển khai trình hoàn thiện, vì GC xử lý các lớp hoàn thiện khác nhau, ngay cả khi bạn sau đó loại bỏ trình hoàn thiện. Cũng lưu ý rằng, mặc dù Bkhông có bộ hoàn thiện, nó vẫn gọi SuppressFinalizeđể xử lý chính xác với bất kỳ lớp con nào thực hiện bộ hoàn thiện.

Khi một lớp thực hiện giao diện IDis Dùng một lần, điều đó có nghĩa là ở đâu đó có một số tài nguyên không được quản lý sẽ bị loại bỏ khi bạn sử dụng xong lớp. Các tài nguyên thực tế được gói gọn trong các lớp; bạn không cần phải xóa chúng một cách rõ ràng. Chỉ cần gọi Dispose()hoặc gói lớp trong một using(...) {}sẽ đảm bảo bất kỳ tài nguyên không được quản lý nào được loại bỏ khi cần thiết.


26
Tôi đồng ý với thecoop. Lưu ý rằng bạn không cần trình hoàn thiện nếu bạn chỉ xử lý các tài nguyên được quản lý (thực tế, bạn KHÔNG nên cố gắng truy cập các đối tượng được quản lý từ bên trong trình hoàn thiện của mình (ngoài "này"), vì không có thứ tự được bảo đảm trong đó GC sẽ dọn sạch các đối tượng. Ngoài ra, nếu bạn đang sử dụng .Net 2.0 hoặc tốt hơn, bạn có thể (và nên) sử dụng SafeHandles để bọc các tay cầm không được quản lý. Safehandles giảm đáng kể nhu cầu của bạn để viết các trình hoàn thiện cho các lớp được quản lý của bạn . com / bclteam / archive / 2005/03/16 / 396900.aspx
JMarsch

5
Tôi nghĩ rằng tốt hơn là nên thực hiện cuộc gọi đến MessageBox.Show ("Lỗi", + GetType (). Tên + "không được xử lý") trong trình hoàn thiện, vì đối tượng dùng một lần nên LUÔN LUÔN và nếu bạn không thực hiện việc này tốt nhất để được cảnh báo với thực tế càng sớm càng tốt.
erikkallen

95
@erikkallen có phải là một trò đùa? :)
Alex Norcliffe

2
vì cần có thêm nỗ lực tính toán trong CLR để theo dõi các lớp học với các công cụ hoàn thiện tích cực. - Thực hiện một quyết toán gây ra điều này xảy ra. Gọi GC.SuppressFinalize có nghĩa là Finalizer không nên được gọi bởi bộ thực thi. Nó vẫn đi Gen2 bất kể. Không thêm trình hoàn thiện nếu bạn không xử lý các tài nguyên được quản lý. Công cụ sửa đổi lớp niêm phong hoặc không niêm phong không liên quan đến điểm đó.
Ritch Melton

3
@Ritch: trích dẫn? Đó không hẳn là một điều xấu; nếu bạn đang thực hiện IDisposable, rất có thể nó sẽ bị treo trong chốc lát. Bạn đang tiết kiệm CLR nỗ lực phải sao chép nó từ Gen0 -> Gen1 -> Gen2
thecoop

123

Các mô hình chính thức để thực hiện IDisposablelà khó hiểu. Tôi tin rằng điều này là tốt hơn :

public class BetterDisposableClass : IDisposable {

  public void Dispose() {
    CleanUpManagedResources();
    CleanUpNativeResources();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Một giải pháp tốt hơn nữa là có một quy tắc mà bạn luôn phải tạo một lớp bao bọc cho bất kỳ tài nguyên không được quản lý nào mà bạn cần xử lý:

public class NativeDisposable : IDisposable {

  public void Dispose() {
    CleanUpNativeResource();
    GC.SuppressFinalize(this);
  }

  protected virtual void CleanUpNativeResource() {
    // ...
  }

  ~NativeDisposable() {
    CleanUpNativeResource();
  }

}

Với SafeHandlevà các dẫn xuất của nó, các lớp này nên rất hiếm .

Kết quả cho các lớp dùng một lần không xử lý trực tiếp các tài nguyên không được quản lý, ngay cả khi có sự kế thừa, là rất mạnh: họ không cần phải quan tâm đến các tài nguyên không được quản lý nữa . Chúng sẽ đơn giản để thực hiện và hiểu:

public class ManagedDisposable : IDisposable {

  public virtual void Dispose() {
    // dispose of managed resources
  }

}

@Kyle: Cảm ơn! Tôi cũng thích nó :-) Có một phần tiếp theo ở đây .
Jordão

4
Mặc dù có một điều tôi muốn lưu ý là nó không ngăn được gọi lần thứ hai.
HuseyinUslu

5
@HuseyinUslu: đây chỉ là bản chất của mẫu. Bạn chắc chắn có thể thêm một disposedcờ và kiểm tra phù hợp.
Jordão

2
@didibus: việc thêm disposedcờ là một vấn đề đơn giản , kiểm tra nó trước khi xử lý và đặt nó sau khi xử lý. Nhìn vào đây cho ý tưởng. Bạn cũng nên kiểm tra cờ trước bất kỳ phương thức nào của lớp. Có ý nghĩa? Nó có phức tạp không?
Jordão

1
+1 cho "Một giải pháp thậm chí tốt hơn là có một quy tắc mà bạn luôn phải tạo một lớp bao bọc cho bất kỳ tài nguyên không được quản lý nào mà bạn cần xử lý" . Tôi tình cờ thấy điều này trong một addon cho VLC và tôi đã sử dụng nó kể từ đó. Tiết kiệm rất nhiều đau đầu ...
Franz B.

37

Lưu ý rằng mọi triển khai IDis Dùng phải tuân theo mẫu bên dưới (IMHO). Tôi đã phát triển mẫu này dựa trên thông tin từ một số "vị thần" .NET tuyệt vời của Nguyên tắc thiết kế .NET Framework (lưu ý rằng MSDN không tuân theo điều này vì một số lý do!). Nguyên tắc thiết kế .NET Framework được viết bởi Krzysztof Cwalina (Kiến trúc sư CLR vào thời điểm đó) và Brad Abrams (Tôi tin rằng Người quản lý chương trình CLR tại thời điểm đó) và Bill Wagner ([C # hiệu quả] và [C hiệu quả hơn C #] hãy tìm những thứ này trên Amazon.com:

Lưu ý rằng bạn KHÔNG BAO GIỜ nên triển khai Finalizer trừ khi lớp của bạn trực tiếp chứa (không kế thừa) tài nguyên chưa được quản lý. Khi bạn triển khai Finalizer trong một lớp, ngay cả khi nó không bao giờ được gọi, nó vẫn được đảm bảo để sống cho một bộ sưu tập bổ sung. Nó được tự động đặt vào Hàng đợi quyết toán (chạy trên một luồng). Ngoài ra, một lưu ý rất quan trọng ... tất cả các mã được thực thi trong Finalizer (nếu bạn cần triển khai một) PHẢI an toàn cho luồng và an toàn ngoại lệ! Mọi thứ BAD sẽ xảy ra nếu không ... (nghĩa là hành vi không xác định và trong trường hợp ngoại lệ, một sự cố ứng dụng nghiêm trọng không thể phục hồi).

Mẫu tôi đã ghép lại (và viết một đoạn mã cho) sau:

#region IDisposable implementation

//TODO remember to make this class inherit from IDisposable -> $className$ : IDisposable

// Default initialization for a bool is 'false'
private bool IsDisposed { get; set; }

/// <summary>
/// Implementation of Dispose according to .NET Framework Design Guidelines.
/// </summary>
/// <remarks>Do not make this method virtual.
/// A derived class should not be able to override this method.
/// </remarks>
public void Dispose()
{
    Dispose( true );

    // This object will be cleaned up by the Dispose method.
    // Therefore, you should call GC.SupressFinalize to
    // take this object off the finalization queue 
    // and prevent finalization code for this object
    // from executing a second time.

    // Always use SuppressFinalize() in case a subclass
    // of this type implements a finalizer.
    GC.SuppressFinalize( this );
}

/// <summary>
/// Overloaded Implementation of Dispose.
/// </summary>
/// <param name="isDisposing"></param>
/// <remarks>
/// <para><list type="bulleted">Dispose(bool isDisposing) executes in two distinct scenarios.
/// <item>If <paramref name="isDisposing"/> equals true, the method has been called directly
/// or indirectly by a user's code. Managed and unmanaged resources
/// can be disposed.</item>
/// <item>If <paramref name="isDisposing"/> equals false, the method has been called by the 
/// runtime from inside the finalizer and you should not reference 
/// other objects. Only unmanaged resources can be disposed.</item></list></para>
/// </remarks>
protected virtual void Dispose( bool isDisposing )
{
    // TODO If you need thread safety, use a lock around these 
    // operations, as well as in your methods that use the resource.
    try
    {
        if( !this.IsDisposed )
        {
            if( isDisposing )
            {
                // TODO Release all managed resources here

                $end$
            }

            // TODO Release all unmanaged resources here



            // TODO explicitly set root references to null to expressly tell the GarbageCollector
            // that the resources have been disposed of and its ok to release the memory allocated for them.


        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );

        this.IsDisposed = true;
    }
}

//TODO Uncomment this code if this class will contain members which are UNmanaged
// 
///// <summary>Finalizer for $className$</summary>
///// <remarks>This finalizer will run only if the Dispose method does not get called.
///// It gives your base class the opportunity to finalize.
///// DO NOT provide finalizers in types derived from this class.
///// All code executed within a Finalizer MUST be thread-safe!</remarks>
//  ~$className$()
//  {
//     Dispose( false );
//  }
#endregion IDisposable implementation

Đây là mã để triển khai IDis Dùng trong một lớp dẫn xuất. Lưu ý rằng bạn không cần liệt kê rõ ràng sự kế thừa từ IDis Dùng trong định nghĩa của lớp dẫn xuất.

public DerivedClass : BaseClass, IDisposable (remove the IDisposable because it is inherited from BaseClass)


protected override void Dispose( bool isDisposing )
{
    try
    {
        if ( !this.IsDisposed )
        {
            if ( isDisposing )
            {
                // Release all managed resources here

            }
        }
    }
    finally
    {
        // explicitly call the base class Dispose implementation
        base.Dispose( isDisposing );
    }
}

Tôi đã đăng triển khai này trên blog của mình tại: Cách triển khai đúng cách Loại bỏ mẫu


Ai đó cũng có thể thêm một mẫu cho một lớp dẫn xuất (xuất phát từ lớp cơ sở này)
akjoshi

3
@akjoshi - Tôi đã cập nhật mẫu ở trên để bao gồm mã cho lớp dùng một lần. Cũng lưu ý, KHÔNG BAO GIỜ triển khai Trình hoàn thiện trong lớp dẫn xuất ...
Dave Black

3
Microsoft có vẻ thích đặt cờ "xử lý" ở cuối phương thức đã xử lý, nhưng điều đó có vẻ sai đối với tôi. Các cuộc gọi dự phòng đến "Vứt bỏ" được cho là không làm gì cả; trong khi người ta thường không mong muốn Dispose được gọi đệ quy, những điều như vậy có thể xảy ra nếu một người đang cố gắng Vứt bỏ một vật thể bị bỏ lại trong trạng thái không hợp lệ bởi một ngoại lệ xảy ra trong quá trình xây dựng hoặc một số hoạt động khác. Tôi nghĩ rằng sử dụng một cờ Interlocked.Exchangetrên số nguyên IsDisposedtrong hàm bao bọc không ảo sẽ an toàn hơn.
supercat

@DaveBlack: Điều gì xảy ra nếu lớp cơ sở của bạn không sử dụng tài nguyên không được quản lý, nhưng lớp dẫn xuất của bạn thì sao? Bạn có phải triển khai Finalizer trong lớp dẫn xuất không? Và nếu vậy, làm thế nào để bạn biết rằng lớp cơ sở đã không triển khai nó nếu bạn không có quyền truy cập vào nguồn?
Didier A.

@DaveBlack "Tôi đã phát triển mô hình này dựa trên thông tin từ một số" vị thần ".NET xuất sắc" Nếu một trong những vị thần là Jon Skeet thì tôi sẽ làm theo lời khuyên của bạn.
Elisabeth

23

Tôi đồng ý với pm100 (và nên nói rõ điều này trong bài viết trước của tôi).

Bạn không bao giờ nên triển khai IDis Dùng trong một lớp trừ khi bạn cần nó. Để được cụ thể, có khoảng 5 lần bạn cần / nên thực hiện IDis Dùng một lần:

  1. Lớp của bạn chứa một cách rõ ràng (tức là không thông qua kế thừa) bất kỳ tài nguyên được quản lý nào thực hiện IDis Dùng một lần và nên được dọn sạch sau khi lớp của bạn không còn được sử dụng. Ví dụ: nếu lớp của bạn chứa một phiên bản của Stream, DbCommand, DataTable, v.v.

  2. Lớp của bạn rõ ràng chứa bất kỳ tài nguyên được quản lý nào thực hiện phương thức Đóng () - ví dụ IDataReader, IDbConnection, v.v ... Lưu ý rằng một số trong các lớp này thực hiện IDis Dùng bằng cách sử dụng phương thức Dispose () cũng như phương thức Đóng ().

  3. Lớp của bạn rõ ràng chứa tài nguyên không được quản lý - ví dụ: đối tượng COM, con trỏ (vâng, bạn có thể sử dụng con trỏ trong C # được quản lý nhưng chúng phải được khai báo trong các khối 'không an toàn', v.v. Trong trường hợp tài nguyên không được quản lý, bạn cũng nên đảm bảo gọi System.R.78.InteropService.Marshal.ReleaseComObject () trên RCW. Mặc dù về mặt lý thuyết, RCW là một trình bao bọc được quản lý, vẫn có tham chiếu đếm đang diễn ra dưới vỏ bọc.

  4. Nếu lớp của bạn đăng ký vào các sự kiện bằng cách sử dụng tài liệu tham khảo mạnh mẽ. Bạn cần hủy đăng ký / tách bản thân khỏi các sự kiện. Luôn luôn đảm bảo rằng những thứ này không có giá trị trước tiên trước khi cố gắng hủy đăng ký / tách chúng ra!.

  5. Lớp học của bạn có chứa bất kỳ sự kết hợp nào ở trên ...

Một cách khác được đề xuất để làm việc với các đối tượng COM và phải sử dụng Marshal.ReleaseComObject () là sử dụng lớp System.R nb.InteropService.SafeHandle.

BCL (Nhóm thư viện lớp cơ sở) có một bài đăng blog hay về nó ở đây http://blogs.msdn.com/bclteam/archive/2005/03/16/396900.aspx

Một lưu ý rất quan trọng cần thực hiện là nếu bạn đang làm việc với WCF và dọn dẹp tài nguyên, bạn nên LUÔN LUÔN tránh khối 'sử dụng'. Có rất nhiều bài viết trên blog và một số trên MSDN về lý do tại sao đây là một ý tưởng tồi. Tôi cũng đã đăng về nó ở đây - Đừng sử dụng 'bằng cách sử dụng ()' với proxy WCF


3
Tôi tin rằng có trường hợp thứ 5: Nếu lớp của bạn đăng ký các sự kiện bằng cách sử dụng các tham chiếu mạnh, thì bạn nên triển khai IDis Dùng một lần và tự hủy đăng ký khỏi các sự kiện trong phương thức Vứt bỏ.
Didier A.

Chào didibus. Ư, bạn đung. Tôi quên về điều đó. Tôi đã sửa đổi câu trả lời của tôi để bao gồm đó là một trường hợp. Cảm ơn.
Dave Black

Tài liệu MSDN cho mẫu xử lý thêm một trường hợp khác: "NGƯỜI TIÊU DÙNG triển khai Mẫu Loại bỏ cơ bản trên các lớp mà bản thân chúng không chứa tài nguyên không được quản lý hoặc các đối tượng dùng một lần nhưng có khả năng có các kiểu con. Lớp .Stream. Mặc dù đây là lớp cơ sở trừu tượng không chứa bất kỳ tài nguyên nào, nhưng hầu hết các lớp con của nó làm và vì điều này, nó thực hiện mô hình này. "
Gonen I

12

Sử dụng lambdas thay vì IDis Dùng.

Tôi chưa bao giờ hồi hộp với toàn bộ ý tưởng sử dụng / IDis Dùng. Vấn đề là nó yêu cầu người gọi phải:

  • biết rằng họ phải sử dụng IDis Dùng
  • nhớ sử dụng 'bằng cách sử dụng'.

Phương pháp ưa thích mới của tôi là sử dụng phương pháp nhà máy và lambda thay thế

Hãy tưởng tượng tôi muốn làm một cái gì đó với SqlConnection (thứ gì đó nên được sử dụng). Kinh điển bạn sẽ làm

using (Var conn = Factory.MakeConnection())
{
     conn.Query(....);
}

Cách mới

Factory.DoWithConnection((conn)=>
{
    conn.Query(...);
}

Trong trường hợp đầu tiên, người gọi đơn giản là không thể sử dụng cú pháp sử dụng. Trong trường hợp thứ hai, người dùng không có sự lựa chọn. Không có phương thức nào tạo đối tượng SqlConnection, người gọi phải gọi DoWithConnection.

DoWithConnection trông như thế này

void DoWithConnection(Action<SqlConnection> action)
{
   using (var conn = MakeConnection())
   {
       action(conn);
   }
}

MakeConnection bây giờ là riêng tư


2
Gói mọi thứ trong lambdas có thể là một cách tiếp cận tốt, nhưng nó có giới hạn. Thực tế không quá tệ đối với các tình huống trong thực tế, tất cả người tiêu dùng của một lớp sẽ sử dụng khối "sử dụng", nhưng nó sẽ không cho phép các tình huống trong đó một phương thức sẽ lưu trữ IDis Dùng trong trường lớp (trực tiếp hoặc trong một cái gì đó như trình lặp ).
supercat

@supercat bạn có thể lập luận rằng việc không cho phép lưu trữ những thứ ăn cắp tài nguyên là một điều tốt. Mô hình vay mượn mà tôi đề xuất ở đây buộc bạn phải tinh gọn với việc sử dụng tài nguyên của bạn
pm100

Nó có thể là một điều tốt, nhưng nó cũng có thể làm cho một số hoạt động rất hợp lý rất khó khăn. Ví dụ: giả sử rằng loại trình đọc cơ sở dữ liệu, thay vì triển khai IEnumerable <T>, sẽ hiển thị một phương thức DoForAll(Action<T>) where T:IComparable<T>, gọi đại biểu được chỉ định trên mỗi bản ghi. Cho hai đối tượng như vậy, cả hai đối tượng sẽ trả về dữ liệu theo thứ tự được sắp xếp, làm thế nào một đầu ra tất cả các mục tồn tại trong một bộ sưu tập nhưng không phải là đối tượng khác? Nếu các loại được triển khai IEnumerable<T>, người ta có thể thực hiện thao tác hợp nhất, nhưng điều đó sẽ không hoạt động DoForAll.
supercat

Cách duy nhất tôi có thể tìm cách hợp nhất hai DoForAllbộ sưu tập mà không cần phải sao chép toàn bộ một bộ sưu tập vào một cấu trúc khác, là sử dụng hai luồng, sẽ khá tốn tài nguyên hơn là chỉ sử dụng một vài IEnumerable và cẩn thận để phát hành chúng.
supercat

-1: câu trả lời tốt cho một câu hỏi không được hỏi. Đây sẽ là một câu trả lời tuyệt vời cho "làm thế nào để tôi tiêu thụ các đối tượng IDis Dùng dễ dàng hơn"
John Saunders

10

không ai trả lời câu hỏi về việc bạn có nên triển khai IDis Dùng hay không mặc dù bạn không cần nó.

Câu trả lời ngắn gọn: Không

Câu trả lời dài:

Điều này sẽ cho phép người tiêu dùng của lớp bạn sử dụng 'bằng cách sử dụng'. Câu hỏi tôi sẽ hỏi là - tại sao họ sẽ làm điều đó? Hầu hết các nhà phát triển sẽ không sử dụng 'bằng cách sử dụng' trừ khi họ biết rằng họ phải - và làm thế nào để họ biết. Hoặc

  • obviuos của họ từ kinh nghiệm (ví dụ một lớp socket)
  • tài liệu của nó
  • họ thận trọng và có thể thấy rằng lớp thực hiện IDis Dùng một lần

Vì vậy, bằng cách triển khai IDis Dùng một lần, bạn đang nói với các nhà phát triển (ít nhất là một số) rằng lớp này kết thúc một cái gì đó phải được phát hành. Họ sẽ sử dụng 'bằng cách sử dụng' - nhưng có những trường hợp khác không thể sử dụng (phạm vi của đối tượng không phải là cục bộ); và họ sẽ phải bắt đầu lo lắng về tuổi thọ của các vật thể trong những trường hợp khác - tôi chắc chắn sẽ lo lắng. Nhưng điều này là không cần thiết

Bạn triển khai Idis Dùng để cho phép họ sử dụng, nhưng họ sẽ không sử dụng trừ khi bạn bảo họ sử dụng.

Vì vậy, đừng làm điều đó


1
Tôi không hiểu tại sao một nhà phát triển sẽ không sử dụng / sử dụng trên một đối tượng triển khai IDis Dùng (trừ khi chương trình sắp thoát ra bằng mọi cách).
adrianm

1
vấn đề là một nhà phát triển sẽ phải viết tất cả các cuộc gọi để xử lý trong tất cả các đường dẫn mã dẫn đến việc không thông báo về nó. Ví dụ, nếu tôi đặt một thể hiện trong Từ điển, khi tôi xóa các mục khỏi từ điển, tôi phải gọi vứt bỏ. Rất nhiều rắc rối không cần thiết trong trường hợp này - đối tượng không cần phải xử lý
pm100

3
Re: Không cần triển khai IDis Dùng một lần - Có bài viết chi tiết tại codeproject.com/KB/dotnet/idis Dùng.aspx thảo luận về một số trường hợp hiếm khi bạn có thể muốn nghĩ về điều này (rất hiếm, tôi chắc chắn). Tóm lại: Nếu bạn có thể thấy trước nhu cầu về IDis Dùng trong tương lai hoặc trong một đối tượng dẫn xuất, thì bạn có thể nghĩ đến việc triển khai IDis Dùng như một "no-op" trong lớp cơ sở của mình để tránh các vấn đề "cắt" trong đó một số đối tượng dẫn xuất yêu cầu xử lý và những người khác không.
Kevin P. Rice

4
  1. Nếu bạn đang sử dụng các đối tượng được quản lý khác đang sử dụng các tài nguyên không được quản lý, bạn không có trách nhiệm đảm bảo những đối tượng đó được hoàn thành. Trách nhiệm của bạn là gọi Vứt bỏ trên các đối tượng đó khi Vứt bỏ được gọi trên đối tượng của bạn và nó dừng ở đó.

  2. Nếu lớp của bạn không sử dụng bất kỳ tài nguyên khan hiếm nào, tôi không biết lý do tại sao bạn sẽ khiến lớp của mình triển khai IDis Dùng một lần. Bạn chỉ nên làm như vậy nếu bạn:

    • Biết rằng bạn sẽ sớm có tài nguyên trong các đối tượng của mình, nhưng không phải bây giờ (và ý tôi là như trong "chúng tôi vẫn đang phát triển, nó sẽ ở đây trước khi chúng tôi hoàn thành", chứ không phải trong "Tôi nghĩ chúng tôi sẽ cần điều này ")
    • Sử dụng tài nguyên khan hiếm
  3. Có, mã sử dụng mã của bạn phải gọi phương thức Vứt bỏ đối tượng của bạn. Và vâng, mã sử dụng đối tượng của bạn có thể sử dụng usingnhư bạn đã hiển thị.

  4. (2 lần nữa?) Có khả năng WebClient sử dụng tài nguyên không được quản lý hoặc các tài nguyên được quản lý khác triển khai IDis Dùng một lần. Lý do chính xác, tuy nhiên, không quan trọng. Điều quan trọng là nó triển khai IDis Dùng một lần và do đó, bạn phải hành động theo kiến ​​thức đó bằng cách xử lý đối tượng khi bạn thực hiện với nó, ngay cả khi WebClient không sử dụng tài nguyên nào khác.


4

Mẫu bỏ:

public abstract class DisposableObject : IDisposable
{
    public bool Disposed { get; private set;}      

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~DisposableObject()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
        if (!Disposed)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            DisposeUnmanagedResources();
            Disposed = true;
        }
    }

    protected virtual void DisposeManagedResources() { }
    protected virtual void DisposeUnmanagedResources() { }
}

Ví dụ về thừa kế:

public class A : DisposableObject
{
    public Component components_a { get; set; }
    private IntPtr handle_a;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeManagedResources");
          components_a.Dispose();
          components_a = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("A_DisposeUnmanagedResources");
          CloseHandle(handle_a);
          handle_a = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

public class B : A
{
    public Component components_b { get; set; }
    private IntPtr handle_b;

    protected override void DisposeManagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeManagedResources");
          components_b.Dispose();
          components_b = null;
        }
        finally
        { 
          base.DisposeManagedResources();
        }
    }

    protected override void DisposeUnmanagedResources()
    {
        try
        {
          Console.WriteLine("B_DisposeUnmanagedResources");
          CloseHandle(handle_b);
          handle_b = IntPtr.Zero;
        }
        finally
        { 
          base.DisposeUnmanagedResources();
        }
    }
}

4

Một số khía cạnh của một câu trả lời khác là hơi không chính xác vì 2 lý do:

Đầu tiên,

using(NoGateway objNoGateway = new NoGateway())

thực sự tương đương với:

try
{
    NoGateway = new NoGateway();
}
finally
{
    if(NoGateway != null)
    {
        NoGateway.Dispose();
    }
}

Điều này nghe có vẻ vô lý vì toán tử 'mới' sẽ không bao giờ trả về 'null' trừ khi bạn có ngoại lệ OutOfMemory. Nhưng hãy xem xét các trường hợp sau: 1. Bạn gọi FactoryClass trả về tài nguyên IDis Dùng một lần hoặc 2. Nếu bạn có một loại có thể hoặc không thể thừa kế từ IDis Dùng tùy thuộc vào cách triển khai của nó - hãy nhớ rằng tôi đã thấy mẫu IDis Dùng được triển khai không chính xác nhiều lần tại nhiều khách hàng nơi các nhà phát triển chỉ thêm phương thức Dispose () mà không kế thừa từ IDis Dùng (xấu, xấu, xấu). Bạn cũng có thể có trường hợp tài nguyên IDis Dùng được trả về từ một thuộc tính hoặc phương thức (một lần nữa là xấu, xấu, xấu - đừng 'cho đi tài nguyên IDis Dùng của bạn)

using(IDisposable objNoGateway = new NoGateway() as IDisposable)
{
    if (NoGateway != null)
    {
        ...

Nếu toán tử 'as' trả về null (hoặc thuộc tính hoặc phương thức trả lại tài nguyên) và mã của bạn trong khối 'bằng cách sử dụng' bảo vệ chống lại 'null', mã của bạn sẽ không nổ tung khi cố gắng gọi Vứt bỏ trên một đối tượng null vì kiểm tra null 'tích hợp'.

Lý do thứ hai câu trả lời của bạn không chính xác là vì stmt sau:

Một bộ hoàn thiện được yêu cầu khi GC phá hủy đối tượng của bạn

Đầu tiên, Finalization (cũng như chính GC) là không xác định. CLR xác định khi nào nó sẽ gọi một bộ hoàn thiện. tức là nhà phát triển / mã không có ý tưởng. Nếu mẫu IDis Dùng được triển khai chính xác (như tôi đã đăng ở trên) và GC.SuppressFinalize () đã được gọi, Trình hoàn thiện sẽ KHÔNG được gọi. Đây là một trong những lý do lớn để thực hiện đúng mẫu. Vì chỉ có 1 luồng Finalizer cho mỗi quy trình được quản lý, bất kể số lượng bộ xử lý logic, bạn có thể dễ dàng làm giảm hiệu suất bằng cách sao lưu hoặc thậm chí treo luồng Finalizer bằng cách quên gọi GC.SuppressFinalize ().

Tôi đã đăng một triển khai chính xác Mẫu vứt bỏ trên blog của mình: Cách triển khai đúng cách Loại bỏ mẫu


2
Bạn có chắc chắn về văn bản NoGateway = new NoGateway();NoGateway != null?
Cœur

1
Đây có phải là đề cập đến stackoverflow.com/a/898856/3195477 ? Hiện tại không có câu trả lời nào được đăng với tên 'Icey'
UuDdLrLrSs

@DaveInCaz có vẻ như đó là chính xác. Tôi không thấy 'Icey' ở bất cứ đâu nhưng bối cảnh phản hồi của tôi dường như được hướng vào câu trả lời được cung cấp bởi liên kết của bạn ở trên. Có lẽ anh ấy đã thay đổi tên người dùng của mình?
Dave Black

@DaveBlack mát mẻ, cảm ơn. Tôi vừa mới chỉnh sửa nó ngay vào văn bản.
UuDdLrLrSs

2

1) WebClient là loại được quản lý, vì vậy bạn không cần bộ hoàn thiện. Bộ hoàn thiện là cần thiết trong trường hợp người dùng của bạn không vứt bỏ () lớp NoGateway của bạn và loại bản địa (không được thu thập bởi GC) cần phải được dọn sạch sau. Trong trường hợp này, nếu người dùng không gọi Dispose (), WebClient có chứa sẽ được xử lý bởi GC ngay sau khi NoGateway thực hiện.

2) Một cách gián tiếp là có, nhưng bạn không cần phải lo lắng về điều đó. Mã của bạn là chính xác và bạn không thể ngăn người dùng quên việc Vứt bỏ () một cách dễ dàng.


2

Mẫu từ msd

public class BaseResource: IDisposable
{
   private IntPtr handle;
   private Component Components;
   private bool disposed = false;
   public BaseResource()
   {
   }
   public void Dispose()
   {
      Dispose(true);      
      GC.SuppressFinalize(this);
   }
   protected virtual void Dispose(bool disposing)
   {
      if(!this.disposed)
      {        
         if(disposing)
         {
            Components.Dispose();
         }         
         CloseHandle(handle);
         handle = IntPtr.Zero;
       }
      disposed = true;         
   }
   ~BaseResource()      
   {      Dispose(false);
   }
   public void DoSomething()
   {
      if(this.disposed)
      {
         throw new ObjectDisposedException();
      }
   }
}
public class MyResourceWrapper: BaseResource
{
   private ManagedResource addedManaged;
   private NativeResource addedNative;
   private bool disposed = false;
   public MyResourceWrapper()
   {
   }
   protected override void Dispose(bool disposing)
   {
      if(!this.disposed)
      {
         try
         {
            if(disposing)
            {             
               addedManaged.Dispose();         
            }
            CloseHandle(addedNative);
            this.disposed = true;
         }
         finally
         {
            base.Dispose(disposing);
         }
      }
   }
}

1
using(NoGateway objNoGateway = new NoGateway())

tương đương với

try
{
    NoGateway = new NoGateway();
}

finally
{
    NoGateway.Dispose();
}

Một bộ hoàn thiện được yêu cầu khi GC phá hủy đối tượng của bạn. Điều này có thể ở một thời điểm hoàn toàn khác so với khi bạn rời khỏi phương pháp của mình. Vứt bỏ IDis Dùng được gọi ngay sau khi bạn rời khỏi khối sử dụng. Do đó, mô hình thường được sử dụng để sử dụng các nguồn tài nguyên miễn phí ngay lập tức sau khi bạn không cần chúng nữa.


1
Một bộ hoàn thiện không được yêu cầu khi GC phá hủy đối tượng. Nếu "Hoàn thiện" bị ghi đè, thì khi đó, nếu không thì sẽ phá hủy đối tượng , nó sẽ được đặt trên hàng đợi các đối tượng cần hoàn thiện, tạm thời tạo một tham chiếu mạnh đến nó và - ít nhất là tạm thời - "hồi sinh" nó.
supercat

-5

Từ những gì tôi biết, chúng tôi khuyên bạn KHÔNG nên sử dụng Finalizer / Destructor:

public ~MyClass() {
  //dont use this
}

Hầu hết, điều này là do không biết khi nào hoặc NẾU nó sẽ được gọi. Phương pháp vứt bỏ tốt hơn nhiều, đặc biệt nếu bạn sử dụng hoặc thải bỏ trực tiếp.

sử dụng là tốt. sử dụng nó :)


2
Bạn nên theo liên kết trong câu trả lời của thecoop. Có, sử dụng / Vứt bỏ là tốt hơn nhưng một lớp dùng một lần chắc chắn nên thực hiện cả hai.
Henk Holterman

Thật thú vị, tất cả các tài liệu tôi đã đọc từ microsoft - ví dụ: hướng dẫn thiết kế khung - nói rằng KHÔNG BAO GIỜ sử dụng hàm hủy. Luôn luôn sử dụng IDis Dùng.
Nic Wise

5
Chỉ cần phân biệt giữa sử dụng một lớp và viết lớp, đọc lại chúng.
Henk Holterman

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.