18

Is it possible to have something like the following:

class C
{
    public Foo Foos[int i]
    {
        ...
    }

    public Bar Bars[int i]
    {
        ...
    }
}

If not, then are what are some of the ways I can achieve this? I know I could make functions called getFoo(int i) and getBar(int i) but I was hoping to do this with properties.

smack0007
  • 11,016
  • 7
  • 41
  • 48

8 Answers8

22

Not in C#, no.

However, you can always return collections from properties, as follows:

public IList<Foo> Foos
{
    get { return ...; }
}

public IList<Bar> Bars
{
    get { return ...; }
}

IList<T> has an indexer, so you can write the following:

C whatever = new C();
Foo myFoo = whatever.Foos[13];

On the lines "return ...;" you can return whatever implements IList<T>, but you might what to return a read-only wrapper around your collection, see AsReadOnly() method.

20

This from C# 3.0 spec

"Overloading of indexers permits a class, struct, or interface to declare multiple indexers, provided their signatures are unique within that class, struct, or interface."

public class MultiIndexer : List<string>  
{
    public string this[int i]
    {
        get{
            return this[i];
        }
    }
    public string this[string pValue]
    {
        get
        {
            //Just to demonstrate
            return this.Find(x => x == pValue);  
        }
    }      
}
Jacob_J
  • 201
  • 2
  • 3
6

There IS a way.. if you define 2 new types to alow the compiler to distinguish the two different signatures...

  public struct EmployeeId
  { 
      public int val;
      public EmployeeId(int employeeId) { val = employeeId; }
  }
  public struct HRId
  { 
      public int val;
      public HRId(int hrId) { val = hrId; }
  }
  public class Employee 
  {
      public int EmployeeId;
      public int HrId;
      // other stuff
  }
  public class Employees: Collection<Employee>
  {
      public Employee this[EmployeeId employeeId]
      {
          get
             {
                foreach (Employee emp in this)
                   if (emp.EmployeeId == employeeId.val)
                      return emp;
                return null;
             }
      }
      public Employee this[HRId hrId]
      {
          get
             {
                foreach (Employee emp in this)
                   if (emp.HRId == hrId.val)
                      return emp;
                return null;
             }
      }
      // (or using new C#6+ "expression-body" syntax)
      public Employee this[EmployeeId empId] => 
             this.FirstorDefault(e=>e.EmployeeId == empId .val;
      public Employee this[HRId hrId] => 
             this.FirstorDefault(e=>e.EmployeeId == hrId.val;

  }

Then to call it you would have to write:

Employee Bob = MyEmployeeCollection[new EmployeeID(34)];

And if you wrote an implicit conversion operator:

public static implicit operator EmployeeID(int x)
{ return new EmployeeID(x); }

then you wouldn't even have to do that to use it, you could just write:

Employee Bob = MyEmployeeCollection[34];

Same thing applies even if the two indexers return different types...

Charles Bretana
  • 143,358
  • 22
  • 150
  • 216
  • Whilst this looks neat at first glance, it isn't self explanatory and has a bit of "magic". If you came across someone else writing this you would assume there was only one index or result, but in this case you could return two completely different results depending on the type passed in, I know you haven't done that but you have to be careful. – rollsch May 11 '17 at 08:58
  • 2
    I guess anything you don't immediately recognize, and are unfamiliar with, has a bit of a "magic" feel to it, no? The first time I saw lambdas I was completely befuddled. – Charles Bretana May 11 '17 at 13:52
4

Try my IndexProperty class to enable multiple indexers in the same class

http://www.codeproject.com/Tips/319825/Multiple-Indexers-in-Csharp

Sion COhen
  • 41
  • 1
3

If you're trying to do something like this:

var myClass = new MyClass();

Console.WriteLine(myClass.Foos[0]);
Console.WriteLine(myClass.Bars[0]);

then you need to define the indexers on the Foo and Bar classes themselves - i.e. put all the Foo objects inside Foos, and make Foos a type instance that supports indexing directly.

To demonstrate using arrays for the member properties (since they already support indexers):

public class C {
    private string[] foos = new string[] { "foo1", "foo2", "foo3" };
    private string[] bars = new string[] { "bar1", "bar2", "bar3" };
    public string[] Foos { get { return foos; } }
    public string[] Bars { get { return bars; } }
}

would allow you to say:

 C myThing = new C();
 Console.WriteLine(myThing.Foos[1]);
 Console.WriteLine(myThing.Bars[2]);
Dylan Beattie
  • 53,688
  • 35
  • 128
  • 197
1

I believe the accepted answer is wrong. It is possible if you will use explicit interface implementation:

class C
{
    public IFooProvider Foos => this;
    public IBarProvider Bars => this;

    Foo IFooProvider.this[int i]
    {
        ...
    }

    Bar IBarProvider.this[int i]
    {
        ...
    }

    public interface IFooProvider
    {
        Foo this[int i] { get; set; }
    }

    public interface IBarProvider
    {
        Bar this[int i] { get; set; }
    }
}

Then you can use it exactly like you wanted:

C c;
c.Foos[1] = new Foo();
c.Bars[0] = new Bar();
Parchandri
  • 31
  • 5
0

C# doesn't have return type overloading. You can define multiple indexers if their input parameters are different.

JoshBerke
  • 66,142
  • 25
  • 126
  • 164
0

No you cant do it. Only methods that can have their signatures differ only by return type are conversion operators. Indexers must have different input parameter types to get it to compile.

Krzysztof Kozmic
  • 27,267
  • 12
  • 73
  • 115