2

I had the habit to implement some scope lock pattern using the "Using" statement.

Example:

ReaderWriterLockSlim _storageLocker
using (_storageLocker.LockRead())
{
   // do suff
}

In this example the extension method LockRead create a specific IDisposable object that will lock in construction and release when disposed.

/// <summary>
/// Scope lock pattern that lock the current <see cref="ReaderWriterLockSlim"/> in read mode
/// </summary>
public static IDisposable LockRead(this ReaderWriterLockSlim locker, TimeSpan timeout = default(TimeSpan))
{
    if (timeout == default(TimeSpan))
        timeout = s_defaultTimeout;

    var isLocked = locker.TryEnterReadLock(timeout);

    if (isLocked)
        return new ScopeLockAction<ReaderWriterLockSlim>(l => l.ExitReadLock(), locker);

    return Disposable.Disposed;
}

This pattern is useful and cleaner than try/Finally but the sad point is that it create a new instance each time you lock.

The ScopeLockAction implement correctly the IDispose pattern and called the GC.SuppressFinalizer() to little bit optimize the recycling.

I know that .NET garbadge collector implements some recycling mechanism that permit him to reuse an allocated space for future same Type Instances.

My questions are:

  • When you have small instance with fixed size that will be often create and dispose, and only used with the using statement (it means no boxing), is it's more performant to use a class or a struct?

  • Does it make any difference?

  • Is there a way to inform the garbadge collector that it could recycle the instance space for another instance of the same type?

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
Mickael Thumerel
  • 516
  • 4
  • 14
  • 3
    Use `BenchmarkDotNet` and try those approaches you want to know about. Usually accessing structure by interface is costly due to boxing. – Alex Seleznyov Sep 14 '18 at 15:32
  • Horizon effect, induced by trying to hammer this into an extension method. You don't actually have to allocate a delegate, all you need is a struct that implements IDisposable. No GC involved at all. – Hans Passant Sep 14 '18 at 15:50

3 Answers3

0

From the answer in the post What are the uses of "using" in C# :

The reason for the "using" statement is to ensure that the object is disposed as soon as it goes out of scope, and it doesn't require explicit code to ensure that this happens.

Structs are Value Types and Classes are Reference types, and I think, with "using" statement, it doesn't matter as it would be destroyed as soon as it goes out of scope.
You can find some good discussion on the use of IDispose/Finalize things in Finalize/Dispose pattern in C#

Farrukh Waheed
  • 2,163
  • 2
  • 29
  • 59
0
  1. tiny structs are more fast if no boxing occurred. (by my tests its 7-20 times faster to make similar objects as structs). But better do a tests!
  2. depends on count objects, but yes structure will make less side cost
  3. no there are no way to inform GC, only internal heuristics will be used

so code will be look like this, be careful you must check case when TryEnterReadLock return false :

        var _storageLocker = new ReaderWriterLockSlim();

        using (var lockContext =new ReadLockContext(_storageLocker))
        {
            if (lockContext.IsAcquired)
            {

            }
        }

public struct ReadLockContext : IDisposable
{
    private readonly ReaderWriterLockSlim locker;

    public ReadLockContext(ReaderWriterLockSlim locker, TimeSpan timeout = default(TimeSpan))
    {
        if (timeout == default(TimeSpan))
            timeout = TimeSpan.FromMilliseconds(-1);

        if (locker.TryEnterReadLock(timeout))
            this.locker = locker;
        else
            this.locker = null;
    }

    public bool IsAcquired => locker != null;

    public void Dispose()
    {
        locker?.ExitReadLock();
    }
}
gabba
  • 2,815
  • 2
  • 27
  • 48
0

Use a struct and don't mess around with the garbage collector.

Note the translation of 'using' does not box the variable even though it implements the IDispose interface.

sjb-sjb
  • 1,112
  • 6
  • 14