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".
I think the design you've posted is the way to go, with the one difference that I would define an interface:
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.
Try explicitly implemented interfaces, as shown at the 2nd way proposed in a reply here: Named indexed property in C#?
This is not technically the correct way to use StackOverflow, but I found your idea so useful that I extended it. I thought it would save people time if I posted what I came up with and an example of how to use it.
First, I needed to be able to support get-only and set-only properties, so I made a slight variation of your code for these scenarios:
Get and Set (very minor changes):
Get Only:
Set Only:
Example
Here's a simple usage example. I inherit from Collection and create a named indexer, as Jon Skeet called it. This example is intended to be simple, not practical:
ExampleCollection in the Wild
This hastily constructed unit test shows how it looks when you ExampleCollection in a project:
Finally, if you want to use the get-only and set-only versions, that looks like this:
Or:
In both cases, the result works exactly the way you would expect a get-only/set-only property to behave.
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.
After some research, I came up with a slightly different solution that better fitted my needs. The example is a little concocted, but it does suit what I need it to adapt it to.
Usage:
And the code to make it work:
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 toItem
when you build your app. The C# compiler only knows how to read this one, so if you write a "named indexer" calledTarget
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)
andvoid set_Target(int, ...)
methods. Sucks.