Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the presence of an indexer as method's parameter signature/contract/type

Tags:

c#

indexer

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.

like image 945
G. Stoynev Avatar asked Feb 17 '23 12:02

G. Stoynev


2 Answers

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.

like image 167
Reed Copsey Avatar answered Feb 19 '23 02:02

Reed Copsey


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;
        }
    }
}
like image 39
asawyer Avatar answered Feb 19 '23 02:02

asawyer