Singleton Indexer - A Workaround for not having Static Indexers in C#

Indexers are used to simplify access to a collection that is encapsulated in a class you’re designing…

From MSDN:

class SampleCollection<T> {
    private T[] arr = new T[100];       
    public T this[int i] {
        get {
            return arr[i];
        }
        set {
            arr[i] = value;
        }
    }
}
SampleCollection<string> stringCollection = new SampleCollection<string>();

stringCollection[0] = "Hello, World";
System.Console.WriteLine(stringCollection[0]);

Static Classes and Static Class Members are used when you want your application to have access to a common set of methods or data.

From MSDN:

public class Automobile {
    public static int NumberOfWheels = 4;
    ...
}
Console.WriteLine(Automobile.NumberOfWheels); // 4

Wouldn’t it be nice if you could combine these to create a Static Class Member with an Indexer, i.e. a Static Indexer? For example: Encoding["UTF8"] instead of Encoding.GetEncoding("UTF8") (Jon Skeet).

You can’t make a Static Indexer in C#…

The CLR can, but you can’t with C#. You can’t even trick the compiler with VB.NET.

Singleton

Indexers require a reference to this. Since Static Classes aren’t instantiated - they don’t have this.

What’s the next best thing?

Singletons can be instantiated so they do have a reference to this. They are another way to provide your application with widely accessible methods and data.

Let’s create a Singleton with an Indexer.

Simple First

We’ll start by building a class for storing heterogeneous data. We might use it to store user preferences in a game. We’ll make an Indexer for it so it has a simple API.

public class DataStore {
    private readonly Dictionary<string, object> _properties;
    public DataStore() {
        this._properties = new Dictionary<string, object>();
    }
    private void SetProperty<T>(string name, T value) {
        _properties.Add(name, value);
    }
    private dynamic GetProperty(string name) {
        dynamic value;
        _properties.TryGetValue(name, out value);
        return value;
    }
    public dynamic this[string index] {
        get { return GetProperty(index); }
        set { SetProperty(index, value); }
    }
}

Don’t worry about the Generics here if you’re not familiar. This could be done using a Dictionary<string, string>. If you want to learn more about dynamic, you can check out this post.

Here is a test to demonstrate the usage:

[Fact]
public void set_properties_using_indexer() {
    var dataStore = new DataStore();
    dataStore["Key1"] = "String";
    dataStore["Key2"] = 55;
    dataStore["Key4"] = DateTime.Now;
    dataStore["Key3"] = new KeyValuePair&ltstring, int&gt("KV", 10);

    Assert.Equal("String", dataStore["Key1"]);
    Assert.Equal(10, dataStore["Key3"].Value);
}

Now we have our public class with Indexer built. Let’s move on to making our Indexed data globally accessible.

What Happens with Static

To show that we can’t just make our class static, let’s try:

static class DataStoreStaticIndexer {
    private static readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    public dynamic this[string index] 
    // throws 'cannot declare indexers in a static class'
    {
        get {
            dynamic value;
            _properties.TryGetValue(index, out value);
            return value;
        }
        set { _properties.Add(index, value); }
    }
}

Enter Singleton

We’ll use Jon Skeet’s Singleton Pattern #6, which uses System.Lazy<T>. Here’s the setup:

public sealed class DataStoreSingletonIndexer {
    private static readonly Lazy<DataStoreSingletonIndexer> lazy =
        new Lazy<DataStoreSingletonIndexer>(() => new DataStoreSingletonIndexer());
    public static DataStoreSingletonIndexer Instance { get { return lazy.Value; } }
    private DataStoreSingletonIndexer() {  
    }
}

Let’s plug in our original DataStore class with its heterogeneous collection:

public sealed class DataStoreSingletonIndexer {
    private static readonly Lazy<DataStoreSingletonIndexer> lazy =
        new Lazy<DataStoreSingletonIndexer>(() => new DataStoreSingletonIndexer());
    private readonly Dictionary<string, object> _properties;
    public static DataStoreSingletonIndexer Instance { get { return lazy.Value; } }
    private DataStoreSingletonIndexer() {  
        _properties = new Dictionary<string, object>();  
    }
    private void SetProperty<T>(string name, T value) {
        _properties.Add(name, value);
    }
    private dynamic GetProperty(string name) {
        dynamic value;
        _properties.TryGetValue(name, out value);
        return value;
    }
    public dynamic this[string index] {
        get { return GetProperty(index); }
        set { SetProperty(index, value); }
    }
}

[Fact]
public void singleton_instantiation_property_manipulation() {
    DataStoreSingletonIndexer.Instance["Key1"] = "String";
    DataStoreSingletonIndexer.Instance["Key2"] = 55;
    DataStoreSingletonIndexer.Instance["Key4"] = DateTime.Now;
    DataStoreSingletonIndexer.Instance["Key3"] = new KeyValuePair<string, int>("KV", 10);

    Assert.Equal("String", DataStoreSingletonIndexer.Instance["Key1"]);
    Assert.Equal(10, DataStoreSingletonIndexer.Instance["Key3"].Value);
}

One problem with this implementation is that our Dictionary<string, object> _properties isn’t thread safe.

Thread Safety

Here’s a test showing that our Dictionary<string, object> isn’t thread safe yet:

[Fact]
public void thread_safety_of_properties_collection() {
    var t1 = new Thread(SetManyPropertiesEven);
    var t2 = new Thread(SetManyPropertiesOdd);
    t1.Start();
    t2.Start();
    t1.Join();
    t2.Join();

    Assert.Equal(10000, DataStoreSingletonIndexer.Instance.GetCount());
    // Xunit.Sdk.EqualExceptionAssert.Equal() Failure
    // Expected: 10000
    // Actual:   9871
}
public void SetManyPropertiesEven() {
    for (int i = 0; i < 10000; i += 2) {
        DataStoreSingletonIndexer.Instance[i.ToString()] = i;
    }
}
public void SetManyPropertiesOdd() {
    for (int i = 1; i < 10000; i += 2) {
        DataStoreSingletonIndexer.Instance[i.ToString()] = i;
    }
}

Luckily, we have an easy fix for this with ConcurrentDictionary<TKey, TValue> (MSDN)

public sealed class DataStoreSingletonIndexer {
    private static readonly Lazy<DataStoreSingletonIndexer> lazy =
        new Lazy<DataStoreSingletonIndexer>(() => new DataStoreSingletonIndexer());
    private readonly ConcurrentDictionary<string, object> _properties;
    public static DataStoreSingletonIndexer Instance { get { return lazy.Value; } }
    private DataStoreSingletonIndexer() {  
        _properties = new ConcurrentDictionary<string, object>();  
    }
    private void SetProperty<T>(string name, T value) {
        _properties.TryAdd(name, value);
    }
    private dynamic GetProperty(string name) {
        dynamic value;
        _properties.TryGetValue(name, out value);
        return value;
    }
    public dynamic this[string index] {
        get { return GetProperty(index); }
        set { SetProperty(index, value); }
    }
}

Our thread-safety test passes! More sample code can be found here

Summary

We found a need for a Static Indexer, i.e. a Static Class with a Static Class Member that we’d like convenient access to using array syntax [].

It turned out that Static Indexers aren’t doable in C#. So as a compromise, we decided to implement a Singleton that exposed an Indexer.

We followed a pattern that uses System.Lazy<T> to make our Indexed class a thread safe Singleton. We also made sure the internal collection was thread safe by using ConcurrentDictionary<string, object>

When you need to make a Static Indexer and find out that you can’t in C#, I hope this finds its way to you. Maybe it just gives you a good use for a Singleton. Either way, hope it helps. I have a lot more coming for 2016. This is just the beginning. So sign up for my newsletter!


Sources

Tweet
comments powered by Disqus