views:

187

answers:

3

Hello, how is it possible to know whether an object implements an indexer?, I need to share a logic for a DataRow and a IDataReader, but they don't share any interface.

I tried also with generics but don't know what restriction should I put on the where clause.

public class Indexer {
    // myObject should be a DataRow or a IDataReader
    private object myObject;
    public object MyObject {
     get { return myObject; }
     set { myObject = value; }
    }
    // won't compile, myObject has no indexer
    public object this[int index] {
     get { return myObject[index]; }
     set { myObject[index] = value; }
    }
    public Indexer(object myObject) {
     this.myObject = myObject;
    }
}

public class Caller {
    void Call() {
     DataRow row = null;
     IDataReader reader = null;
     var ind1 = new Indexer(row);
     var ind2 = new Indexer(reader);
     var val1 = ind1[0];
     var val2 = ind1[0];
    }
}
+3  A: 

You'd need to declare an interface with an indexer property, use that interface as the constraint, and the type argument class would need to implement that interface in order to satisfy the constraint.

As you don't control the classes you want to use, that wouldn't work.

An alternative is to make the Indexer class take the get/set operations as separate parameters:

public class Indexer {

    private Func<int, object> getter;        
    private Action<int, object> setter;

    public object this[int index] 
    {
        get { return getter(index); }
        set { setter(index, value); }
    }

    public Indexer(Func<int, object> g, Action<int, object> s) 
    {
        getter = g;
        setter = s;
    }
}

public static class IndexerExtensions
{
    public static Indexer ToIndexer(this DataRow row)
    {
        return new Indexer(n => row[n], (n, v) => row[n] = v);
    }

    public static Indexer ToIndexer(this IDataReader row)
    {
        return new Indexer(n => row[n], (n, v) => row[n] = v);
    }
}

You could then do this:

DataRow row = null;
IDataReader reader = null;
var ind1 = row.ToIndexer();
var ind2 = reader.ToIndexer();
var val1 = ind1[0];
var val2 = ind1[0];
Daniel Earwicker
But i can't make DataRow implement my interface
Jhonny D. Cano -Leftware-
See updated answer.
Daniel Earwicker
i didn't downvote it, FYI, sorry
Jhonny D. Cano -Leftware-
gonna check it, nice +1
Jhonny D. Cano -Leftware-
+1 I like this solution, although C# 3.0 is needed
Patrick McDonald
It's similar to Reed's except that you don't have to declare a class to handle each case - if you have a one-off, you can just write a couple of lambdas to construct an Indexer, similar to how an anonymous class would be used in Java to make a one-off class.
Daniel Earwicker
I think i'm gonna do that, i will mix both answers, tx
Jhonny D. Cano -Leftware-
+1 This is a very nice option, Earwicker.
Reed Copsey
Note that IDataReader hasn't setter
Jhonny D. Cano -Leftware-
If you wanted total type safety, you'd have to make a ReadOnlyIndexer to cover such cases, which may in fact be all you need here. Or a hacky approach would be to pass a setter lambda that does nothing!
Daniel Earwicker
+1  A: 
get { 
    DataRow row = myObject as DataRow;
    if (row != null)
        return row[index];
    IDataReader reader = myObject as IDataReader;
    if (reader != null)
        return reader[index];
}

and use the same logic for set{}

Patrick McDonald
I don't want to incur in the cost of Casting ["as" keyword] so that answer doesn't fits to me, imagine there is a reader with 200 records, it would be 400 casts, i think that must somewhat hurt performance
Jhonny D. Cano -Leftware-
Note that if you're actually visiting a database to get the data, the cost of 400 casts is unlikely to be significant.
Daniel Earwicker
I'd agree with Earwicker. I just prefer my approach because it allows you to have more type safety - the perf. is probably perfectly acceptable using cases, but my approach allows you to save IDataReader instead of object.
Reed Copsey
+3  A: 

You could make your Indexer an abstract base class, with two subclasses, one for DataRow, and one for IDataReader.

To make it easier to use, 2 factory methods could exist, such as:

var ind1 = Indexer.CreateFromDataRow(row);
var ind2 = Indexer.CreateFromIDataReader(reader);

They could create a specific base class for that type, with it's own logic for handling the indexing.

This avoids the overhead of checking types constantly for every get/set call (at the cost of a single virtual property instead of a standard property).

Reed Copsey