views:

125

answers:

4

In C# I find indexed properties extremely useful. For example:

var myObj = new MyClass();
myObj[42] = "hello"; 
Console.WriteLine(myObj[42]);

However as far as I know there is no syntactic sugar to support fields that themselves support indexing (please correct me if I am wrong). For example:

var myObj = new MyClass();
myObj.field[42] = "hello"; 
Console.WriteLine(myObj.field[42]);

The reason I need this is that I am already using the index property on my class, but I have GetNumX(), GetX(), and SetX() functions as follows:

public int NumTargetSlots {  
    get { return _Maker.NumRefs; }  
}
public ReferenceTarget GetTarget(int n) {
    return ReferenceTarget.Create(_Maker.GetReference(n));
}
public void SetTarget(int n, ReferenceTarget rt) {
    _Maker.ReplaceReference(n, rt._Target, true);
}

As you can probably see exposing these as one indexable field property would make more sense. I could write a custom class to achieve this every time I want the syntactic sugar but all of the boilerplate code just seem unnecessary.

So I wrote a custom class to encapsulate the boilerplate and to make it easy to create properties that can be indexed . This way I can add a new property as follows:

public IndexedProperty<ReferenceTarget> TargetArray  {
    get { 
       return new IndexedProperty<int, ReferenceTarget>(
           (int n) => GetTarget(n), 
           (int n, ReferenceTarget rt) => SetTarget(n, rt));
       }
}

The code for this new IndexedProperty class looks like:

public class IndexedProperty<IndexT, ValueT>
{
    Action<IndexT, ValueT> setAction;
    Func<IndexT, ValueT> getFunc;

    public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction)
    {
        this.getFunc = getFunc;
        this.setAction = setAction;
    }

    public ValueT this[IndexT i]
    {
        get {
            return getFunc(i);
        }
        set {
            setAction(i, value);
        }
    }
}

So my question is: is there a better way to do all of this?

Well to be specific, is there a more idiomatic way in C# to create an indexable field property, and if not how could I improve my IndexedProperty class?

EDIT: After further research, Jon Skeet calls this a "named indexer".

+3  A: 

Well, the simpliest is to have the property return an object which implements IList.

Remember that just because it implements IList doesn't mean it's a collection itself, just that it implements certain methods.

James Curran
Hmmmm... I like this line of thinking. However the problem I have with IList is the members like "Add", "Remove", etc. It would be kind of dishonest to expose the property as an IList just ignore them. Perhaps there is another interface that would make more sense?
cdiggins
You can have a read-only `IList`. `Array` and `ReadOnlyCollection<T>` both implement `IList`; both these classes throw exceptions when you call `Add`, `Remove`, etc.
Tim Robinson
But its not "a read-only" IList. You can set elements at particular indexes. It is distinctly not a list, but neither is Array. However, I did not know that "Array" implemented "IList"! That seems pretty obviously a bad design decision. Of course, it would now classify as "idiomatic C#". I'm on the fence.
cdiggins
I've always thought that there should be an interface in the framework specifically for this use case that doesn't imply collection semantics - `IIndexable<T>`, maybe? Even though `IList` specifically gives you an out (via the `IsReadOnly` property), it does imply collection semantics simply by virtue of inheriting from `ICollection`.
Dathan
If the collection should not be changed by the outside world, use IEnumerable<T>.If the collection will be changed by the outside world, use ICollection<T>.If indexed access is required, use IList<T>.
Gage
There needs to be a term to distinguish between a collection for which items cannot be added or removed and a collection for which the items it contains cannot be modified. Unfortunately, we use the term "read-only" to describe both.
Dr. Wily's Apprentice
+2  A: 

Why not have your class inherit IList then you can just use the index and add your own properties to it. Although you will still have the Add and Remove functions its not dishonest not to use them. Plus you may find it useful to have them furthur down the road.

For more information about Lists and Arrays check out: http://stackoverflow.com/questions/2975426/which-is-better-to-use-array-or-list

EDIT:

MSDN has an article on index properties you may want to take a look at. Doesn't seem to complicated just tedious.

http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx

There is another option where you can create an alternative Add method but depending on the type of object your add method may not always be called. Explained here:

http://stackoverflow.com/questions/580202/how-to-i-override-listts-add-method-in-c

EDIT 2: Similar to the first post

Why don't you have a hidden list object in your class and then just create your own methods for obtaining the data. That way Add and Remove aren't seen and the list is already indexed.

Also what do you mean by "named indexer" are you looking for the equivalent of the row["My_Column_Name"]. Theres an MSDN article I found that may be useful as it seems to show the basic way to implement that property.

http://msdn.microsoft.com/en-us/library/146h6tk5.aspx

class Test
    {
        private List<T> index;
        public T this[string name]{ get; set; }
        public T this[int i]
        {
            get
            {
                return index[i];
            }
            set
            {
                index[i] = value;
            }
        }
    }
Gage
I want my types to indicate the intended usage. The problem with using an IList, is that I need to add documentation for any consumers of my code, saying yes I implement IList, but you shouldn't use "Add" or "Remove". I prefer self-documenting code.
cdiggins
@cdiggins I'm of two minds about this. I agree with you in principle, but for `IList` and `ICollection`, the framework documentation specifically notes that implementing classes should throw `NotSupportedException` upon a call to the `Add`, `Clear`, `Insert`, `Remove`, or `RemoveAt` methods if the collection is read-only (i.e., if the `IsReadOnly` property returns true). It's reasonable to expect your users to use your code in a way that conforms with the documentation, so I think using `IList` is reasonable - and is actually the recommended way to do what you want.
Dathan
Hey Gage, I had already linked to that MSDN article in my post.
cdiggins
lol Didn't notice that my bad
Gage
+2  A: 

I think the design you've posted is the way to go, with the one difference that I would define an interface:

public interface IIndexed<IndexT, ValueT>
{
    ValueT this[IndexT i] { get; set; }
}

And for common cases, I would use the class you put in the original question (which would implement this interface).

It would be nice if the base class library provided a suitable interface for us, but it doesn't. Returning an IList here would be a perversion.

qstarin
+1  A: 

This doesn't answer your question, but it's interesting to note that CIL supports making properties like you've described - some languages (For example, F#) will allow you to define them in such a way too.

The this[] indexer in C# is just a specific instance of one of these which is renamed to Item when you build your app. The C# compiler only knows how to read this one, so if you write a "named indexer" called Target in an F# library, and try to use it in a C#, the only way you could access the property is via the ... get_Target(int) and void set_Target(int, ...) methods. Sucks.

Mark H