Click here to Skip to main content
Click here to Skip to main content
Go to top

About #region preprocessor directive

, 29 Aug 2010
Rate this:
Please Sign up or sign in to vote.
This post talks about how #region directives are useful and when they are not appropriate.

I’m not going to introduce you to #region directive, it’s probably what every user of C# knows about and uses. What I want to discuss is their usage in your code. #region allows us to group some related code and then collapse it. Pretty. Now you don’t have to scan through a lot of code in a method that does something uninteresting, like:

public void DoSomething()
{
  bool shouldIDoSomething;
 
  #region Decide if I should do something
 
  if(needToDoSomething && haventDoneSomethingThisDay)
    shouldIDoSomething = true;
  else
  {
    // do some other logic to decide and set shouldIDoSomething to some value
  }
 
  #endregion
 
  if(shouldIDoSomething)
  {
    done++;
  }
}

That is over-simplified of course, in a real world, you can find a hundred lines and more in such region. Now, if we collapse it, it looks just neat, doesn’t it?

public void DoSomething()
{
  bool shouldIDoSomething;
 
  [Decide if I should do something]
 
  if(shouldIDoSomething)
  {
    done++;
  }
}

Okay, wait a minute now. You have just grouped some statements and named it properly. If you look closer, you have just created a new method, but it’s inlined into the current one! A function should do just one thing. This is one of the principles that are found in Clean Code book. Why don’t we extract the function into its own, so that both of them would do only one thing? Let’s see:

public void DoSomething()
{
  if(ShouldIDoSomething())
  {
    done++;
  }
}
 
private bool ShouldIDoSomething()
{
  if(needToDoSomething && haventDoneSomethingThisDay)
    shouldIDoSomething = true;
  else
  {
    // do some other logic to decide and set shouldIDoSomething to some value
  }
}

That is much cleaner, because you have reduced the complexity of the previous version of DoSomething method. Both can now be tested separately to ensure that none of the logic breaks.

So, what we learned here is: #region is not meant to be used in huge methods. Every time you use #region in a method, stop and think about what you’ve just created. Most of the time, you can extract that piece of code into its own method.

Next, we often see beautiful regions like these:

#region Get Customer
 
public void GetCustomer()
{
  // code to get the customer
}
 
#endregion
 
#region Save Customer
 
public void SaveCustomer()
{
  // code to save the customer
}
 
#endregion

When we collapse them, we get:

[Get Customer]
 
[Save Customer]

How does that make reading easier? What’s the point of it, I don’t get it? How is it better than just having the methods collapsed? It makes it even harder to read, because every time you see such region, you have to expand it and then expand the method. Silly, isn’t it? Don’t use #region just because you can!

And the next example is when we have regions like this:

public class PriceCalculator
{
  public decimal CalculatePrice()
  {
    decimal price = 100m;
    decimal discount = CalculateDiscount();
    return price * (1m - discount));
  }
 
  #region Discount Calculation
 
  private void CalculateDiscount()
  {
    decimal discount = 0m;
 
    if(CanApplyDiscount())
      discount = 0.05m;
 
    return discount;
  }
 
  private void CanApplyDiscount()
  {
    // some logic, other method calls
  }
 
  // some other discount calculation methods
  ...
 
  #endregion
}

Now if you compare this example with the first example I gave in this blog post, you might see the similarity. It’s exactly the same, but at class, not method, level. We have a similar principle here. According to the Single Responsibility Principle (SRP), a class should have only one responsibility. Look at the class above. You can easily spot two responsibilities here – price calculation and discount calculation. The discount calculation methods were already grouped, so why didn’t they get extracted into a new class? Like this:

public class PriceCalculator
{
  public decimal CalculatePrice()
  {
    decimal price = 100m;
    decimal discount = new DiscountCalculator().CalculateDiscount();
    return price * (1m - discount));
  }
}
 
public class DiscountCalculator
{
  public void CalculateDiscount()
  {
    decimal discount = 0m;
 
    if(CanApplyDiscount())
      discount = 0.05m;
 
    return discount;
  }
 
  private void CanApplyDiscount()
  {
    // some logic, other method calls
  }
 
  // some other discount calculation methods
  ...
}

Note: In real application, you might want to create and interface IDiscountCalculator and use it in PriceCalculator class. The goal is to decouple PriceCalculator from concrete implementations of IDiscountCalculator because you might want to change them without changing the PriceCalculator class.

So the moral here is: Always extract a group of related methods into a new class that has one concrete responsibility!

So, how to use the #region directive then? Well, it’s still useful for grouping things together. I usually have the more or less traditional regions in every class, to group the different constructs that we have in classes – fields, properties, methods, events, inner types. So usually my classes look like this when you open its file:

public class SomeClass
{
  [Events]
 
  [Fields]
 
  [Properties]
 
  [Methods]
}

So, to sum it all up, I see regions as a way to control the complexity of reading our source code. You can hide a group of related stuff in a region. However, it’s not an excuse to create monster methods or classes. Regions do not eliminate complexity – they only make it hidden when you read the code. So you must control the complexity of your source code by writing small, clear and focused methods and classes. When you accomplish that, you’ll notice that regions aren’t even necessary.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gediminas Geigalas
Software Developer (Senior)
Lithuania Lithuania
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembernikhi _singh1-Mar-12 0:25 
General#region and try/catch PinmemberBosse6217-Sep-10 8:20 
GeneralRe: #region and try/catch Pinmembergecka4-Oct-10 19:54 
GeneralRe: #region and try/catch PinmemberBosse627-Oct-10 10:38 
Hi gecka
 
and thank you for giving me a reply. I totally agree what you are saying and I will change my approach when coding for .NET programs. You are so right when saying that my 'catch' blocks almost always are the same. Many repeating statements. But I forgot to tell you that I almost always are coding for COM which implies that I can not EVER send exceptions to the COM client. I just have to be repeating myself in the code... or? Maybe a lame exceuse but that's the reason why I code like I do. However, you're right, it is not the best approach when coding in the .NET environment only. I'll shape up!
Thanks for your input.
/Bosse62
GeneralMy vote of 5 PinmemberJason Down17-Sep-10 6:04 
GeneralMy vote of 5 Pinmemberfreegemini12-Sep-10 9:16 
GeneralMy vote of 5 Pinmemberzhuqil6-Sep-10 22:52 
GeneralAbsolutely right PinmemberDrWheetos31-Aug-10 8:29 
Generalmy vote of 5 also Pinmembersuggie31-Aug-10 5:22 
GeneralMy vote of 5 PinmemberHerman Cordes30-Aug-10 21:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 29 Aug 2010
Article Copyright 2010 by Gediminas Geigalas
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid