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

Handy wrapper class for thread-safe property access

0.00/5 (No votes)
21 Feb 2009 1  
A simple C# approach to thread-safe property access using Generics and type cast overloading.

Introduction

After writing C# threaded applications for a few years, I started to get bored of writing the synchronization code for each property I wanted to make thread-safe. If you want your class to manage the locking in a neat OOP encapsulated way, you must create locking variables and handle access to each property using get { } and set { }.

So came the idea of writing a simple wrapper class to do the dirty work for me. The Synchronized class is my first try at this, and can hopefully be further developed and extended.

I hope this can be of interest to some, and I really do hope to get some feedback to improve and extend my work. :)

Background

The basics to make this work are the use of Generics and type casting overloading. I suggest those who are not familiar with these concepts have a look at the .NET documentation or at one of the many good articles on the web.

Using the code

A typical C# approach to making properties thread-safe would be something like:

internal class MyThreadSafeCass
{
    // *** Lock ***
    private object PropertyLock = new object();
       
    // *** Property ***
    private int m_Property = 0;
        
    // *** Thread-safe access to Property using locking ***
    internal int Property
    {
        get
        {
            lock (PropertyLock)
            {
                return m_Property;
            }
        }
        set
        {
            lock (PropertyLock)
            {
                m_Property = value;
            }
        }
    }
}

Now, every thread in your application can access Property in a thread-safe way.

A class containing a few properties which must be made thread-safe would lead to writing quite some code, and decrease the overall neatness and readability.

Let's have a look at the Synchronized wrapper class. Its definition is quite simple:

// **********************************************
// *** Synchronized access wrapper class V1.0 ***
// **********************************************
// *** (C)2009 S.T.A. snc                     ***
// **********************************************
using System;

namespace STA.Threading
{

   internal class Synchronized<T>
   {
        // *** Locking ***
        private object m_ValueLock;

        // *** Value buffer ***
        private T m_Value;

        // *** Access to value ***
        internal T Value
        {
            get
            {
                lock (m_ValueLock)
                {
                    return m_Value;
                }
            }
            set
            {
                lock (m_ValueLock)
                {
                    m_Value = value;
                }
            }
        }

        // *******************
        // *** Constructor ***
        // *******************
        internal Synchronized()
        {
            m_ValueLock = new object();
        }

        internal Synchronized(T value)
        {
            m_ValueLock = new object();
            Value = value;
        }

        internal Synchronized(T value, object Lock)
        {
            m_ValueLock = Lock;
            Value = value;
        }

        // ********************************
        // *** Type casting overloading ***
        // ********************************
        public static implicit operator T(Synchronized<T> value)
        {
            return value.Value;
        }

    }
}

It makes use of Generics in order to be able to wrap any type, and overloads the implicit type casting to the wrapped type to allow a Synchronized object to be used in place of the wrapped type, where possible.

Now, we can use it to modify our test class:

using STA.Threading;

internal class MyThreadSafeCass
{
    // *** Thread-safe property ***
    internal Synchronized<int> Property = new Synchronized<int>(0);
}

Simple and neat.

What it does and what it does not

A property wrapped in a Synchronized class can be used in many common situations without needing any special syntax, but there are some exceptions. Here is a brief summary of its most common uses:

using STA.Threading;

...

// s1 <- 10
Synchronized<int> s1 = new Synchronized(10);
// s2 <- 0 (deafult for int)
Synchronized<int> s2 = new Synchronized();

s2 = 20; // ERROR: the '=' operator cannot be overloaded, thus
         // you cannot assign a value of type T directly to a
         // Synchronized<T> object

s2.Value = 20; // Valid: s2 <- 20

int sum = s1 + s2: // Valid: s1 and s2 are subject to implicit
                   // type casting to type T, so sum <- 30

s1++; // ERROR: I didn't find a way to overload the ++ and -- unary
      // operators because of a limitation with Generics: you cannot
      // use ++ or -- on generic type T. Hopefully this can be worked
      // around in some way in a future release. Maybe using "where
      // T : ..." ?

s1.Value++; // Valid: s1 <- 11

int TestFunction(int Value)
{
    return Value * 10;
}

int ret = TestFunction(s1); // Valid: s1 is subject to implicit type
                            // casting to type T, so ret <- 110

int.TryParse("50", out s1); // ERROR: out requires a modifiabile
                            // variable, so type casting is not
                            // possible

int.TryParse("50", out s1.Value); // ERROR: a property cannot be
                                  // used with out or ref

int tmp;
int.TryParse("50", out tmp);
s1.Value = tmp;              // Valid: s1 <- 50

...

The biggest limitation with this approach is it's no good with properties of complex types. For example, a Synchronized<DataDet> property will not render calls to the wrapped DataSet's properties or thread-safe methods.

Points of interest

I found the approach to implicit and explicit type casting overloading in C# particularly nice and efficient. Before this project, I only had experience with unary and binary operators overloading, and when I first started looking around in the documentation for some hint as to how to implement my idea, I doubted there would be such an easy solution.

History

  • 2009-02-21 - V1.0 - First release.

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