Indexed Properties

brentnewbury

Newcomer
Joined
Oct 27, 2004
Messages
15
Location
Nottingham, UK
Hiya all,

I'm not a newbie to .NET by any means, but I've obviously missed something big here.

For some reason I can't seem to get indexed properties to work! Well, not in the way I want (and think to be most logical), I can get them to work like:
Code:
public int this[int index]
{
    get { ... }
    set { ... }
}
but not like this:
Code:
public int Item[int index]
{
    get { ... }
    set { ... }
}
I know I seem dumb, but to me it doesn't seem logical to have the item itself manage it's own collection. To me it seems more logical to have the owning class manage a collection of its subordinates.

Here's the code I would like to use:
Code:
using System;
using System.Collections.Generic;

public class Collector
{
	private List<Item> _collection;

	public Item Items[int index]
	{
		get { return this._collection[index]; }
		set { this._collection[index] = (Item)value; }
	}

	// Other members, properties, and methods

	public class Item
	{
		// Members, properties, and methods
	}
}
Do any of you have any advice?


Regards,


Brent
 
Last edited:
The first thing that comes to mind is exposing the collection:
C#:
using System;
using System.Collections.Generic;

public class Collector
{
	private List<Item> _collection;

	public List<Item> Items
	{
		get { return this._collection; }
	}

	// Other members, properties, and methods
	public class Item
	{
		// Members, properties, and methods
	}
}

-ner
 
Nerseus said:
The first thing that comes to mind is exposing the collection:
C#:
using System;
using System.Collections.Generic;

public class Collector
{
	private List<Item> _collection;

	public List<Item> Items
	{
		get { return this._collection; }
	}

	// Other members, properties, and methods
	public class Item
	{
		// Members, properties, and methods
	}
}

-ner
Well bloody hell! I've just been searching for bloody hours trying to find stuff about named indexers (e.g. System.Runtime.CompilerServices.CSharp.IndexName("..."), System.Runtime.CompilerServices.IndexerName("..."), etc).

I'm bloody annoyed with myself! But why don't Microsoft just have named indexers, why is this keyword compulsory? Are they trying to enforce a wierred and wonderful object oriented ideal?
 
It depends on what you want to do, using the indexer on "this" is valid if that's what you wanted - but you described something else.

Suppose you had this code to create a Collector:
Collector c = new Collector();

Now, if you wanted to index into c itself, then you'd use the "this" indexer:
C#:
// Inside the class...
    public int this[int index]
    {
        get { ... }
        set { ... }
    }

// The code using the class:
Collector c = new Collector();
c[0].Field = ""; // The [0] uses the indexer

If you wanted to have a property named Items, then you'd use the property:
C#:
// Inside the class...
    public List<Item> Items
    {
        get { ... }
    }

// The code using the class:
Collector c = new Collector();
c.Items[0].Field = "";

You can, of course, implement both if you wanted to. That would make it look like the "Items" property was a "default" of the class (so that c[0] and c.Items[0] are the same thing). It really depends on what you want.

Since your class is called Collector, I'm guessing its purpose is solely to collect things - in that case I'd use the indexer and not expose an Items property. For that matter, if you don't really need your own class, you could just declare instances of List<Item> as needed.

-ner
 
Nerseus said:
It depends on what you want to do, using the indexer on "this" is valid if that's what you wanted - but you described something else.

Suppose you had this code to create a Collector:
Collector c = new Collector();

Now, if you wanted to index into c itself, then you'd use the "this" indexer:
C#:
// Inside the class...
    public int this[int index]
    {
        get { ... }
        set { ... }
    }

// The code using the class:
Collector c = new Collector();
c[0].Field = ""; // The [0] uses the indexer

If you wanted to have a property named Items, then you'd use the property:
C#:
// Inside the class...
    public List<Item> Items
    {
        get { ... }
    }

// The code using the class:
Collector c = new Collector();
c.Items[0].Field = "";

You can, of course, implement both if you wanted to. That would make it look like the "Items" property was a "default" of the class (so that c[0] and c.Items[0] are the same thing). It really depends on what you want.

Since your class is called Collector, I'm guessing its purpose is solely to collect things - in that case I'd use the indexer and not expose an Items property. For that matter, if you don't really need your own class, you could just declare instances of List<Item> as needed.

-ner
The problem with public List<Item> Items {} is that you expose the entire underlying collection. Declaring the collection as readonly seems to have problems of it's own.

I don't understand why Microsoft just doesn't expand System.Runtime.CompilerServices.IndexerName("...") for use with all languages under the .NET framwork and not just that ones that don't natively support indexers.

There must be some good reason. Atleast I hope there is. Come on Microsoft, I want to know why, give me the details, I want to understand!
 
Eureaka!

The best solution I've come up with so far is:
C#:
public IList<Item> Items
{
	get { return this._collection.AsReadOnly(); }
}
I've attached a working solution (see bellow). The idea behind this is to stop any abuse due to exposing the underlying collection, like so:
C#:
public List<Item> Items
{
	get { return this._collection; }
}

Give me a shout if you see any problems :). (Apart from the run-time checking)


- Brent
 

Attachments

The whole point of indexers is to create classes which behave in the same manner (or very similar) as an array. With this in mind, it doesn't make much sense to create an indexer property for an object that doesn't act like an collection itself, but rather simply contains a collection.

I understand that you want to create a property that acts like an array, but using an indexer in that fasion means you lose a whole lot of functionality. When you return an indexed object, you can perform a foreach loop on it (assuming that it is enumerable), you can store a reference to the collection if you need to access it repeatedly, and if the collection programmer so desires, you can modify the collection by adding and removing items and call any other function that the programmer decided to implement. When you use a VB style indexed property, all you get is a property get and a property set.
 
marble_eater said:
The whole point of indexers is to create classes which behave in the same manner (or very similar) as an array. With this in mind, it doesn't make much sense to create an indexer property for an object that doesn't act like an collection itself, but rather simply contains a collection.

I understand that you want to create a property that acts like an array, but using an indexer in that fasion means you lose a whole lot of functionality. When you return an indexed object, you can perform a foreach loop on it (assuming that it is enumerable), you can store a reference to the collection if you need to access it repeatedly, and if the collection programmer so desires, you can modify the collection by adding and removing items and call any other function that the programmer decided to implement. When you use a VB style indexed property, all you get is a property get and a property set.
I have a Article Class that has a list of Pages. To me, myArticle[0]; seems to completed ignore the relationship between an Article and it's Pages. myArtical.Page[0]; seems to model the relationship quite well between an Article and it's Pages..

I'm finding it difficult to articulate what I have in my head. Regardless of the programatical limitation of the framework, this seems the better model of the problem space. Personally, I'd say Microsoft was wrong in it's implimentation.

Just to reitterate myArticle[0]; seems like an array of Articles, whereas myArticle.Page[0]; is definately a collection of pages that are attached to a particular instance of an Article.
 
First of all, this is not a limitation of the .Net framework, but a limitation of C#. Visual Basic can do parameterized properties, which are like indexers on steroids, but this was left out of C# intentionally. Why? Let's look at the hypothetical Article class.

Needless to say, the Article class represents an article. An article is composed of pages, but it also has attributes that apply to an article as a whole. In the world of programming, these would be represented as properties, such as the TypeOfPaper, Title, and TypeOfBinding properties. Pages are represented by the Page class, but how we deal with the Page objects is a matter for debate.

Since C# does not support parametized properties, we could use a function to obtain page objects: the hypothetical GetPage(int pageNumber) function. This representation implies that each page is a separate object relationally dependant upon only the Article class that contains it. While there is nothing really wrong with this, there are other ways of looking at it that Microsoft prefers.

In the real world, a group of objects has its own inherent properties, which are separate from the properties of whatever may contain the group. Microsoft has designed C# with the intent that programmers will see groups in this manner. When we apply this concept to our program, we should represent the group of pages itself as a class, which would be the hypothetical PageCollection class. The PageCollection class has a Count property, an Add method to add pages, and a Remove method to remove pages, allowing us to treat not only the Article and each Page as an object, but to also treat the group of Pages as an object. Our Article class not only exposes but internally manages its pages through a PageCollection class. This is just a step towards an object oriented point of view.

The entire .Net framework follows this design pattern. When you access the items in a ListBox, you don't write ListBox[3] to get the fourth item. You write ListBox.Items[3]. And the items property returns a collection rather than a single item, so that you can also do things like ListBox.Items.Clear(). If you want to access pages by number, but don't want to treat the pages as a group, I would advise that you simply use the GetPage(int pageIndex) method instead of a property.
 
marble_eater said:
First of all, this is not a limitation of the .Net framework, but a limitation of C#. Visual Basic can do parameterized properties, which are like indexers on steroids, but this was left out of C# intentionally. Why? Let's look at the hypothetical Article class.

Needless to say, the Article class represents an article. An article is composed of pages, but it also has attributes that apply to an article as a whole. In the world of programming, these would be represented as properties, such as the TypeOfPaper, Title, and TypeOfBinding properties. Pages are represented by the Page class, but how we deal with the Page objects is a matter for debate.

Since C# does not support parametized properties, we could use a function to obtain page objects: the hypothetical GetPage(int pageNumber) function. This representation implies that each page is a separate object relationally dependant upon only the Article class that contains it. While there is nothing really wrong with this, there are other ways of looking at it that Microsoft prefers.

In the real world, a group of objects has its own inherent properties, which are separate from the properties of whatever may contain the group. Microsoft has designed C# with the intent that programmers will see groups in this manner. When we apply this concept to our program, we should represent the group of pages itself as a class, which would be the hypothetical PageCollection class. The PageCollection class has a Count property, an Add method to add pages, and a Remove method to remove pages, allowing us to treat not only the Article and each Page as an object, but to also treat the group of Pages as an object. Our Article class not only exposes but internally manages its pages through a PageCollection class. This is just a step towards an object oriented point of view.

The entire .Net framework follows this design pattern. When you access the items in a ListBox, you don't write ListBox[3] to get the fourth item. You write ListBox.Items[3]. And the items property returns a collection rather than a single item, so that you can also do things like ListBox.Items.Clear(). If you want to access pages by number, but don't want to treat the pages as a group, I would advise that you simply use the GetPage(int pageIndex) method instead of a property.
Damn! I finally get what you mean. Like a prat I just didn't think about proper collection classes! (I have been awake for 28 hours :yawn: :)

Thank you for the advise.
 
brentnewbury said:
Damn! I finally get what you mean. Like a prat I just didn't think about proper collection classes! (I have been awake for 28 hours :yawn: :)

Thank you for the advise.
Just tried it then realise the reason why I didn't use this method; I don't want anything outside the Article class to be able to add/remove pages. The reason for this is that the Article constructor takes one parameter; the Article ID. From there the constructor initialises the class and sets up pages etc. All I need to do is get pages, so to be honest, I think I may just use a standard public Page GetPage(int pageNumber) {}.

Thanks alot for all your help. Like I said, I didn't get any sleep last night. :yawn:
:D
 
Back
Top