CA2202, cách giải quyết trường hợp này


102

Ai có thể cho tôi biết làm thế nào để loại bỏ tất cả các cảnh báo CA2202 khỏi mã sau đây?

public static byte[] Encrypt(string data, byte[] key, byte[] iv)
{
    using(MemoryStream memoryStream = new MemoryStream())
    {
        using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
            {
                using(StreamWriter streamWriter = new StreamWriter(cryptoStream))
                {
                    streamWriter.Write(data);
                }
            }
        }
        return memoryStream.ToArray();
    }
}

Cảnh báo 7 CA2202: Microsoft.Cách sử dụng: Đối tượng 'cryptoStream' có thể được xử lý nhiều lần trong phương thức 'CryptoServices.Encrypt (string, byte [], byte [])'. Để tránh tạo ra một System.ObjectDisposedException, bạn không nên gọi Dispose nhiều lần trên một đối tượng.: Lines: 34

Cảnh báo 8 CA2202: Microsoft.Cách sử dụng: Đối tượng 'memoryStream' có thể được xử lý nhiều lần trong phương thức 'CryptoServices.Encrypt (string, byte [], byte [])'. Để tránh tạo ra một System.ObjectDisposedException, bạn không nên gọi Dispose nhiều lần trên một đối tượng.: Lines: 34, 37

Bạn cần Phân tích mã Visual Studio để xem các cảnh báo này (đây không phải là cảnh báo trình biên dịch c #).


1
Mã này không tạo ra những cảnh báo này.
Julien Hoarau

1
Tôi nhận được 0 cảnh báo cho điều này (Cảnh báo cấp 4, VS2010). Và đối với ai đó gặp vấn đề về Google trong lĩnh vực này, vui lòng thêm văn bản cảnh báo.
Henk Holterman

29
Cảnh báo CAxxxx được tạo bởi Phân tích mã và FxCop.
dtb

Cảnh báo này không áp dụng cho mã được hiển thị - cảnh báo có thể bị loại bỏ cho chính xác trường hợp này. Khi bạn đã xem lại mã của mình và đồng ý với đánh giá đó, hãy đặt điều này bên trên phương pháp của bạn: " [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification="BrainSlugs83 said so.")]" - đảm bảo rằng bạn có using System.Diagnostics.CodeAnalysis;câu lệnh "" trong khối usings của mình.
BrainSlugs83

Câu trả lời:


-3

Điều này biên dịch mà không có cảnh báo:

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;
        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            var result = memoryStream;              
            memoryStream = null;
            streamWriter = new StreamWriter(cryptoStream);
            cryptoStream = null;
            streamWriter.Write(data);
            return result.ToArray();
        }
        finally
        {
            if (memoryStream != null)
                memoryStream.Dispose();
            if (cryptograph != null)
                cryptograph.Dispose();
            if (cryptoStream != null)
                cryptoStream.Dispose();
            if (streamWriter != null)
                streamWriter.Dispose();
        }
    }

Chỉnh sửa để trả lời các nhận xét: Tôi vừa xác minh lại rằng mã này không tạo ra cảnh báo, trong khi mã gốc thì có. Trong mã gốc, CryptoStream.Dispose()MemoryStream().Dispose() thực sự được gọi hai lần (có thể có hoặc không có vấn đề).

Mã sửa đổi hoạt động như sau: các tham chiếu được đặt thành null, ngay khi trách nhiệm xử lý được chuyển sang một đối tượng khác. Ví dụ: memoryStreamđược đặt thành nullsau khi gọi hàm CryptoStreamtạo thành công. cryptoStreamđược đặt thành null, sau khi lệnh gọi hàm StreamWritertạo thành công. Nếu không có ngoại lệ nào xảy ra, streamWriterđược xử lý trong finallykhối và sẽ lần lượt loại bỏ CryptoStreamMemoryStream.


85
-1 Thực sự tồi tệ khi tạo mã xấu chỉ để tuân theo một cảnh báo cần bị loại bỏ .
Jordão

4
Tôi đồng ý rằng bạn không nên bán mã cho một thứ gì đó mà cuối cùng có thể được khắc phục vào một thời điểm nào đó trong tương lai, chỉ cần ngăn chặn.
peteski

3
Làm thế nào để giải quyết vấn đề này? CA2202 vẫn được báo cáo vì memoryStream vẫn có thể được xử lý hai lần trong khối cuối cùng.
Chris Gessler

3
Vì CryptoStream gọi Dispose trên MemoryStream bên trong, nó có thể được gọi hai lần, đó là lý do cho cảnh báo. Tôi đã thử giải pháp của bạn và vẫn nhận được cảnh báo.
Chris Gessler

2
Ôi trời, bạn nói đúng - Tôi không ngờ rằng sẽ có logic dọn dẹp xen lẫn với ... logic-logic của bạn ... - điều đó thật kỳ quái và khó hiểu - nó chắc chắn rất thông minh - nhưng một lần nữa, đáng sợ - vui lòng không làm điều này trong mã sản xuất; để rõ ràng: bạn hiểu rằng không có vấn đề chức năng thực tế nào mà điều này đang giải quyết, đúng không? (Không sao để loại bỏ những đồ vật này nhiều lần.) - Tôi sẽ loại bỏ phiếu phản đối nếu tôi có thể (SO ngăn cản tôi, nó nói rằng bạn phải chỉnh sửa câu trả lời) - nhưng tôi chỉ làm vậy một cách miễn cưỡng ... - và nghiêm túc, đừng bao giờ làm điều này.
BrainSlugs83

142

Bạn nên ngăn chặn các cảnh báo trong trường hợp này. Mã xử lý đồ dùng một lần phải nhất quán và bạn không cần phải quan tâm đến việc các lớp khác có quyền sở hữu đồ dùng một lần mà bạn đã tạo và cũng gọi Disposechúng.

[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
public static byte[] Encrypt(string data, byte[] key, byte[] iv) {
  using (var memoryStream = new MemoryStream()) {
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream)) {
      streamWriter.Write(data);
    }
    return memoryStream.ToArray();
  }
}

CẬP NHẬT: Trong tài liệu IDisposable.Dispose, bạn có thể đọc phần này:

Nếu phương thức Dispose của một đối tượng được gọi nhiều hơn một lần, thì đối tượng đó phải bỏ qua tất cả các lệnh gọi sau phương thức đầu tiên. Đối tượng không được ném một ngoại lệ nếu phương thức Dispose của nó được gọi nhiều lần.

Có thể lập luận rằng quy tắc này tồn tại để các nhà phát triển có thể sử dụng usingcâu lệnh một cách lành mạnh trong một loạt các đồ dùng một lần, như tôi đã trình bày ở trên (hoặc có thể đây chỉ là một hiệu ứng phụ tốt đẹp). Do đó, theo cùng một mã thông báo, CA2202 không phục vụ mục đích hữu ích nào và nó sẽ bị loại bỏ theo dự án. Thủ phạm thực sự sẽ là việc triển khai bị lỗi DisposeCA1065 sẽ giải quyết vấn đề đó (nếu nó thuộc trách nhiệm của bạn).


14
Theo tôi đây là một lỗi trong fxcop, quy tắc này đơn giản là sai. Phương thức xử lý không bao giờ được ném ra một ObjectDisposedException và nếu nó xảy ra thì bạn nên xử lý nó vào thời điểm đó bằng cách gửi một lỗi về tác giả của mã thực hiện vứt bỏ theo cách này.
justin.m.chase

14
Tôi đồng ý với @HansPassant trong luồng khác: công cụ đang thực hiện công việc của nó và cảnh báo bạn về chi tiết triển khai không mong muốn của các lớp. Cá nhân tôi nghĩ rằng vấn đề thực sự là thiết kế của chính các API. Việc các lớp lồng nhau mặc định cho rằng có thể sở hữu một đối tượng khác được tạo ở nơi khác có vẻ rất đáng nghi ngờ. Tôi có thể thấy điều đó có thể hữu ích ở đâu nếu đối tượng kết quả sẽ được trả lại, nhưng mặc định cho giả định đó có vẻ phản trực quan, cũng như vi phạm các mẫu IDisposable thông thường.
BTJ

8
Nhưng msdn không khuyến khích Supress loại tin nhắn này. Hãy xem tại: msdn.microsoft.com/en-us/library/…
Adil Mammadov

2
Cảm ơn liên kết @AdilMammadov, thông tin hữu ích nhưng microsoft không phải lúc nào cũng đúng về những điều này.
Tim Abell

40

Chà, chính xác là, phương thức Dispose () trên các luồng này sẽ được gọi nhiều lần. Lớp StreamReader sẽ có 'quyền sở hữu' của cryptoStream, do đó, việc định đoạt streamWriter cũng sẽ định đoạt cryptoStream. Tương tự như vậy, lớp CryptoStream đảm nhận trách nhiệm cho memoryStream.

Đây không phải là lỗi thực sự, các lớp .NET này có khả năng chống lại nhiều lệnh gọi Dispose (). Nhưng nếu bạn muốn loại bỏ cảnh báo thì bạn nên bỏ câu lệnh using cho các đối tượng này. Và tự làm đau bản thân một chút khi lý luận điều gì sẽ xảy ra nếu mã ném một ngoại lệ. Hoặc tắt cảnh báo bằng một thuộc tính. Hoặc chỉ phớt lờ cảnh báo vì nó thật ngớ ngẩn.


10
Việc phải có kiến ​​thức đặc biệt về hành vi bên trong của các lớp (như một lớp dùng một lần chiếm quyền sở hữu lớp khác) là quá nhiều để đặt câu hỏi nếu một người muốn thiết kế một API có thể tái sử dụng. Vì vậy, tôi nghĩ rằng các usingtuyên bố nên ở lại. Những cảnh báo này thực sự ngớ ngẩn.
Jordão

4
@ Jordão - đó không phải là công cụ dùng để làm gì? Để cảnh báo bạn về hành vi nội bộ mà bạn có thể chưa biết?
Hans Passant

8
Tôi đồng ý. Nhưng, tôi vẫn sẽ không bỏ các usingtuyên bố. Chỉ cảm thấy sai khi dựa vào một đối tượng khác để định đoạt một đối tượng mà tôi đã tạo ra. Đối với mã này, nó là ổn, nhưng cũng có khá nhiều triển khai của StreamTextWriterngoài kia (không chỉ trên BCL). Mã để sử dụng tất cả chúng phải nhất quán.
Jordão

3
Vâng, đồng ý với Jordão. Nếu bạn thực sự muốn lập trình viên biết được hành vi bên trong của api, hãy nói ra bằng cách đặt tên cho hàm của bạn là DoSomethingAndDisposeStream (Luồng luồng, dữ liệu OtherData).
ZZZ

4
@HansPassant Bạn có thể chỉ ra nơi được ghi lại rằng XmlDocument.Save()phương thức sẽ gọi Disposetrên tham số được cung cấp không? tôi không thấy nó trong tài liệu của Save(XmlWriter)(nơi tôi đang gặp phải lỗi FxCop), hoặc trong Save()chính phương pháp hoặc trong tài liệu của XmlDocumentchính nó.
Ian Boyd

9

Khi một StreamWriter được xử lý, nó sẽ tự động loại bỏ Luồng được bọc (ở đây: CryptoStream ). CryptoStream cũng tự động disposes các bọc Suối (ở đây: các MemoryStream ).

Vì vậy, MemoryStream của bạn được xử lý bởi CryptoStream và câu lệnh using . Và CryptoStream của bạn được xử lý bởi StreamWriter và tuyên bố sử dụng bên ngoài .


Sau một số thử nghiệm, dường như không thể loại bỏ hoàn toàn các cảnh báo. Về mặt lý thuyết, MemoryStream cần được xử lý, nhưng về mặt lý thuyết thì bạn không thể truy cập vào phương thức ToArray của nó nữa. Thực tế, MemoryStream không cần phải xử lý, vì vậy tôi sẽ sử dụng giải pháp này và loại bỏ cảnh báo CA2000.

var memoryStream = new MemoryStream();

using (var cryptograph = new DESCryptoServiceProvider())
using (var writer = new StreamWriter(new CryptoStream(memoryStream, ...)))
{
    writer.Write(data);
}

return memoryStream.ToArray();

9

Tôi sẽ làm điều này bằng cách sử dụng #pragma warning disable.

Nguyên tắc .NET Framework khuyên bạn nên triển khai IDisposable.Dispose theo cách mà nó có thể được gọi nhiều lần. Từ mô tả MSDN của IDisposable . Mục đích:

Đối tượng không được ném ngoại lệ nếu phương thức Dispose của nó được gọi nhiều lần

Do đó, cảnh báo dường như gần như vô nghĩa:

Để tránh tạo ra một System.ObjectDisposedException, bạn không nên gọi Dispose nhiều lần trên một đối tượng

Tôi đoán có thể lập luận rằng cảnh báo có thể hữu ích nếu bạn đang sử dụng một đối tượng IDisposable được triển khai không tốt, không tuân theo các nguyên tắc triển khai tiêu chuẩn. Nhưng khi sử dụng các lớp từ .NET Framework như bạn đang làm, tôi muốn nói rằng sẽ an toàn để loại bỏ cảnh báo bằng cách sử dụng #pragma. Và IMHO điều này thích hợp hơn để vượt qua các vòng như được đề xuất trong tài liệu MSDN cho cảnh báo này .


4
CA2202 là cảnh báo Phân tích mã chứ không phải cảnh báo trình biên dịch. #pragma warning disablechỉ có thể được sử dụng để ngăn chặn cảnh báo trình biên dịch. Để loại bỏ cảnh báo Phân tích mã, bạn cần sử dụng một thuộc tính.
Martin Liversage

2

Tôi đã phải đối mặt với các vấn đề tương tự trong mã của mình.

Có vẻ như toàn bộ CA2202 được kích hoạt vì MemoryStreamcó thể được xử lý nếu ngoại lệ xảy ra trong hàm tạo (CA2000).

Điều này có thể được giải quyết như thế này:

 1 public static byte[] Encrypt(string data, byte[] key, byte[] iv)
 2 {
 3    MemoryStream memoryStream = GetMemoryStream();
 4    using (DESCryptoServiceProvider cryptograph = new DESCryptoServiceProvider())
 5    {
 6        CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
 7        using (StreamWriter streamWriter = new StreamWriter(cryptoStream))
 8        {
 9            streamWriter.Write(data);
10            return memoryStream.ToArray();
11        }
12    }
13 }
14
15 /// <summary>
16 /// Gets the memory stream.
17 /// </summary>
18 /// <returns>A new memory stream</returns>
19 private static MemoryStream GetMemoryStream()
20 {
21     MemoryStream stream;
22     MemoryStream tempStream = null;
23     try
24     {
25         tempStream = new MemoryStream();
26
27         stream = tempStream;
28         tempStream = null;
29     }
30     finally
31     {
32         if (tempStream != null)
33             tempStream.Dispose();
34     }
35     return stream;
36 }

Lưu ý rằng chúng ta phải trả về memoryStreambên trong usingcâu lệnh cuối cùng (dòng 10) vì cryptoStreamđược xử lý ở dòng 11 (vì nó được sử dụng trong streamWriter usingcâu lệnh), dẫn memoryStreamđến cũng được xử lý ở dòng 11 (vì memoryStreamđược sử dụng để tạo cryptoStream).

Ít nhất mã này đã làm việc cho tôi.

BIÊN TẬP:

Nghe có vẻ buồn cười, tôi phát hiện ra rằng nếu bạn thay thế GetMemoryStreamphương thức này bằng mã sau,

/// <summary>
/// Gets a memory stream.
/// </summary>
/// <returns>A new memory stream</returns>
private static MemoryStream GetMemoryStream()
{
    return new MemoryStream();
}

bạn nhận được cùng một kết quả.


1

Dòng tiền điện tử dựa trên dòng bộ nhớ.

Điều dường như đang xảy ra là khi dòng lạnh được xử lý (khi kết thúc sử dụng) thì dòng bộ nhớ cũng được xử lý, sau đó dòng bộ nhớ lại được xử lý.


1

Tôi muốn giải quyết vấn đề này theo cách đúng đắn - đó là không ngăn chặn các cảnh báo và xử lý đúng cách tất cả các đồ vật dùng một lần.

Tôi lấy ra 2 trong 3 luồng làm trường và xử lý chúng trong Dispose()phương thức của lớp tôi. Có, việc triển khai IDisposablegiao diện có thể không nhất thiết phải như những gì bạn đang tìm kiếm nhưng giải pháp trông khá rõ ràng so với dispose()các cuộc gọi từ tất cả các vị trí ngẫu nhiên trong mã.

public class SomeEncryption : IDisposable
    {
        private MemoryStream memoryStream;

        private CryptoStream cryptoStream;

        public static byte[] Encrypt(string data, byte[] key, byte[] iv)
        {
             // Do something
             this.memoryStream = new MemoryStream();
             this.cryptoStream = new CryptoStream(this.memoryStream, encryptor, CryptoStreamMode.Write);
             using (var streamWriter = new StreamWriter(this.cryptoStream))
             {
                 streamWriter.Write(plaintext);
             }
            return memoryStream.ToArray();
        }

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

       protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.memoryStream != null)
                {
                    this.memoryStream.Dispose();
                }

                if (this.cryptoStream != null)
                {
                    this.cryptoStream.Dispose();
                }
            }
        }
   }

0

Lạc đề nhưng tôi khuyên bạn nên sử dụng một kỹ thuật định dạng khác để nhóm usingcác:

using (var memoryStream = new MemoryStream())
{
    using (var cryptograph = new DESCryptoServiceProvider())
    using (var encryptor = cryptograph.CreateEncryptor(key, iv))
    using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
    using (var streamWriter = new StreamWriter(cryptoStream))
    {
        streamWriter.Write(data);
    }

    return memoryStream.ToArray();
}

Tôi cũng ủng hộ việc sử dụng vars ở đây để tránh lặp lại các tên lớp thực sự dài.

PS Cảm ơn @ShellShock đã chỉ ra rằng tôi không thể bỏ qua dấu ngoặc nhọn trước tiên usingvì nó sẽ đưa ra memoryStreamtrong returntuyên bố ngoài phạm vi.


5
MemoryStream.ToArray () sẽ không nằm ngoài phạm vi?
Polyfun

Điều này hoàn toàn tương đương với đoạn mã gốc. Tôi chỉ sử dụng dấu ngoặc nhọn không giới hạn, giống như bạn có thể làm như vậy với ifs (mặc dù tôi sẽ không tư vấn kỹ thuật này cho bất kỳ điều gì khác ngoài usings).
Dan Abramov

2
Trong mã gốc, memoryStream.ToArray () nằm trong phạm vi của lần sử dụng đầu tiên; bạn đã có nó bên ngoài phạm vi.
Polyfun

Cảm ơn bạn rất nhiều, tôi chỉ nhận ra rằng bạn có nghĩa là returntuyên bố. Đúng vậy. Tôi đã chỉnh sửa câu trả lời để phản ánh điều này.
Dan Abramov

Cá nhân tôi nghĩ rằng usingkhông có dấu ngoặc nhọn làm cho mã dễ hỏng hơn (nghĩ rằng nhiều năm khác nhau và hợp nhất). joelonsoftware.com/2005/05/11/making-wrong-code-look-wrong & Imperialviolet.org/2014/02/22/applebug.html
Tim Abell

0

Tránh tất cả việc sử dụng và sử dụng Cuộc gọi vứt bỏ lồng nhau!

    public static byte[] Encrypt(string data, byte[] key, byte[] iv)
    {
        MemoryStream memoryStream = null;
        DESCryptoServiceProvider cryptograph = null;
        CryptoStream cryptoStream = null;
        StreamWriter streamWriter = null;

        try
        {
            memoryStream = new MemoryStream();
            cryptograph = new DESCryptoServiceProvider();
            cryptoStream = new CryptoStream(memoryStream, cryptograph.CreateEncryptor(key, iv), CryptoStreamMode.Write);
            streamWriter = new StreamWriter(cryptoStream);

            streamWriter.Write(data);
            return memoryStream.ToArray();
        }
        finally 
        {
            if(streamWriter != null)
                streamWriter.Dispose();
            else if(cryptoStream != null)
                cryptoStream.Dispose();
            else if(memoryStream != null)
                memoryStream.Dispose();

            if (cryptograph != null)
                cryptograph.Dispose();
        }
    }

1
Hãy giải thích lý do tại sao bạn nên tránh usingtrong trường hợp này.
StuperUser 22/10/12

1
Bạn có thể giữ câu lệnh using ở giữa, nhưng bạn phải giải quyết những câu lệnh khác. Để có được một giải pháp mạch lạc hợp lý và có thể nâng cấp theo mọi hướng, tôi đã quyết định loại bỏ tất cả các mục đích sử dụng!
Harry Saltzman 22/10/12

0

Tôi chỉ muốn mở mã để chúng tôi có thể thấy nhiều cuộc gọi đến Dispose trên các đối tượng:

memoryStream = new MemoryStream()
cryptograph = new DESCryptoServiceProvider()
cryptoStream = new CryptoStream()
streamWriter = new StreamWriter()

memoryStream.Dispose(); //implicitly owned by cryptoStream
cryptoStream.Dispose(); //implicitly owned by streamWriter
streamWriter.Dispose(); //through a using

cryptoStream.Dispose(); //INVALID: second dispose through using
cryptograph.Dispose(); //through a using
memorySTream.Dipose(); //INVALID: second dispose through a using

return memoryStream.ToArray(); //INVALID: accessing disposed memoryStream

Mặc dù hầu hết các lớp .NET (hy vọng) có khả năng phục hồi chống lại sự nhầm lẫn của nhiều cuộc gọi đến .Dispose , nhưng không phải tất cả các lớp đều có khả năng phòng thủ trước sự lạm dụng của lập trình viên.

FX Cop biết điều này và cảnh báo bạn.

Bạn có một vài lựa chọn;

  • chỉ gọi Disposemột lần trên bất kỳ đối tượng nào; không sử dụngusing
  • tiếp tục gọi vứt bỏ hai lần và hy vọng mã không bị lỗi
  • ngăn chặn cảnh báo

-1

Tôi đã sử dụng loại mã này lấy byte [] và trả về byte [] mà không sử dụng luồng

public static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
{
  DES des = new DES();
  des.BlockSize = 128;
  des.Mode = CipherMode.CBC;
  des.Padding = PaddingMode.Zeros;
  des.IV = IV
  des.Key = key
  ICryptoTransform encryptor = des.CreateEncryptor();

  //and finaly operations on bytes[] insted of streams
  return encryptor.TransformFinalBlock(plaintextarray,0,plaintextarray.Length);
}

Bằng cách này, tất cả những gì bạn phải làm là chuyển đổi từ chuỗi sang byte [] bằng cách sử dụng các bảng mã.

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.