C
C#2y ago
Hark Dand

✅ Indexer return collection or single object depending on parameter

Hi all, is there any way to selectively return either a single object or a collection of multiple objects depending on the given parameter? I want to achieve the following behavior: Let's say I have some class which references a table by Rows (alphabetically, e.g. A, B, C, ...) and Columns (numerically, e.g. 1, 2, 3, ...) If I want to have a reference to the cell A1 I would write the following:
public void SomeMethod()
{
var returnedObject = someClass["A1"]
}
public void SomeMethod()
{
var returnedObject = someClass["A1"]
}
Additionally, I would also like to be able to give ranges as the index. For example:
public void SomeMethod() {
var returnedObjectCollection = someClass["A1:A11"]
foreach (var o in returnedObjectCollection)
{
// Do something here...
}
}
public void SomeMethod() {
var returnedObjectCollection = someClass["A1:A11"]
foreach (var o in returnedObjectCollection)
{
// Do something here...
}
}
Is there any way of achieving such a behaviour? Thank you very much for your help!
20 Replies
sterbehilfe
sterbehilfe2y ago
Yes, you can create your own indexer in your class like this and create a method like "GetCell" for the logic:
public ReturnedObjectType this[string cell] => GetCell(cell);
public ReturnedObjectType this[string cell] => GetCell(cell);
But for your indexer returning a collection you will need a different signature, because you can't have two indexer with the same parameter types, for example:
public ReturnedObjectCollectionType this[string startCell, string endCell] => GetCellRange(startCell, endCell);
public ReturnedObjectCollectionType this[string startCell, string endCell] => GetCellRange(startCell, endCell);
Hark Dand
Hark Dand2y ago
Yeah, I thought about doing that. That would probably work (and I considered that option, maybe should've included that), but partially just out of interest I would really like to know if it would be possible with my proposition.
sterbehilfe
sterbehilfe2y ago
Yeah, you can't return different types for same parameter types. What could work is returning a base class or an interface for both, but I don't think that would work well.
Sossenbinder
Sossenbinder2y ago
You can probably have two indexer, one of which accepting https://learn.microsoft.com/en-us/dotnet/api/system.range?view=net-7.0
Range Struct (System)
Represents a range that has start and end indexes.
Sossenbinder
Sossenbinder2y ago
Then it would also be type safe If the first one returns a single item while the latter returns an iterable colection If that works with your type of range of course I'm not sure anymore but I think indexers can also accept multiple parameters, that could also work So you can have one accepting a single string while the other takes multiple string params
Hark Dand
Hark Dand2y ago
Yes, that would be the solution @Sterbehilfe proposed, that would surely work. I'll have a look into ranges. Never used them before, so I can't for sure say whether or not they work for my use case. Thanks for the suggestions, though!
Sossenbinder
Sossenbinder2y ago
Oh true I've just noticed, the second example Sterbehilfe mentioned is what I meant as well
Hark Dand
Hark Dand2y ago
Okay, had a quick look at Range. It seems like Ranges won't be the solution either. Their constructor is Range(Index, Index), and the Index constructor is Index(int value, bool fromEnd = false), which I wouldn't be able to properly use as my data structure is more like a Dictionary. I could, of course, introduce some sort of ordering on the Dictionary, but the Range "operator" (e.g. [1..3]) still isn't what I'm searching for. Something interesting I found: The range operator is exclusive of the latter part, So [1..4] will give back the first 3 elements, which I find to be quite unintuitive.
ero
ero2y ago
It actually gives the second - fourth elements Programming usually has exclusive upper bounds Random's upper bound is exclusive Python's range is exclusive upper bound This really isn't a place for indexers, imo If you don't know how to implement it, you're implementing it wrong Have GetCell(String) and GetCellRange(String, String) methods
Hark Dand
Hark Dand2y ago
Yeah, my bad, it does I don’t know if I can agree with that Whether or not it would be sensible to implement it with indexers, I would still like to know if it is possible
ero
ero2y ago
Well you got your answer, no?
Hark Dand
Hark Dand2y ago
Not really, since I am still looking for what I wrote in the original post
ero
ero2y ago
Just
class SomeClass
{
public T this[string s] => /* */;
public List<T> this[string s1, string s2] => /* */;
}
class SomeClass
{
public T this[string s] => /* */;
public List<T> this[string s1, string s2] => /* */;
}
no?
Hark Dand
Hark Dand2y ago
Yes, that would solve the problem. @Sterbehilfe already proposed that. But using the indexer for ranges would then look look like this someClass[“A1“, “A11“] Instead of someClass[“A1:A11“] And the latter is what I am trying to achieve
ero
ero2y ago
Yeah that's not possible And wouldn't make sense anyway
ero
ero2y ago
This undoubtedly works with dynamic Which you should avoid like the devil You could argue that sure, other languages are able to specify multiple return types But c# cannot
Hark Dand
Hark Dand2y ago
Yeah, I know that Would you have an idea on how that has been implemented?
ero
ero2y ago
public dynamic this[string cellOrRange] => /* */; Then in the getter body parse the input, check if it contains a :, and do things accordingly And then the rest of your code just becomes a nightmare You could also return object and cast it where you call it Neither are even remotely okay solutions imo
Hark Dand
Hark Dand2y ago
I think that seems to be the answer to my question, so thank you.