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
{
}
#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
{
}
}
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()
{
}
#endregion
#region Save Customer
public void SaveCustomer()
{
}
#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()
{
}
...
#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()
{
}
...
}
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.
CodeProject