6

Is it possible to create a generic class/method where the type must have an indexer?

My thought was to make the following two extension methods work on any type which uses an indexer for getting and setting values, but can't seem to find anything about it. Only stuff about making the indexer itself generic, which is not what I'm after...

    public static T GetOrNew<T>(this HttpSessionStateBase session, string key) where T : new()
    {
        var value = (T) session[key];
        return ReferenceEquals(value, null) 
            ? session.Set(key, new T()) 
            : value;
    }

    public static T Set<T>(this HttpSessionStateBase session, string key, T value)
    {
        session[key] = value;
        return value;
    }
Svish
  • 152,914
  • 173
  • 462
  • 620

2 Answers2

11

There is no way to apply a generic constraint that the generic type argument have an indexer (or any operator). The best that you can do is create an interface with that restriction and restrict the generic argument to implementing that interface.

Servy
  • 202,030
  • 26
  • 332
  • 449
3

Servy has it. You can't require that the type have an indexer, but you can require that the type implements an interface that exposes an indexer. IList and IDictionary, and their generic counterparts, are the primary built-in interfaces that expose indexers, with integer and string index values respectively. Unfortunately, a few built-in types, like HttpSessionState, expose indexers of their own accord without implementing an interface identifying that they do so.

You can also define your own indexed interface to apply to any type you control:

public interface IIndexable<TKey, TVal>
{
   TVal this[TKey key]{get;}
}

So, best-case, you could implement three overloads of these methods:

public static TElem GetOrNew<TList, TElem>(this TList collection, int key) 
    where TList : IList<TElem>, TElem:new()
{
    ...
}

public static TElem Set<TList, TElem>(this TList collection, int key, TElem value) 
    where TList: IList<TElem>
{
    ...
}

public static TVal GetOrNew<TDict, TKey, TVal>(this TDict collection, TKey key) 
    where TDict : IDictionary<TKey, TVal>, TVal : new()
{
    ...
}

public static TVal Set<TDict, TKey, TVal>(this TDict collection, TKey key, TVal value) 
    where TDict : IDictionary<TKey, TVal>
{
    ...
}

public static TVal GetOrNew<TColl, TKey, TVal>(this TDict collection, TKey key) 
    where TColl : IIndexable<TKey, TVal>, TVal: new()
{
    ...
}

public static TVal Set<TColl, TKey, TVal>(this TDict collection, TKey key, TVal value) 
    where TColl : IIndexable<TKey, TVal>
{
    ...
}

... which would allow you to use this method set on the 90th percentile of objects with an indexer (including Array, which for practical purposes implements IList).

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • Could indeed be useful in some cases! Unfortunately `HttpSessionStateBase` only implements `ICollection` :( – Svish Oct 24 '14 at 07:46
  • Which goes to the simple answer of "this is not possible in the general case, but you can implement solutions in more specific cases". The question becomes, how many of these cases do you really care about? – KeithS Oct 24 '14 at 15:15
  • In this case, just the one. The question was asked more of curiosity than need :) – Svish Oct 24 '14 at 15:26