Performance pitfalls of a common developer practice

Adrian Mos
CodeX
Published in
15 min readJul 27, 2023

Every developer, old and young, at some point in life, is going to stumble over code similar to the one below:

public int ProcessStuff(IEnumerable<int> stuffToProcess)
{
var itemsCache = stuffToProcess.ToArray();

foreach (var item in itemsCache)
{
if (Count > 15)
{
RemoveLastItem();
}

var (success, result) = ProcessValue(item);
if (success)
{
AddItem(item);

if (Count == 0) SetBaseline(result);
}

AddResultToResultsCollection(result);

if (Count > 2)
{
UpdateAverages();
UpdateMedianDeviationValue();
ReevaluateWithBaseline();
}
}

return _medianDeviation;
}

Did you catch an anti-pattern? If yes, congratulations, you are probably experienced, maybe even senior, and are well-versed in reading code that can be improved. But for those that haven’t, let’s add some context, in the sense that we should know what the Count is:

public int Count { get; private set; }

Ok, if you still cannot spot the anti-pattern, do not worry. It is a common occurrence to see this happening in all codebases. I, for one, have yet to see a codebase entirely rid of this issue. Let’s highlight, for clarity:

public int ProcessStuff(IEnumerable<int> stuffToProcess)
{
var itemsCache = stuffToProcess.ToArray();

foreach (var item in itemsCache)
{
// Count is accessed here...
if (Count > 15)
{
RemoveLastItem();
}

var (success, result) = ProcessValue(item);
if (success)
{
AddItem(item);

// ...and here...
if (Count == 0) SetBaseline(result);
}

AddResultToResultsCollection(result);

// ...and here!
if (Count > 2)
{
UpdateAverages();
UpdateMedianDeviationValue();
ReevaluateWithBaseline();
}

// Did you remember that this was a foreach loop?
}

return _medianDeviation;
}

That’s right, the Count property is called again and again within the same method.

Why it’s an anti-pattern

First and foremost, because a property getter is not field access. A property getter works much like a method that returns a value of the same type as the property.

As a funny fact, one of my first projects was a GUI app that had to leverage Microsoft Visio. Back then, Visio’s API was seriously limited, and, since it was implemented mostly as a COM wrapper that had to be compatible with some obscure standards, it didn’t have property support. Instead, if you had a Count property, you would actually have two methods exposed:

int get_Count();

void set_Count(int value);

This, of course, resulted in both some funny discussions during code review, but it was also a rather important lesson in how we can understand properties.

This has major implications in our example, because, although the result of the property getter is used much like a field, there is no actual guarantee that it is going to be the same every time. In our case, yes, it is, but that is because we know what the getter does, as we can see it, and we know that it is side-effect-free. We cannot know that an outside library doesn’t, perchance, have a property whose getter is not side-effect-free (in itself an anti pattern), or whose getter simply does a lot of extra work for no reason at all (which is not necessarily an anti-pattern, but still not performant).

Also, we have a very obvious second issue: performance. Since the runtime treats the getter like a method, it will not offer you the same speed as a local field. The compiler cannot guess that the multiple calls are actually intended to be used as field access, and will not cache the return value anywhere. This means that, for each property call, there is the added cost of calling the getter, whatever that may entail.

Third issue, and also the mightiest, has to do with synchronized multi-threaded access. If you access the getter multiple times, and the state that the getter depends on is updated in-between calls by multiple threads, what’s to say that the result won’t change between calls? Clearly in our example there is no indication that any guarding against such an issue was even attempted.

But how bad is it, performance-wise?

Obviously, a couple of accesses is generally not going to hurt, nor is it going to be a major performance improvement if we cache he value there (of course, depending on the side effects of the getter).

But, really, how bad is it? Let’s find out with a few simple scenarios, and a trusty benchmark.net console app.

The test system

I used my current developer machine for this test, specced as follows:

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22621.1928/22H2/2022Update/SunValley2)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
[Host] : .NET Framework 4.8.1 (4.8.9166.0), X64 RyuJIT VectorSize=256
Job-GAFOEP : .NET 6.0.18 (6.0.1823.26907), X64 RyuJIT AVX2
Job-LCYYTK : .NET 7.0.7 (7.0.723.27404), X64 RyuJIT AVX2
Job-FXSAZS : .NET Framework 4.8.1 (4.8.9166.0), X64 RyuJIT VectorSize=256

RunStrategy=Throughput

As you can see, I am using the .NET 4.8.1 runtime as a baseline, and testing for the .NET 6 and .NET 7 as well.

The test project

The test project is composed of a simple console application, with the benchmark.net NuGet package added (official site here, quick start here, documentation here). Inside, we have an object that holds properties, named SimpleClassWithProperties, and the benchmark class, named TestRepeatedPropertyCalls.

The benchmark class aims to benchmark a few scenarios, such as:

  1. A simple property access, in which an int property is accessed in a loop
  2. The same scenario, but the property value is cached and only accessed once outside the loop
  3. A property which calls a lazy field behind and gets its value (an otherwise rather common pattern)
  4. The same scenario, but in which the property value is cached between calls

We’ll use, for the purpose of our test, loops of 1 000, 10 000, 100 000 and 1 000 000 iterations.

The set-up

I’ve used, in a typical benchmark.net fashion, a setup for my tests:

[SimpleJob(RunStrategy.Throughput, runtimeMoniker: RuntimeMoniker.Net481, baseline: true)]
[SimpleJob(RunStrategy.Throughput, runtimeMoniker: RuntimeMoniker.Net60)]
[SimpleJob(RunStrategy.Throughput, runtimeMoniker: RuntimeMoniker.Net70)]
[RPlotExporter]
public class TestRepeatedPropertyCalls
{
private readonly SimpleClassWithProperties _props = new();

private int _field = 1;

[Params(1000, 10000, 100000, 1000000)] public int Iterations { get; set; }

[GlobalSetup]
public void Setup()
{
_props.Initialize();
}

// ... The benchmarks go here
}

First results

The first scenario serves as a good benchmark. It is simple, it is by far the most common use of the anti-pattern, and a rather easy to miss problem in code.

[Benchmark]
public void RepeatedSimpleCall()
{
var props = _props;
for (var i = 0; i < Iterations; i++)
{
// public int SimpleValueProperty => 1;
int q = props.SimpleValueProperty;
q++;
}
}
|             Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|------------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedSimpleCall | .NET 6.0 | 1000 | 254.1 ns | 2.44 ns | 2.28 ns | 0.52 | 0.01 |
| RepeatedSimpleCall | .NET 7.0 | 1000 | 243.7 ns | 0.98 ns | 0.92 ns | 0.50 | 0.00 |
| RepeatedSimpleCall | .NET Framework 4.8.1 | 1000 | 484.1 ns | 2.95 ns | 2.76 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCall | .NET 6.0 | 10000 | 2,427.2 ns | 34.44 ns | 32.21 ns | 0.51 | 0.01 |
| RepeatedSimpleCall | .NET 7.0 | 10000 | 2,403.2 ns | 32.47 ns | 30.38 ns | 0.50 | 0.01 |
| RepeatedSimpleCall | .NET Framework 4.8.1 | 10000 | 4,793.3 ns | 25.10 ns | 23.48 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCall | .NET 6.0 | 100000 | 23,972.2 ns | 197.94 ns | 175.47 ns | 0.50 | 0.00 |
| RepeatedSimpleCall | .NET 7.0 | 100000 | 23,900.3 ns | 265.07 ns | 234.98 ns | 0.50 | 0.01 |
| RepeatedSimpleCall | .NET Framework 4.8.1 | 100000 | 47,712.1 ns | 180.07 ns | 168.44 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCall | .NET 6.0 | 1000000 | 243,836.9 ns | 2,915.52 ns | 2,727.18 ns | 0.51 | 0.01 |
| RepeatedSimpleCall | .NET 7.0 | 1000000 | 240,423.8 ns | 3,204.80 ns | 2,997.78 ns | 0.50 | 0.01 |
| RepeatedSimpleCall | .NET Framework 4.8.1 | 1000000 | 479,850.0 ns | 4,859.77 ns | 4,545.83 ns | 1.00 | 0.00 |

As we can see, even at 1 000 iterations, we can see a huge difference between .NET 4.8.1 and .NET 6, and a smaller one between .NET 6 and .NET 7. This goes to show the amount of work that went into .NET 6 and 7, and how fast the runtimes are nowadays. A testament to the .NET team.

The property doesn’t even access a field behind the scenes, it just returns a constant. Let’s see, however, what happens if we do access a field.

[Benchmark]
public void RepeatedSimpleCallWithBackingField()
{
var props = _props;
for (var i = 0; i < Iterations; i++)
{
// public int SimpleFieldProperty => _field;
int q = props.SimpleFieldProperty;
q++;
}
}
|                             Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|----------------------------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedSimpleCallWithBackingField | .NET 6.0 | 1000 | 245.1 ns | 0.70 ns | 0.58 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithBackingField | .NET 7.0 | 1000 | 245.6 ns | 1.76 ns | 1.64 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithBackingField | .NET Framework 4.8.1 | 1000 | 245.9 ns | 1.40 ns | 1.24 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithBackingField | .NET 6.0 | 10000 | 2,395.7 ns | 16.24 ns | 15.19 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithBackingField | .NET 7.0 | 10000 | 2,383.7 ns | 10.73 ns | 8.37 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithBackingField | .NET Framework 4.8.1 | 10000 | 2,392.6 ns | 9.84 ns | 9.20 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithBackingField | .NET 6.0 | 100000 | 24,162.7 ns | 463.42 ns | 533.68 ns | 1.00 | 0.03 |
| RepeatedSimpleCallWithBackingField | .NET 7.0 | 100000 | 23,803.0 ns | 120.81 ns | 94.32 ns | 0.98 | 0.03 |
| RepeatedSimpleCallWithBackingField | .NET Framework 4.8.1 | 100000 | 24,271.6 ns | 484.19 ns | 557.59 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithBackingField | .NET 6.0 | 1000000 | 238,677.4 ns | 2,587.81 ns | 2,294.02 ns | 0.99 | 0.02 |
| RepeatedSimpleCallWithBackingField | .NET 7.0 | 1000000 | 238,182.7 ns | 1,726.83 ns | 1,530.79 ns | 0.99 | 0.02 |
| RepeatedSimpleCallWithBackingField | .NET Framework 4.8.1 | 1000000 | 240,777.6 ns | 4,150.65 ns | 3,882.52 ns | 1.00 | 0.00 |

Now, let’s test the method accessing the field directly, without the use of a property:

[Benchmark]
public void RepeatedBackingField()
{
for (var i = 0; i < Iterations; i++)
{
int q = _field;
q++;
}
}
|               Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|--------------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedBackingField | .NET 6.0 | 1000 | 249.6 ns | 5.00 ns | 4.68 ns | 1.02 | 0.02 |
| RepeatedBackingField | .NET 7.0 | 1000 | 246.5 ns | 2.08 ns | 1.84 ns | 1.01 | 0.01 |
| RepeatedBackingField | .NET Framework 4.8.1 | 1000 | 244.5 ns | 1.35 ns | 1.19 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedBackingField | .NET 6.0 | 10000 | 2,384.9 ns | 14.12 ns | 11.79 ns | 0.99 | 0.01 |
| RepeatedBackingField | .NET 7.0 | 10000 | 2,410.9 ns | 24.77 ns | 23.17 ns | 1.00 | 0.01 |
| RepeatedBackingField | .NET Framework 4.8.1 | 10000 | 2,399.8 ns | 19.73 ns | 18.45 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedBackingField | .NET 6.0 | 100000 | 23,884.3 ns | 166.91 ns | 156.13 ns | 1.00 | 0.01 |
| RepeatedBackingField | .NET 7.0 | 100000 | 24,117.7 ns | 460.60 ns | 582.51 ns | 1.02 | 0.03 |
| RepeatedBackingField | .NET Framework 4.8.1 | 100000 | 23,884.1 ns | 195.06 ns | 172.91 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedBackingField | .NET 6.0 | 1000000 | 238,962.3 ns | 1,106.20 ns | 923.72 ns | 1.01 | 0.00 |
| RepeatedBackingField | .NET 7.0 | 1000000 | 240,825.0 ns | 4,556.26 ns | 4,474.85 ns | 1.02 | 0.02 |
| RepeatedBackingField | .NET Framework 4.8.1 | 1000000 | 237,235.7 ns | 1,274.31 ns | 1,129.65 ns | 1.00 | 0.00 |

And finally, let’s see what happens if we access the property once, then cache the result in a local variable, and use it further down the road:

[Benchmark]
public void RepeatedSimpleCallWithCachedValue()
{
var props = _props;
// public int SimpleValueProperty => 1;
int q = props.SimpleValueProperty;
for (var i = 0; i < Iterations; i++)
{
q++;
}
}
|                            Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|---------------------------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedSimpleCallWithCachedValue | .NET 6.0 | 1000 | 247.3 ns | 1.85 ns | 1.73 ns | 0.98 | 0.03 |
| RepeatedSimpleCallWithCachedValue | .NET 7.0 | 1000 | 248.3 ns | 4.26 ns | 4.18 ns | 0.99 | 0.03 |
| RepeatedSimpleCallWithCachedValue | .NET Framework 4.8.1 | 1000 | 252.4 ns | 5.01 ns | 5.57 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithCachedValue | .NET 6.0 | 10000 | 2,400.8 ns | 13.57 ns | 12.03 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithCachedValue | .NET 7.0 | 10000 | 2,402.8 ns | 20.51 ns | 19.18 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithCachedValue | .NET Framework 4.8.1 | 10000 | 2,400.0 ns | 9.35 ns | 8.75 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithCachedValue | .NET 6.0 | 100000 | 23,822.2 ns | 134.62 ns | 125.93 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithCachedValue | .NET 7.0 | 100000 | 23,942.8 ns | 133.21 ns | 104.00 ns | 1.00 | 0.01 |
| RepeatedSimpleCallWithCachedValue | .NET Framework 4.8.1 | 100000 | 23,914.0 ns | 237.39 ns | 210.44 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedSimpleCallWithCachedValue | .NET 6.0 | 1000000 | 242,126.0 ns | 3,900.67 ns | 3,457.84 ns | 1.02 | 0.02 |
| RepeatedSimpleCallWithCachedValue | .NET 7.0 | 1000000 | 243,313.0 ns | 4,712.39 ns | 4,628.19 ns | 1.02 | 0.02 |
| RepeatedSimpleCallWithCachedValue | .NET Framework 4.8.1 | 1000000 | 238,232.4 ns | 1,425.86 ns | 1,263.99 ns | 1.00 | 0.00 |

Let’s think about these results for a second.

We can clearly see the linearity in growth, relative to the iterations count. We do indeed get the general sense that 100 000 iterations is 100 times more time-consuming than 1 000 iterations and 10 times less time-consuming than 1 000 000 iterations. So there’s nothing hanky going on in the background, or, at least, not yet.

Next, we get the sense that there’s really not that much difference in performance when it comes to different ways of doing our work, with the notable exception of the “simple” call in .NET Framework. This deals with not how the values work, but more like how .NET Framework treats constant values, something which has been greatly improved in .NET Core and beyond.

So far, though, it almost seems like the anti-pattern that I’m talking about isn’t really that bad at all, is it? Let’s see when we get the second set of results.

More complex tasks

Let’s imagine a more complex pattern, which is still one of the most widely-used ones across the .NET world: accessing a property getter that returns a lazily-initialized field. Not a significantly more complex operation, not yet one with arcane side-effects, but still one that might get heavy. How heavy, you ask? Well…

[Benchmark]
public void RepeatedLazyCall()
{
var props = _props;
for (var i = 0; i < Iterations; i++)
{
// private readonly Lazy<int> _intLazy = new(() => 1);
// public int LazyValueProperty => _intLazy.Value;
int q = props.LazyValueProperty;
q++;
}
}
|           Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|----------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedLazyCall | .NET 6.0 | 1000 | 492.1 ns | 3.45 ns | 3.23 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET 7.0 | 1000 | 733.5 ns | 7.68 ns | 7.18 ns | 0.30 | 0.01 |
| RepeatedLazyCall | .NET Framework 4.8.1 | 1000 | 2,439.7 ns | 34.70 ns | 32.46 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCall | .NET 6.0 | 10000 | 4,856.4 ns | 22.03 ns | 19.53 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET 7.0 | 10000 | 4,874.7 ns | 25.70 ns | 24.04 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET Framework 4.8.1 | 10000 | 24,029.2 ns | 69.25 ns | 64.78 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCall | .NET 6.0 | 100000 | 72,443.7 ns | 397.80 ns | 372.10 ns | 0.30 | 0.00 |
| RepeatedLazyCall | .NET 7.0 | 100000 | 48,631.2 ns | 371.58 ns | 347.58 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET Framework 4.8.1 | 100000 | 242,897.8 ns | 1,269.98 ns | 1,187.94 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCall | .NET 6.0 | 1000000 | 488,006.0 ns | 3,229.68 ns | 3,021.05 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET 7.0 | 1000000 | 487,949.1 ns | 2,684.41 ns | 2,379.66 ns | 0.20 | 0.00 |
| RepeatedLazyCall | .NET Framework 4.8.1 | 1000000 | 2,408,080.8 ns | 10,460.44 ns | 9,784.70 ns | 1.00 | 0.00 |

Oooooook, well, that was quite educational. Not only do we have an interesting variability (especially if you look at the .NET 6 results), but we have a huuuuuuuuge difference between the 238 milliseconds of the local field access in the last test and the 2.4 seconds in the .NET Framework’s test. Even the .NET 7, which is almost across the board faster, is still around double in time consumed. That just gives you an indication as to how much of an anti-pattern this is for anything other than the simple properties (think auto-properties).

But did these carry over over local field access? I believe you’ll be completely not surprised to find out that they obviously didn’t, as this kind of test was simply put here as a showcase for developers at the beginning of their career:

[Benchmark]
public void RepeatedLazyCallWithCachedValue()
{
var props = _props;
int q = props.LazyValueProperty;
for (var i = 0; i < Iterations; i++)
{
q++;
}
}
|                          Method |              Runtime | Iterations |           Mean |        Error |      StdDev | Ratio | RatioSD |
|-------------------------------- |--------------------- |----------- |---------------:|-------------:|------------:|------:|--------:|
| RepeatedLazyCallWithCachedValue | .NET 6.0 | 1000 | 262.9 ns | 2.92 ns | 2.59 ns | 1.00 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET 7.0 | 1000 | 272.0 ns | 2.24 ns | 2.10 ns | 1.04 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET Framework 4.8.1 | 1000 | 262.8 ns | 1.96 ns | 1.74 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCallWithCachedValue | .NET 6.0 | 10000 | 2,436.0 ns | 20.96 ns | 18.58 ns | 1.00 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET 7.0 | 10000 | 2,446.6 ns | 29.89 ns | 24.96 ns | 1.01 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET Framework 4.8.1 | 10000 | 2,429.9 ns | 13.19 ns | 11.69 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCallWithCachedValue | .NET 6.0 | 100000 | 24,085.5 ns | 224.20 ns | 209.72 ns | 0.99 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET 7.0 | 100000 | 24,274.8 ns | 272.75 ns | 227.76 ns | 0.99 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET Framework 4.8.1 | 100000 | 24,398.5 ns | 184.29 ns | 172.38 ns | 1.00 | 0.00 |
| | | | | | | | |
| RepeatedLazyCallWithCachedValue | .NET 6.0 | 1000000 | 244,722.8 ns | 1,662.51 ns | 1,555.11 ns | 1.01 | 0.01 |
| RepeatedLazyCallWithCachedValue | .NET 7.0 | 1000000 | 248,581.2 ns | 4,916.05 ns | 6,037.35 ns | 1.02 | 0.02 |
| RepeatedLazyCallWithCachedValue | .NET Framework 4.8.1 | 1000000 | 242,355.5 ns | 1,202.67 ns | 1,124.98 ns | 1.00 | 0.00 |

Conclusion

First off, we get a sense of just how faster .NET 6 is than .NET Framework 4.8.1. While it’s true that this is happening on an anti-pattern, after all, we’re talking about property getters and lazy initialization, and there are many legitimate scenarios in which frequent use of them makes a lot of sense. Improving that area can make a great difference.

So there you have it. One of the most basic complications of property getters already gets you into trouble with repeated calls, performance-wise. I’ll leave you to your imagination to find out just how bad things get when we’re dealing with side-effects or even WTF moments like database reads.

I guess the main reason why I wish to point out this anti-pattern is that it’s both an easy anti-pattern to follow, and it’s also a very easy one to get rid of. Even tools like ReSharper can help you automatically replace all your property getter calls, and it’s one of the things most easily picked up by IntelliCode.

I’m sure going to be glad when serious AI rolls out, as it will most probably make short work of this anti-pattern.

While today’s .NET environments have greatly improved over what used to be the norm in an already-fast environment, fostering a culture of avoiding anti-patterns and performance-oriented coding can only help in the long run. Of course you can have code that is not really optimal, but it’s fast to write, but, really, with examples like the repeated property getter anti-pattern, exactly how much developer time do you save?

That is, unless, if you code multi-threaded applications, in which case, by thinking ahead and not following this anti-pattern, you’ll almost be certain to be saving a TON of time in debugging race conditions and improperly-synchronized field accesses across threads. Ask me how I know.

--

--

Adrian Mos
CodeX
Writer for

Software developer, architect and father, with a love of science and writing, and a desire for global social and economic development.