Static Indexers?

C#.NetStaticIndexerStatic Indexers

C# Problem Overview


Why are static indexers disallowed in C#? I see no reason why they should not be allowed and furthermore they could be very useful.

For example:

public static class ConfigurationManager 
{
        public object this[string name]
        {
            get => ConfigurationManager.getProperty(name);
            set => ConfigurationManager.editProperty(name, value);
        }

        /// <summary>
        /// This will write the value to the property. Will overwrite if the property is already there
        /// </summary>
        /// <param name="name">Name of the property</param>
        /// <param name="value">Value to be wrote (calls ToString)</param>
        public static void editProperty(string name, object value) 
        {
            var ds = new DataSet();
            var configFile = new FileStream("./config.xml", FileMode.OpenOrCreate);
            ds.ReadXml(configFile);

            if (ds.Tables["config"] == null)
                ds.Tables.Add("config");

            var config = ds.Tables["config"];

            if (config.Rows[0] == null) 
                config.Rows.Add(config.NewRow());

            if (config.Columns[name] == null) 
                config.Columns.Add(name);

            config.Rows[0][name] = value.ToString();

            ds.WriteXml(configFile);
            configFile.Close();
        }

        public static void addProperty(string name, object value) =>
            ConfigurationManager.editProperty(name, value);

        public static object getProperty(string name) 
        {
            var ds = new DataSet();
            var configFile = new FileStream("./config.xml", FileMode.OpenOrCreate);
            ds.ReadXml(configFile);
            configFile.Close();

            if (ds.Tables["config"] == null) return null;

            var config = ds.Tables["config"];

            if (config.Rows[0] == null) return null;
            if (config.Columns[name] == null) return null;

            return config.Rows[0][name];
        }
    }

The above code would benefit greatly from a static indexer. However it won't compile because static indexers are not allowed. Why is this so?

C# Solutions


Solution 1 - C#

I believe it was considered not to be terribly useful. I think it's a shame too - an example I tend to use is Encoding, where Encoding.GetEncoding("foo") could be Encoding["Foo"]. I don't think it would come up very often, but aside from anything else it just feels a little inconsistent not to be available.

I would have to check, but I suspect it's available in IL (Intermediate Language) already.

Solution 2 - C#

Indexer notation requires a reference to this. Since static methods don't have a reference to any particular instance of the class, you can't use this with them, and consequently you can't use indexer notation on static methods.

The solution to your problem is using a singleton pattern as follows:

public class Utilities
{
    private static ConfigurationManager _configurationManager = new ConfigurationManager();
    public static ConfigurationManager ConfigurationManager => _configurationManager;
}

public class ConfigurationManager
{
    public object this[string value]
    {
        get => new object();
        set => // set something
    }
}

Now you can call Utilities.ConfigurationManager["someKey"] using indexer notation.

Solution 3 - C#

As a work-around, you can define an instance indexer on a singleton/static object (say that ConfigurationManager is a singleton, instead of being a static class):

class ConfigurationManager
{
  //private constructor
  ConfigurationManager() {}
  //singleton instance
  public static ConfigurationManager singleton;
  //indexer
  object this[string name] { ... etc ... }
}

Solution 4 - C#

I was also in need (well, more like nice-to-have) of an static indexer to store attributes, so I figured out a somewhat awkward workaround:

Within the class you want to have an static indexer (here: Element), create a subclass of the same name + "Dict". Give it a readonly static as instance of said subclass, and then add your desired indexer.

Last, add the class as static import (hence the subclass to only expose the static field).

import static Element.ElementDict;

public class Element {
	// .... 
	private static readonly Dictionary<string, object> elemDict = new Dictionary<string, object>();
	public class ElementDict {
		public readonly static ElementDict element = new ElementDict();
		public object this[string key] {
			get => elemDict.TryGetValue(key, out object o) ? o : null;
			set => elemDict[key] = value;
		}
	}
}

and then you can use it either capitalized as Type, or without as dictionary:

var cnt = element["counter"] as int;
element["counter"] = cnt;

But alas, if one were to actually use object as "value"-Type, then the below would be still shorter (at least as declaration), and also provide immediate Typecasting:

public static T load<T>(string key) => elemDict.TryGetValue(key, out object o) ? (T) o : default(T);
public static void store<T>(string key, T value) => elemDict[key] = value;

var cnt = Element.load<int>("counter");
Element.store("counter", cnt);

Solution 5 - C#

With the newer constructs in C# 6, you might simplify the singleton pattern with a property expression body. For instance, I used the following shortcut which works nicely with code-lense:

public static class Config
{
   public static NameValueCollection Get => ConfigurationManager.AppSettings;
}

It has the added benefit of being find-replace-able for upgrading older code and unifying your application settings access.

Solution 6 - C#

The this keyword refers to the current instance of the class. Static member functions do not have a this pointer. The this keyword can be used to access members from within constructors, instance methods, and instance accessors.(retrieved from msdn). Since this references an instance of the class it conflicts with the nature of static, since static isn't associated with an instance of the class.

One workaround would be the following which allows you to use the indexer against a private Dictionary so you only need to create a new instance and you access the static part.

    public class ConfigurationManager 
{
    public ConfigurationManager()
    {
        // TODO: Complete member initialization
    }
    public object this[string keyName]
    {
        get
        {
                return ConfigurationManagerItems[keyName];
        }
        set
        {
                ConfigurationManagerItems[keyName] = value;
        }
    }
    private static Dictionary<string, object> ConfigurationManagerItems = new Dictionary<string, object>();        
}

This allows you to skip the whole accessing a member of the class and just create an instance of it and index it.

    new ConfigurationManager()["ItemName"]

Solution 7 - C#

The reason is because it is quite hard to understand what exactly you are indexing with a static indexer.

You say that the code would benefit from a static indexer, but would it really? All it would do is change this:

ConfigurationManager.editProperty(name, value);
...
value = ConfigurationManager.getProperty(name)

Into this:

ConfigurationManager[name] = value
...
value = ConfigurationManager[name]

which does not make the code better in any way; it is not smaller by many lines of code, it isn't easier to write thanks to autocomplete and it is less clear, as it hides the fact that you are getting and setting something you call 'Property' and it actually forces the reader to go read the documentation on what exactly the indexer returns or sets, because it is in no way obvious that it is a property that you are indexing for, while with both:

ConfigurationManager.editProperty(name, value);
...
value = ConfigurationManager.getProperty(name)

You can read it out loud and immediately understand what the code does.

Remember that we want to write code that is easy (= fast) to understand, not code that is fast to write. Do not mistake the speed at which you can lay down the code with the speed at which you complete projects.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMalfistView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#JulietView Answer on Stackoverflow
Solution 3 - C#ChrisWView Answer on Stackoverflow
Solution 4 - C#DW.comView Answer on Stackoverflow
Solution 5 - C#vGHazardView Answer on Stackoverflow
Solution 6 - C#lamorachView Answer on Stackoverflow
Solution 7 - C#pieView Answer on Stackoverflow