56

Why can't you create a generic indexer in .NET?

the following code throws a compiler error:

public T this<T>[string key]
{
    get => /* Return generic type T. */
}

Does this mean you can't create a generic indexer for a generic member collection?

unknown6656
  • 2,765
  • 2
  • 36
  • 52
Igor Zelaya
  • 4,167
  • 4
  • 35
  • 52
  • What usage syntax would you want? obj[i] looks like it would conflict with comparing obj to T, obj[i] is better, but I find it hard to read. My preference is obj[i], but not by much. – Simon Buchan Jan 30 '09 at 08:16
  • I fail to see why we wouldn't just want to use a generic method. It's cleaner, IMO, as adding generics to indexes would lead to difficult-to-read code. – William Jan 09 '13 at 00:19
  • If it was implemented then `` would be preceding to `this` as `public T this[string key]` – Ken Kin May 08 '19 at 02:15

7 Answers7

36

Here's a place where this would be useful. Say you have a strongly-typed OptionKey<T> for declaring options.

public static class DefaultOptions
{
    public static OptionKey<bool> SomeBooleanOption { get; }
    public static OptionKey<int> SomeIntegerOption { get; }
}

Where options are exposed through the IOptions interface:

public interface IOptions
{
    /* since options have a default value that can be returned if nothing's
     * been set for the key, it'd be nice to use the property instead of the
     * pair of methods.
     */
    T this<T>[OptionKey<T> key]
    {
        get;
        set;
    }

    T GetOptionValue<T>(OptionKey<T> key);
    void SetOptionValue<T>(OptionKey<T> key, T value);
}

Code could then use the generic indexer as a nice strongly-typed options store:

void Foo()
{
    IOptions o = ...;
    o[DefaultOptions.SomeBooleanOption] = true;
    int integerValue = o[DefaultOptions.SomeIntegerOption];
}
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
19

I don't know why, but indexers are just syntactic sugar. Write a generic method instead and you'll get the same functionality. For example:

   public T GetItem<T>(string key)
   {
      /* Return generic type T. */
   }
davogones
  • 7,321
  • 31
  • 36
  • Note. Indexers are also used for the dictionary collection initialization (e.g. `new Dictionary { ["key"] = 3 }` ). You cannot use an alternative for this I don't think. – AgentM Jun 21 '23 at 09:40
17

Properties can't be generic in C#2.0/3.0 so therefore you can't have a generic indexer.

Kev
  • 118,037
  • 53
  • 300
  • 385
  • 4
    Do you have a link to anything on that front? I can't see it in the "new features" document. – Jon Skeet Jan 30 '09 at 08:18
  • 2
    mmmm...I think I found the link you are looking for. http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=352188 What It seems incredible to me is that Microsoft says it is not a useful feature but have already implemented some workaround extenders(i.e.DataRowExtender) – Igor Zelaya Jan 30 '09 at 08:35
  • 3
    year 2016, c# 6 there is no generic indexer. https://github.com/dotnet/roslyn/issues/523 – Nuri YILMAZ May 03 '16 at 18:19
9

You can; just drop the <T> part from your declaration and it will work fine. i.e.

public T this[string key]
{
   get { /* Return generic type T. */ }
}

(Assuming your class is generic with a type parameter named T).

Greg Beech
  • 133,383
  • 43
  • 204
  • 250
  • 4
    @Igor - that's why I said "Assuming your class is generic with a type parameter named T". The question did not indicate that the type parameter should be different from the container (and in any case this would be a very unusual design!). – Greg Beech Jan 30 '09 at 08:01
  • 2
    @Greg...how unusual can it be? Take as an example the Field<> generic method in DataRow. What they did is unboxing through a generic method since it is impossible through a generic indexer. – Igor Zelaya Jan 30 '09 at 08:06
  • @Igor - it's the implication that's the issue. An indexer implies direct access to the actual object at that index, not a converted form of that object, whereas a method has no such implication. And just think of the syntax: someObject["abc"] isn't very pretty. – Greg Beech Jan 30 '09 at 08:13
3

I like the ability to have an indexer without handing out a direct reference to the "indexed" item. I wrote a simple "call back" Indexer class below ...

R = the returned type from the indexer P = the passed type into the indexer

All the indexer really does is pass the operations to the deployer and allow them to manage what actually occurs and gets returned.

public class GeneralIndexer<R,P>
    {
        // Delegates
        public delegate R gen_get(P parm);
        public delegate void gen_set(P parm, R value);
        public delegate P[] key_get();

        // Events
        public event gen_get GetEvent;
        public event gen_set SetEvent;
        public event key_get KeyRequest;

        public R this[P parm]
        {
            get { return GetEvent.Invoke(parm); }
            set { SetEvent.Invoke(parm, value); }
        }

        public P[] Keys
        {
            get
            {
                return KeyRequest.Invoke();
            }
        }

    }

To use it in a program or class:

private GeneralIndexer<TimeSpan, string> TimeIndex = new GeneralIndexer<TimeSpan,string>();

{
            TimeIndex.GetEvent += new GeneralIndexer<TimeSpan, string>.gen_get(TimeIndex_GetEvent);
            TimeIndex.SetEvent += new GeneralIndexer<TimeSpan, string>.gen_set(TimeIndex_SetEvent);
            TimeIndex.KeyRequest += new GeneralIndexer<TimeSpan, string>.key_get(TimeIndex_KeyRequest);

}

works like a champ especially if you want to monitor access to your list or do any special operations when something is accessed.

3

The only thing I can think of this can be used is something along these lines:

var settings = ConfigurationSection.AppSettings;
var connectionString = settings<string>["connectionString"];
var timeout = settings<int>["timeout"];

But this doesn't actually buy you anything. You've just replaced round parentheses (as in (int)settings["timeout"]) with angle brackets, but received no additional type safety as you can freely do

var timeout = settings<int>["connectionString"];

If you have something that's strongly but not statically typed, you might want to wait until C# 4.0 with its dynamic keyword.

Community
  • 1
  • 1
Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288
  • 7
    Well, actually the settings syntax is preferable. C-style casts often forces you to include additional parenthesis. – John Leidegren Jan 30 '09 at 08:17
  • 1
    @John I agree. I think that is nicer than creating method accessors like Filed<> and SetFiled<> in DataRow Class for example. – Igor Zelaya Jan 30 '09 at 08:24
  • It would add type safety if reading `settings["foo"]` would store the last thing written to `settings["foo"]`, without regard for whether anything had been written to `settings["foo"]` before or since. The biggest thing to note about such a design is that after `settings["foo"] = someSiameseCat`, an attempt to read `settings["foo"] would not see that cat, whereas after non-generic `settings["foo"] = someSiameseCat;` `myAnimal = (Animal)settings["SiameseCat"] would see it. – supercat Sep 16 '13 at 16:47
0

In recent C-sharp you can declare the return type as "dynamic". This is the same as using "object" except that the C# runtime will allow you to use it in code as if it was the type you think it is and then check at run-time to be sure you were right...