Introduction
Closure itself isn’t a brand new topic. I’ve seen many blog articles that explain the topic (there’s even a Wikipedia page in a few languages!), however, I decided to present my own example. To be honest, thanks to this post I finally focused on its internals and what really happens behind the scenes. In this post I’ll show you a simple example of the closure that might be the root of an exception.
Example
Let’s consider the following code snippet:
public class Closure
{
public static Func<int> GetFuncWithClosure(int arg)
{
int internalVariable = arg;
var func = new Func<int>(() =>
{
internalVariable -= 5;
return 10 / internalVariable;
});
return func;
}
public static Func<int, int> GetFuncWithoutClosure()
{
var func = new Func<int, int>((arg) =>
{
arg -= 5;
return 10 / arg;
});
return func;
}
}
As you can see there’s one simple class named Closure
and two static methods named GetFuncWithClosure
and GetFuncWithoutClosure
. First method returns the Func<int> – in other words, a function that accepts no argument and returns an integer. Second method returns the Func<int, int> – a function that accepts one argument and returns an integer. There’s one caveat in terms of GetFuncWithClosure
though, returned function uses the variable named internalVariable
, hence closure.
Tests
Having our program ready to go, let’s focus on a few unit tests.
[Fact]
public void GetFuncWithClosure_InvokeOnce_GetExpectedResult()
{
//arrange
var func = Closure.GetFuncWithClosure(10);
//act
var result = func();
//assert
Assert.Equal(2, result);
}
[Fact]
public void GetFuncWithClosure_InvokeMoreThanOnce_GetUnexpectedResult()
{
//arrange
var func = Closure.GetFuncWithClosure(10);
//act & assert
var result = func();
Assert.Equal(2, result);
Assert.Throws<DivideByZeroException>(() => func());
}
[Fact]
public void GetFuncWithoutClosure_InvokeOnce_GetExpectedResult()
{
//arrange
var func = Closure.GetFuncWithoutClosure();
//act
var result = func(10);
//assert
Assert.Equal(2, result);
}
[Fact]
public void GetFuncWithoutClosure_InvokeMoreThanOnce_GetExpectedResult()
{
//arrange
var func = Closure.GetFuncWithoutClosure();
//act
var result = func(10);
var result2 = func(10);
//assert
Assert.Equal(2, result);
Assert.Equal(2, result2);
}
We have four tests:GetFuncWithClosure_InvokeOnce_GetExpectedResult
, GetFuncWithClosure_InvokeMoreThanOnce_GetUnexpectedResult
, GetFuncWithoutClosure_InvokeOnce_GetExpectedResult
, GetFuncWithoutClosure_InvokeMoreThanOnce_GetExpectedResult
.
First two tests test GetFuncWithClosure
, whereas next two test GetFuncWithoutClosure
method. Surprisingly, GetFuncWithClosure
throws the DivideByZeroException exception, when GetFuncWithClosure
invoked second time.
To get better insights we should go to the sharplab.io.
Behind the scenes
If we paste our code in sharplab.io, we will get the answer of the DivideByZeroException exception occurrence cause. So, let’s take a closer look at the following:
For now let’s focus on the problematic GetFuncWithClosure
method. As you can see the method body uses now some strange <>c__DisplayClass0_0
class. Well, that is the exact example of the closure in C#. Compiler had to create something (<>c__DisplayClass0_0
class) to store the int internalVariable
just because the variable has been used inside the returned Func<int>. On the other hand, GetFuncWithoutClosure
method doesn’t use any external data within returned Func<int, int>, therefore no corresponding *DisplayClass*.
Once we know that, it’s obvious why the DivideByZeroException exception occurs. First invocation (i.e. func()
) subtracts 5 from the <>c__DisplayClass0_0
object’s internalVariable
. So far so good, but second invocation subtracts 5 again from the <>c__DisplayClass0_0
object’s internalVariable
zeroing its value. One thing is certain: Math does not like to divide by zero.
Summary
To sum up, closure is an interesting part of computer programming that definitely deserves to be understood. The above example shows that the code might work in an unexpected manner, until we don’t know what happens behind the scenes. To get better understanding what the closure is I highly recommend to read some articles/blogs (including aforementioned Wikipedia page) or watch some YouTube video (e.g. this one).
As always I’ve prepared an example and you can find it on my github, project: closure, test project: closure-tests. I encourage you to go through the code, debug it and try to test your own scenarios.
Have a nice day, bye!
Be First to Comment