#if DEBUG so với có điều kiện (NGÀY DEBUG)


432

Cái nào tốt hơn để sử dụng, và tại sao, trong một dự án lớn:

#if DEBUG
    public void SetPrivateValue(int value)
    { ... }
#endif

hoặc là

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value)
{ ... }

18
Xem blog.msdn.com/b/ericlippert/archive/2009/09/10/ cho một số suy nghĩ về câu hỏi này.
Eric Lippert

2
bạn cũng có thể sử dụng cái này: if (Debugger.IsAttached) {...}
sofsntp

Lưu ý cho nhà phát triển Unity: DEBUG có nghĩa là trong trình chỉnh sửa hoặc trong các bản dựng phát triển. forum.unity.com/threads/ từ
KevinVictor

Câu trả lời:


578

Nó thực sự phụ thuộc vào những gì bạn sẽ làm:

  • #if DEBUG: Mã ở đây thậm chí sẽ không đạt được IL khi phát hành.
  • [Conditional("DEBUG")]: Mã này sẽ đến IL, tuy nhiên các lệnh gọi đến phương thức sẽ bị bỏ qua trừ khi DEBUG được đặt khi người gọi được biên dịch.

Cá nhân tôi sử dụng cả hai tùy thuộc vào tình huống:

Ví dụ có điều kiện ("DEBUG"): Tôi sử dụng điều này để tôi không phải quay lại và chỉnh sửa mã của mình sau khi phát hành, nhưng trong quá trình gỡ lỗi tôi muốn chắc chắn rằng mình đã không mắc lỗi chính tả nào. Hàm này kiểm tra xem tôi nhập tên thuộc tính một cách chính xác khi cố gắng sử dụng nó trong công cụ INotifyPropertyChanged của tôi.

[Conditional("DEBUG")]
[DebuggerStepThrough]
protected void VerifyPropertyName(String propertyName)
{
    if (TypeDescriptor.GetProperties(this)[propertyName] == null)
        Debug.Fail(String.Format("Invalid property name. Type: {0}, Name: {1}",
            GetType(), propertyName));
}

Bạn thực sự không muốn tạo một hàm bằng cách sử dụng #if DEBUGtrừ khi bạn sẵn sàng kết thúc mọi cuộc gọi đến hàm đó bằng #if DEBUG:

#if DEBUG
    public void DoSomething() { }
#endif

    public void Foo()
    {
#if DEBUG
        DoSomething(); //This works, but looks FUGLY
#endif
    }

đấu với:

[Conditional("DEBUG")]
public void DoSomething() { }

public void Foo()
{
    DoSomething(); //Code compiles and is cleaner, DoSomething always
                   //exists, however this is only called during DEBUG.
}

Ví dụ #if DEBUG: Tôi sử dụng điều này khi cố gắng thiết lập các ràng buộc khác nhau cho giao tiếp WCF.

#if DEBUG
        public const String ENDPOINT = "Localhost";
#else
        public const String ENDPOINT = "BasicHttpBinding";
#endif

Trong ví dụ đầu tiên, tất cả mã đều tồn tại, nhưng chỉ bị bỏ qua trừ khi bật DEBUG. Trong ví dụ thứ hai, const ENDPOINT được đặt thành "Localhost" hoặc "BasicHttpBinding" tùy thuộc vào việc DEBUG có được đặt hay không.


Cập nhật: Tôi đang cập nhật câu trả lời này để làm rõ một điểm quan trọng và khó khăn. Nếu bạn chọn sử dụng ConditionalAttribute, hãy nhớ rằng các cuộc gọi bị bỏ qua trong quá trình biên dịch và không chạy . Đó là:

MyL Library.dll

[Conditional("DEBUG")]
public void A()
{
    Console.WriteLine("A");
    B();
}

[Conditional("DEBUG")]
public void B()
{
    Console.WriteLine("B");
}

Khi thư viện được biên dịch theo chế độ phát hành (nghĩa là không có ký hiệu DEBUG), nó sẽ mãi mãi có cuộc gọi đến B()từ bên trong A()bị bỏ qua, ngay cả khi một cuộc gọi đến A()được bao gồm vì DEBUG được xác định trong cụm gọi.


13
Gỡ lỗi #if cho DoS Something không cần phải có tất cả các câu lệnh gọi được bao quanh bởi #if DEBUG. bạn có thể 1: chỉ #if DEBUG bên trong DoS Something, hoặc, thực hiện #else với định nghĩa trống của DoS Something. Tuy nhiên, nhận xét của bạn đã giúp tôi hiểu được sự khác biệt, nhưng #if DEBUG không cần phải xấu xí như bạn đã chứng minh.
Apeiron

3
Nếu bạn chỉ #if DEBUG nội dung, JIT vẫn có thể bao gồm lệnh gọi hàm khi mã của bạn chạy trong bản dựng không gỡ lỗi. Sử dụng thuộc tính có điều kiện có nghĩa là JIT biết thậm chí không xuất ra các cuộc gọi khi ở trong bản dựng không DEBUG.
Jeff Yates

2
@JeffYates: Tôi không thấy những gì bạn đang viết khác với những gì tôi đã giải thích.
của tôi

1
@Apeiron nếu bạn chỉ có nội dung hàm trong #if gỡ lỗi thì lệnh gọi hàm vẫn được thêm vào ngăn xếp cuộc gọi, trong khi điều này thường không quan trọng lắm, thêm khai báo và lệnh gọi hàm vào #if có nghĩa là trình biên dịch hoạt động như nếu hàm không tồn tại, vì vậy phương thức của tôi là cách sử dụng #if chính xác hơn. mặc dù cả hai phương pháp đều tạo ra kết quả không thể phân biệt được với nhau trong sử dụng bình thường
MikeT

5
nếu có ai thắc mắc, IL = Ngôn ngữ trung gian - vi.wikipedia.org/wiki/Common_Inter
liền_Lingu

64

Chà, đáng chú ý là chúng không có nghĩa giống nhau chút nào.

Nếu biểu tượng DEBUG không được xác định, thì trong trường hợp đầu tiên, SetPrivateValuechính nó sẽ không được gọi ... trong trường hợp thứ hai nó sẽ tồn tại, nhưng bất kỳ người gọi nào được biên dịch mà không có biểu tượng DEBUG sẽ bị bỏ qua các cuộc gọi đó.

Nếu mã và tất cả các trình gọi của nó nằm trong cùng một cụm thì sự khác biệt này ít quan trọng hơn - nhưng điều đó có nghĩa là trong trường hợp đầu tiên, bạn cũng cần phải có #if DEBUGxung quanh mã gọi .

Cá nhân tôi muốn giới thiệu cách tiếp cận thứ hai - nhưng bạn cần phải giữ sự khác biệt giữa chúng rõ ràng trong đầu.


5
+1 để gọi mã cũng sẽ cần có các câu lệnh #if. Điều đó có nghĩa là sẽ có sự phát triển của các câu lệnh #if ...
Lucas B

Mặc dù tùy chọn thứ hai (thuộc tính có điều kiện) đẹp hơn và sạch hơn trong một số trường hợp, có thể cần truyền đạt thực tế rằng một lệnh gọi phương thức sẽ bị xóa khỏi cụm trong quá trình biên dịch (ví dụ, theo quy ước đặt tên).
axit lysergic-

45

Tôi chắc chắn sẽ không đồng ý với tôi, nhưng đã dành thời gian làm nhân viên xây dựng liên tục nghe "Nhưng nó hoạt động trên máy của tôi!", Tôi đưa ra quan điểm rằng bạn cũng không bao giờ nên sử dụng. Nếu bạn thực sự cần một cái gì đó để thử nghiệm và gỡ lỗi, hãy tìm ra một cách để làm cho khả năng kiểm tra đó tách biệt khỏi mã sản xuất thực tế.

Tóm tắt các kịch bản với chế độ thử nghiệm đơn vị, tạo một phiên bản của mọi thứ cho một kịch bản bạn muốn kiểm tra, nhưng đừng đặt thử nghiệm để gỡ lỗi vào mã cho các nhị phân mà bạn kiểm tra và viết để phát hành sản xuất. Các thử nghiệm gỡ lỗi này chỉ ẩn các lỗi có thể có từ các nhà phát triển để chúng không được tìm thấy cho đến sau này trong quy trình.


4
Tôi hoàn toàn đồng ý với bạn Jimmy. Nếu bạn đang sử dụng DI và chế nhạo cho các thử nghiệm của mình, tại sao bạn cần #if debughoặc bất kỳ cấu trúc tương tự nào trong mã của bạn?
Richard Ev

@RichardEv Có thể có một cách tốt hơn để xử lý việc này, nhưng tôi hiện đang sử dụng nó để cho phép bản thân mình chơi một phần của những người dùng khác nhau thông qua chuỗi truy vấn. Tôi không muốn điều này trong sản xuất nhưng tôi muốn nó để gỡ lỗi để tôi có thể kiểm soát quy trình công việc được thực hiện mà không phải tạo nhiều người dùng và đăng nhập vào cả hai tài khoản để đi qua luồng. Mặc dù đây là lần đầu tiên tôi thực sự phải sử dụng nó.
Tony

4
Thay vì chỉ để kiểm tra, chúng tôi thường làm những việc như đặt email người nhận mặc định cho chính mình, trong các bản dựng gỡ lỗi, sử dụng #if DEBUGđể chúng tôi không vô tình spam người khác trong khi kiểm tra một hệ thống phải truyền email như một phần của quy trình. Đôi khi đây là những công cụ phù hợp cho công việc :)
Mã hóa

6
Tôi thường đồng ý với bạn nhưng nếu bạn ở trong tình huống hiệu suất là tối quan trọng thì bạn không muốn làm lộn xộn mã với ghi nhật ký và đầu ra của người dùng, nhưng tôi đồng ý 100% rằng họ không nên sử dụng để thay đổi hành vi cơ bản
MikeT

5
-1 Không có gì sai khi sử dụng một trong hai. Yêu cầu kiểm tra đơn vị và DI bằng cách nào đó thay thế bản dựng cho phép gỡ lỗi của sản phẩm là ngây thơ.
Ted Bigham

15

Điều này cũng có thể hữu ích:

if (Debugger.IsAttached)
{
...
}

1
Cá nhân, tôi không thấy làm thế nào điều này có thể hữu ích so với 2 lựa chọn thay thế khác. Điều này đảm bảo rằng toàn bộ khối được biên dịch và Debugger.IsAttachedphải được gọi trong thời gian chạy ngay cả trong các bản dựng phát hành.
Jai

9

Với ví dụ đầu tiên, SetPrivateValuesẽ không tồn tại trong xây dựng nếu DEBUGkhông được định nghĩa, với ví dụ thứ hai, gọi đến SetPrivateValuesẽ không tồn tại trong xây dựng nếuDEBUG không được định nghĩa.

Với ví dụ đầu tiên, bạn sẽ phải thực hiện bất kỳ cuộc gọi nào SetPrivateValuevới#if DEBUG là tốt.

Với ví dụ thứ hai, các cuộc gọi đến SetPrivateValuesẽ bị bỏ qua, nhưng lưu ý rằngSetPrivateValue chính nó vẫn sẽ được biên dịch. Điều này hữu ích nếu bạn đang xây dựng thư viện, vì vậy một ứng dụng tham chiếu thư viện của bạn vẫn có thể sử dụng chức năng của bạn (nếu điều kiện được đáp ứng).

Nếu bạn muốn bỏ qua các cuộc gọi và tiết kiệm không gian của callee, bạn có thể sử dụng kết hợp cả hai kỹ thuật:

[System.Diagnostics.Conditional("DEBUG")]
public void SetPrivateValue(int value){
    #if DEBUG
    // method body here
    #endif
}

@P Daddy: Gói #if DEBUGxung quanh Conditional("DEBUG")không loại bỏ các cuộc gọi đến chức năng đó, nó chỉ loại bỏ hoàn toàn chức năng khỏi IL, do đó bạn vẫn có các lệnh gọi đến chức năng không tồn tại (lỗi biên dịch).
của tôi

1
Nếu một người không muốn mã tồn tại trong bản phát hành, thì nên bọc phần thân phương thức trong "#if DEBUG", có thể với sơ khai "#else" (với giá trị trả về ném hoặc giả) và sử dụng thuộc tính để đề xuất mà người gọi không bận tâm với cuộc gọi? Điều đó có vẻ tốt nhất của cả hai thế giới.
supercat

@myermian, @supercat: Vâng, bạn đều đúng. Lỗi của tôi. Tôi sẽ chỉnh sửa theo đề xuất của supercat.
P Daddy

5

Giả sử mã của bạn cũng có một #elsecâu lệnh xác định hàm null, giải quyết một trong những điểm của Jon Skeet. Có một sự khác biệt quan trọng thứ hai giữa hai.

Giả sử #if DEBUGhoặc Conditionalhàm tồn tại trong một DLL được tham chiếu bởi thực thi dự án chính của bạn. Sử dụng #if, việc đánh giá điều kiện sẽ được thực hiện liên quan đến cài đặt biên dịch của thư viện. Sử dụng Conditionalthuộc tính, việc đánh giá điều kiện sẽ được thực hiện liên quan đến các cài đặt biên dịch của invoker.


2

Tôi có tiện ích mở rộng SOAP WebService để ghi lưu lượng truy cập mạng bằng cách sử dụng tùy chỉnh [TraceExtension]. Tôi chỉ sử dụng điều này cho các bản dựng Debug và bỏ qua các bản dựng Phát hành . Sử dụng #if DEBUGđể bọc [TraceExtension]thuộc tính do đó loại bỏ nó khỏi các bản dựng phát hành .

#if DEBUG
[TraceExtension]
#endif
[System.Web.Service.Protocols.SoapDocumentMethodAttribute( ... )]
[ more attributes ...]
public DatabaseResponse[] GetDatabaseResponse( ...) 
{
    object[] results = this.Invoke("GetDatabaseResponse",new object[] {
          ... parmeters}};
}

#if DEBUG
[TraceExtension]
#endif
public System.IAsyncResult BeginGetDatabaseResponse(...)

#if DEBUG
[TraceExtension]
#endif
public DatabaseResponse[] EndGetDatabaseResponse(...)

0

Thông thường, bạn sẽ cần nó trong Program.cs nơi bạn muốn quyết định chạy Debug trên mã Non-Debug và điều đó chủ yếu là trong Windows Services. Vì vậy, tôi đã tạo một trường chỉ đọc IsDebugMode và đặt giá trị của nó trong hàm tạo tĩnh như dưới đây.

static class Program
{

    #region Private variable
    static readonly bool IsDebugMode = false;
    #endregion Private variable

    #region Constrcutors
    static Program()
    {
 #if DEBUG
        IsDebugMode = true;
 #endif
    }
    #endregion

    #region Main

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    static void Main(string[] args)
    {

        if (IsDebugMode)
        {
            MyService myService = new MyService(args);
            myService.OnDebug();             
        }
        else
        {
            ServiceBase[] services = new ServiceBase[] { new MyService (args) };
            services.Run(args);
        }
    }

    #endregion Main        
}
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.