Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

About #region preprocessor directive

0.00/5 (No votes)
29 Aug 2010 1  
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:

C#
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?

C#
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:

C#
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:

C#
#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:

C#
[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:

C#
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:

C#
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:

C#
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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here