4

As an example, I'll use SqlDataReader and DataRow classes: they both define the following indexer:

public object this[int columnIndex] { get; set; }

What is the lowest common denominator type to use as method's parameter type, so that both (and any other class implementing same indexer) can be passed and used in the same fashion, e.g.:

void DoSomething(??? indexedObject)
{
   string foo = indexedObject[0].ToString(); 
          // let's ignore the presence of ToString()
          // for the purpose of this question
}

Is it object? What if the indexed object does not derive from object (I think that's possible, even though very unlikely).

If it matters, I am targeting .NET 3.5.

Edit: I am looking for some contract enforcement that causes callers to pass objects that implement said indexer.

G. Stoynev
  • 7,389
  • 6
  • 38
  • 49
  • Use [dynamic](http://msdn.microsoft.com/en-us/library/vstudio/dd264741.aspx)? – tnw May 17 '13 at 18:33
  • 1
    There is no common interface or base class that has this indexer (other than `IDataReader`) – SLaks May 17 '13 at 18:33
  • @tnw: dynamic was introduced in 4.0. – zimdanen May 17 '13 at 18:35
  • SLaks is right. You'll have to create your own interface that exposes the indexer, and adapters types that impliment it, that can convert specific the general indexer call into type specific indexer calls, and pass that to `DoSomething` (Well that'd be one way at least) – asawyer May 17 '13 at 18:35
  • 1
    Regarding your edit - There is no type general type constraint of "This type has an index operator of Type " – asawyer May 17 '13 at 18:45

2 Answers2

7

Is it object?

Pretty much. There is no common interface or class you can use, so object is really the only thing guaranteed shared in the hierarchy.

What if the indexed object does not derive from object?

This is not possible in .NET. System.Object is the base type of all types, and values can always be treated as an object (even if they do require boxing for this to work).

However, passing in as object will not provide access to the indexer, other than via reflection.

The only direct way to do this would be via dynamic, but that requires .NET 4 (and is not type safe, which means you could get runtime exceptions).

A better approach might be to provide a Func<T, int, string> which would allow you to, at the call site, specify how to extract the value:

void DoSomething<T>(T object, Func<T, int, string> valueExtractor)
{
    string foo = valueExtractor(object, 0); 
}

Then call via:

DoSomething(indexedObject, (o,i) => o[i].ToString());

This allows you to pass in an object and a mechanism to extract a value given an index at the call site, which works for any type.


Edit in regards to:

Edit: I am looking for some contract enforcement that causes callers to pass objects that implement said indexer.

There is not a built in contract or interface that these types both implement, nor any way to constrain a generic based on the existence of an indexer. You will need a different approach, such as my suggestion of using a delegate to extract the value.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Related to my question I probably can't use object because `Syste.Object` doesn't implement an indexer of that type, so code won't even compile. – G. Stoynev May 17 '13 at 19:48
  • @G.Stoynev That's why I wrote "However, passing in as object will not provide access to the indexer, other than via reflection." The functional approach is really probably the nicest. – Reed Copsey May 17 '13 at 19:53
  • On the alternative approach: adding the valueExtractor. It is a generic-enough idea, but for all practical purposes when I reach such solution I end up thinking "If I am forcing callers to provide that delegate, can't I achieve the same via an interface", which is kinda confirmed by Hans Passant's answer. – G. Stoynev May 17 '13 at 19:58
  • @G.Stoynev Yes - but then you're forcing callers to create a class to implement your interface and wrap the "real" class, since the class itself doesn't implement any such interface. It's a matter of which is worse... – Reed Copsey May 17 '13 at 21:24
1

I'd say Reed Copsey's Func<T> delegate solution is the best way to go, but since your on c# 3.5 you'll have to go and define your own delegates, Func<T> won't be available. It's very straightforward though.

Edit - Oops, this was 3.5 not 4.0.

For what it's worth, here is another solution that as I outlined in a comment, uses an interface to define adapter types to that understand how to call the specialized type indexers but allow the call site (DoSomething) to work with the common interface:

void Main()
{
    var t1 = new Type1(); // ie Sql Reader
    var t2 = new Type2(); // ie DataRow

    DoSomething(new Type1IndexAdapter(t1));
    DoSomething(new Type2IndexAdapter(t2));
}

public void DoSomething(ICanIndex indexer)
{
    var r = indexer["test"];
}

public interface ICanIndex
{
    string this[string index]{get;}
}

public class Type1IndexAdapter : ICanIndex
{
    public Type1 value;
    public Type1IndexAdapter(Type1 val)
    {
        this.value = val;
    }
    public string this[string index]
    {
        get
        {
            return this.value[index];
        }
    }
}

public class Type2IndexAdapter : ICanIndex
{
    public Type2 value;
    public Type2IndexAdapter(Type2 val)
    {
        this.value = val;
    }
    public string this[string index]
    {
        get
        {
            return this.value[index];
        }
    }
}

public class Type1 // ie SqlDataReader 
{
    public string this[string index]
    {
        get
        {
            Console.WriteLine("Type 1 indexer called: " + index);
            return null;
        }
    }
}


public class Type2 // ie DataRow 
{
    public string this[string index]
    {
        get
        {
            Console.WriteLine("Type 2 indexer called: " + index);
            return null;
        }
    }
}
asawyer
  • 17,642
  • 8
  • 59
  • 87