Creating IndexOf in a custom collection

G

Guest

I have a design question. I am creating a custom collection of products.
The unique key for the products is productId which is an integer.

By default, IndexOf(object obj), when obj is an int, would return the value
of obj because it returns the index of the item at position obj.

Thoughts I had were to let the default method perform the default behavior
and then create an overload as IndexOf(int index, ProductSearchCriteria
criteria) where ProductSearchCriteria is an enum with { Index, ProductId }
allowing the developer calling my class to specify the meaning of the int
when calling IndexOf().

I'd appreciate any feedback and especially any better ideas.

Thanks,

Dale
 
M

Marc Gravell

Hang on...

what exactly does "IndexOf(5, blah.Index)" mean? Give me the index of the
item at index 5 [position 6]?

Do you perhaps mean the /indexer/ (this), i.e. this[5]? In which case, I
would suggest keeing the int indexer with it's general usage for
collections, and perhaps add a GetByProductID(int id), or (more generic) a
GetByKey.

In some cases, a key indexer is common (e.g. hashtable, etc), but you get
into a lot of trouble if both the key and index share a type... or are
implicitely convertible. Lots of pain.

Marc
 
G

Guest

IndexOf(5, blah.Index) means return the index of the object at position 5.
This basically mimics the default behavior and, in fact, this overload would
simply call the default overload. The real usefulness of this and the
default behavior is to return -1 if there is no position 5.

IndexOf(5, blah.ProductId) means return the index of the product whose
ProductId is 5.

I agree with you about the problems when the key is integer. I even thought
about doing away with the default behavior because consumers of my class
would probably never call it - after all, you can always do
ProductCollection.Count to see if there is a position 5 - but many other
collection methods depend on the default implementation of IndexOf and all of
those methods would have to be rewritten as well.

Unless someone else comes up with something neither of us thought of, I am
going to go with your suggestion. It seems to be the right thing to do:
leave the default behavior alone and create a new GetIndexById() method.

Thanks,

Dale
 
M

Marc Gravell

You may have misundertood the default implementation; normally, you give
IndexOf an actual item (hence object, not the index), and it tells you the
index of the first item to Equals() it. The only time IndexOf(5) would be
useful is if your collection held integers... and it would only work if 5
was in the list.

I'm assuming you are dealing with 1.1; for completeness in terms of naming /
consistency, you might also like to compare this to the
List<T>.FindIndex(Predicate<T>) method in 2.0.

Marc
 
B

Brian Gideon

Dale,

The best way of doing this is to use predicate logic. Which framework
version are you using? The reason I'm asking is because this is much
easier in 2.0. Consider the following example.

// This method would be in your custom collection.
public int FindIndex(Predicate<T> match)
{
for (int index = 0; index < collection.Count; index++)
{
if (match(collection[index])) return index;
}
return -1;
}

// This is how you would use it.
public static void Main()
{
YourCollection collection = GetReferenceToYourCollectionSomehow();

int index1 = collection.FindIndex(
delegate(YourItem item)
{
return item.Index == 5;
});

int index2 = collection.FindIndex(
delegate(YourItem item)
{
return item.ProductId == 5;
});
}

You can use the List<T> collection as an example. It already has the
FindIndex method. You can do something similar in 1.1, but you'd have
to define your own strongly-typed delegate and provide an
implementation for each type of search you want to perform (by Index,
ProductId, etc).

Brian
IndexOf(5, blah.Index) means return the index of the object at position 5.
This basically mimics the default behavior and, in fact, this overload would
simply call the default overload. The real usefulness of this and the
default behavior is to return -1 if there is no position 5.

IndexOf(5, blah.ProductId) means return the index of the product whose
ProductId is 5.

I agree with you about the problems when the key is integer. I even thought
about doing away with the default behavior because consumers of my class
would probably never call it - after all, you can always do
ProductCollection.Count to see if there is a position 5 - but many other
collection methods depend on the default implementation of IndexOf and all of
those methods would have to be rewritten as well.

Unless someone else comes up with something neither of us thought of, I am
going to go with your suggestion. It seems to be the right thing to do:
leave the default behavior alone and create a new GetIndexById() method.

Thanks,

Dale
--
Dale Preston
MCAD C#
MCSE, MCDBA


Marc Gravell said:
Hang on...

what exactly does "IndexOf(5, blah.Index)" mean? Give me the index of the
item at index 5 [position 6]?

Do you perhaps mean the /indexer/ (this), i.e. this[5]? In which case, I
would suggest keeing the int indexer with it's general usage for
collections, and perhaps add a GetByProductID(int id), or (more generic) a
GetByKey.

In some cases, a key indexer is common (e.g. hashtable, etc), but you get
into a lot of trouble if both the key and index share a type... or are
implicitely convertible. Lots of pain.

Marc
 
M

Marc Gravell

Of course, if product id is unique (original post) you could also look at
Dictionary<int, YourClass> (2.0) or Hashtable (1.1); this doesn't guarantee
the rigid insertion order, but makes for very efficient lookup by ID, and
would still support enumeration.

Marc
 
G

Guest

Well, this is getting more interesting than I'd hoped. Here's a little more
information.

I have long been a fan of custom collections over arrays. This app is in
..Net 2.0. I started out with a custom collection by extending
System.Collections.Generics.IEnumerable. But because I want to have indexers
based on specific properties of the contained class, my Generics based
collection needs to be locked into the Product type. To do that, I add
"where T : Product".

Suddenly my Generic class is no longer Generic and the whole generic
structure of having to specify <Product> seems like a burden with no benefit.

Am I wrong? I'd love to use the new features of .Net 2.0 where they fit but
does it always fit? Or does it fit here?

Thanks for your patience,

Dale
--
Dale Preston
MCAD C#
MCSE, MCDBA


Brian Gideon said:
Dale,

The best way of doing this is to use predicate logic. Which framework
version are you using? The reason I'm asking is because this is much
easier in 2.0. Consider the following example.

// This method would be in your custom collection.
public int FindIndex(Predicate<T> match)
{
for (int index = 0; index < collection.Count; index++)
{
if (match(collection[index])) return index;
}
return -1;
}

// This is how you would use it.
public static void Main()
{
YourCollection collection = GetReferenceToYourCollectionSomehow();

int index1 = collection.FindIndex(
delegate(YourItem item)
{
return item.Index == 5;
});

int index2 = collection.FindIndex(
delegate(YourItem item)
{
return item.ProductId == 5;
});
}

You can use the List<T> collection as an example. It already has the
FindIndex method. You can do something similar in 1.1, but you'd have
to define your own strongly-typed delegate and provide an
implementation for each type of search you want to perform (by Index,
ProductId, etc).

Brian
IndexOf(5, blah.Index) means return the index of the object at position 5.
This basically mimics the default behavior and, in fact, this overload would
simply call the default overload. The real usefulness of this and the
default behavior is to return -1 if there is no position 5.

IndexOf(5, blah.ProductId) means return the index of the product whose
ProductId is 5.

I agree with you about the problems when the key is integer. I even thought
about doing away with the default behavior because consumers of my class
would probably never call it - after all, you can always do
ProductCollection.Count to see if there is a position 5 - but many other
collection methods depend on the default implementation of IndexOf and all of
those methods would have to be rewritten as well.

Unless someone else comes up with something neither of us thought of, I am
going to go with your suggestion. It seems to be the right thing to do:
leave the default behavior alone and create a new GetIndexById() method.

Thanks,

Dale
--
Dale Preston
MCAD C#
MCSE, MCDBA


Marc Gravell said:
Hang on...

what exactly does "IndexOf(5, blah.Index)" mean? Give me the index of the
item at index 5 [position 6]?

Do you perhaps mean the /indexer/ (this), i.e. this[5]? In which case, I
would suggest keeing the int indexer with it's general usage for
collections, and perhaps add a GetByProductID(int id), or (more generic) a
GetByKey.

In some cases, a key indexer is common (e.g. hashtable, etc), but you get
into a lot of trouble if both the key and index share a type... or are
implicitely convertible. Lots of pain.

Marc
 
M

Marc Gravell

Well, it depends on the intention of the class. One strategy is to have
a generic base class, but add specific functionality in a subclass.
Also, if in 2.0 I would recommend re-using the existing generic types -
and it sounds like Dictionary is the one to use here. For instance, I'm
assuming that all your "data item"s have an ID, and that product may be
subclassed - and may warrant specific (typed) collections, then
something like below. Alternatively, in *many* cases it is sufficient
to simply use a List<T>, either wrapped or unwrapped (if I was writing
a library component as a vendor, then I would wrap all the collections;
if it was just for internal use I could perhaps leave as List<T> and
just absorb the code-hack time if/when I changed the implementation to
SuperList<T>).

public abstract class DataItem {
public int Id {...}
}

public class DataItemDictionary<T> : IEnumerable<T> where T : DataItem
{
private readonly Dictionary<int, T> items = new Dictionary<int, T>();

public void Add(T item) {
items.Add(item.Id, item);
}
public T this[int id] {
get {return items[id];}
}
public IEnumerator<T> GetEnumerator() {
return items.Values.GetEnumerator();
}
// etc
}

public class Product : DataItem {
public string SkuCode {...}
}

public class ProductDictionary<T> : DataItemDictionary<T> where T :
Product {
public T this[string skuCode] {
foreach(T item in this) {
if(item.SkuCode == skuCode) return item;;
}
throw new KeyNotFoundException();
}
}

etc

Marc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top