7

Is it possible to have something like this in C#? I am not very sure:

class Library
{
    public string Books[string title]
    {
        get{return this.GetBookByName(string title);}
    }

    public DateTime PublishingDates[string title]
    {
        get{return this.GetBookByName(string title).PublishingDate;}
    }
}

So it could be used as such:

myLibrary.Books["V For Vendetta"]
myLibrary.PublishingDates["V For Vendetta"] = ...

So my complete member methods that I need to implement in my framework (by calling them) are:

GetCustomStringValue (key)
GetCustomIntValue (key)
GetCustomBoolValue (key)
GetCustomFloatValue (key)
SetCustomStringValue (key)
SetCustomIntValue (key)
SetCustomBoolValue (key)
SetCustomFloatValue (key)

I want to implement them cleaner in my own type.

Joan Venge
  • 315,713
  • 212
  • 479
  • 689
  • 1
    What is the point of this? Why can’t you just use plain old normal methods for getting and setting? – Timwi Jan 18 '11 at 23:53
  • Just thought someone might be able to come up with a better solution. It doesn't seem elegant to have it this way, but just experiments. – Joan Venge Jan 19 '11 at 00:17
  • Multiple indexed properties can be useful for WPF binding – Maxence Jan 08 '21 at 10:58

5 Answers5

12

The only way you could do this would be to have Books be a property that returns a type that has its own suitable indexer. Here's one possible approach:

public class Indexer<TKey, TValue>
{
    private Func<TKey, TValue> func;

    public Indexer(Func<TKey, TValue> func)
    {
        if (func == null)
            throw new ArgumentNullException("func");

        this.func = func;
    }

    public TValue this[TKey key]
    {
        get { return func(key); }
    }
}

class Library
{
    public Indexer<string, Book> Books { get; private set; }
    public Indexer<string, DateTime> PublishingDates { get; private set; }

    public Library()
    {
        Books = new Indexer<string, Book>(GetBookByName);
        PublishingDates = new Indexer<string, DateTime>(GetPublishingDate);
    }

    private Book GetBookByName(string bookName)
    {
        // ...
    }

    private DateTime GetPublishingDate(string bookName)
    {
        return GetBookByName(bookName).PublishingDate;
    }
}

But you should seriously consider providing an implementation of IDictionary<,> instead of using this approach, as it will allow other nifty stuff, like enumeration of key-value pairs, etc.

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • @Joan: No problem. Make sure you get the latest copy since I just fixed Books/PublishingDates being public fields -- now they're properties. – cdhowie Jan 18 '11 at 23:42
  • Thanks man, I am wondering if this would also let me to set the index values? Like myLib.Books["name"] = new Book (); etc? – Joan Venge Jan 18 '11 at 23:45
  • @Joan Venge: If you want to do that, change the class to also take an `Action`, store it in a field, and then add a setter: `set { action(key, value); }`. Alternatively, make the type an interface instead and get the `Library` to use a private implementation of the interface. – Ani Jan 18 '11 at 23:51
  • This solution is ridiculous. A whole separate class and a redundant delegate invocation just for the weird goal of being able to use square brackets instead of round ones. Just use normal methods, they’re just fine the way they are... – Timwi Jan 18 '11 at 23:55
  • @cdhowie: Thanks for your explanation. Actually I would really appreciate if you could show me in code in your sample. Basically my get and set methods are different only. – Joan Venge Jan 19 '11 at 00:16
  • @cdhowie: I did it based on your explanation. – Joan Venge Jan 19 '11 at 00:24
  • @Joan: Is there a reason you can't use IDictionary? – cdhowie Jan 19 '11 at 00:46
  • @cdhowie: Way to go for misrepresenting what I said. – Timwi Jan 19 '11 at 01:00
  • @cdhowie: You mean instead of your Indexer type? – Joan Venge Jan 19 '11 at 01:43
  • @Joan: Yes. The indexer type is just a hack -- you should consider using a type that better represents the data. Since Library.Books presumably represents a dictionary, using the type `IDictionary` for the property would probably be a good idea. You would then set the property to something that implements that interface -- either `Dictionary` or a custom type. – cdhowie Jan 19 '11 at 15:01
2

C# doesn't support it, unfortunately. It only recognises the this[] property, which is really just an indexable property named Item when compiled. The CLI supports any number of indexable properties though, and that can be reflected in some other languages like F#, where you can define your own.

Even when you do define your own in CIL or otherwise though, you still can't call them from C# like you would want to, you need to make a manual call to get_Books(index); for a property named Books. All properties are just syntactic sugar for method calls like that. C# only recognises the property named Item as indexable.

Mark H
  • 13,797
  • 4
  • 31
  • 45
  • this is one of my arguments for why VB.NET is better than C# -- it supports parameterised (indexed) properties. (Don't worry, I have just as many reasons for why C# is better than VB.NET!) – AAT Jan 18 '11 at 23:55
  • @AAT, can you please post a link to some code samples for the vb.net? I am curious about it. – Joan Venge Jan 19 '11 at 01:44
  • @Joan -- it's not difficult: you just define a property like "Public Property PropertyName(paramName as ParamType) as PropertyType". You can use any type or number of parameters. – AAT Feb 05 '11 at 12:37
2

In C#, indexers have to be called this (see http://msdn.microsoft.com/en-us/library/aa664459(v=VS.71).aspx). You can overload indexers, but remember that C# doesn't allow overloading based on return type only. So, whereas you can have:

public int this[int i]
public string this[string s]

You couldn't have:

public int this[int i]
public string this[int i]

The .NET class library design guidelines recommend having only one indexer per class.

So in your case, there's no way to do what you're asking using indexers only.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
0

Why not just use methods?

class Library 
{     
    public string Books(string title)
    {         
        return this.GetBookByName(title);
    }      

    public DateTime PublishingDates(string title)
    {         
        return this.GetBookByName(title).PublishingDate;   
    } 
} 
Ed S.
  • 122,712
  • 22
  • 185
  • 265
Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
  • I could but this specific example I have has both Get and Set methods that work like this, so I thought it would be cleaner if I had one index property for each that does both, instead of 2 methods. They differ in return types and the rest is the same, you pass a key, get a value of type int, bool, float or string. – Joan Venge Jan 18 '11 at 23:32
  • 2
    I agree that it doesn't compile, but @Kirk's point is still valid. – Babak Naffas Jan 18 '11 at 23:33
  • 1
    You need to get rid of the gets! – Richard Marskell - Drackir Jan 18 '11 at 23:33
  • 1
    It compiles just fine the way it is now — and it addresses the actual problem with the question. Upvoting! – Timwi Jan 18 '11 at 23:57
  • Thanks all for the edits; I hope you get my point even if the execution was lacking. No need for OP to make things more complicated than they need to be... – Kirk Broadhurst Jan 19 '11 at 00:10
0

Just an exercice of style using multiple indexer parameters:

enum Target
{
    Books,

    PublishingDates
}

class Book
{
    public string Title { get; set; }

    public DateTime PublishingDate { get; set; }
}

class Library
{
    public object this[Target target, string title]
    {
        get
        {
            switch (target)
            {
                case Target.Books:
                    return GetBookByTitle(title);

                case Target.PublishingDates:
                    return GetBookByTitle(title).PublishingDate;

                default:
                    throw new ArgumentOutOfRangeException(nameof(target), 
                        target, null);
            }
        }
    }

    static Book GetBookByTitle(string title)
    {
        return new Book {Title = "V For Vendetta"};
    }
}

var book = (Book)myLibrary[Target.Books, "V For Vendetta"];
var dateTime = (DateTime)myLibrary[Target.PublishingDates, "V For Vendetta"];
Maxence
  • 12,868
  • 5
  • 57
  • 69