Hugoagogo
Hugoagogo

Reputation: 1646

Access the indexer of an unknown object type

Is there any way if given an unknown object to check if it has an indexer, and if it does access a value from it.

The background is I am trying to write a custom converter for WPF that allows pulling an item out of an object by index, along the lines of.

public class IndexedMultiConverter : IMultiValueConverter
{
    #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        int index = (int)values[1]; // What index

        if (values[0] has indexer)
        {
            return values[0][index];
        }

        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Upvotes: 0

Views: 608

Answers (2)

User42389041
User42389041

Reputation: 91

The only 2 ways of finding out if a value's type has a indexer is to:

1) Check if the value is IList list and then just do return list[index] if it is.

2) Find a indexer via reflection, sins a type doesn't need to implement the IList interface to have one.

Lets take this class as a example:

class IndexerClass
{
    public object this[int index]
    {
        get
        {
            return (index + 1);
        }
    }

    internal string this[bool index]
    {
        get
        {
            return index.ToString();
        }  
    }

    private int this[IList<int> list, bool defValueIfNone]
    {
        get
        {
            if ((list == null) || (list.Count == 0))
            {
                if (defValueIfNone)
                {
                    return 0;
                }
                throw new ArgumentException("Invalid list");
            }
            return list[0];
        }
    }     
}

The name which is used for indexers is Item, note that if a class has a indexer(s) it can't have a property named Item as it would conflict with them.

To find the indexer which accepts the int index, the only foolproof way of doing so is like this:

var instance = new IndexerClass();

var type = typeof(IndexerClass); //sins you get a value just do: value.GetType();

var props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

if (props.Length > 0)
{
    foreach (var prop in props)
    {
        if (prop.Name == "Item")
        {
            var i_param = prop.GetIndexParameters();

            if (i_param.Length == 1)
            {
                if (i_param[0].ParameterType == typeof(int)) //you can also add `||` and check if the ParameterType is equal to typeof sbyte, byte, short, ushort, uint, long, ulong, float or double.
                {
                    return prop.GetValue(instance, new object[] { 0 });
                }
            }
        }
    }
}
return null;

Upvotes: 1

Arie
Arie

Reputation: 5373

You can do it with reflection.

Below is an example of accessing a class with two indexers with different types of key, if you are always sure what type of indexer you have, it will be a bit less complicated. But I think it is worth noting that a class with multiple indexers or an indexer with multiple keys is possible.

public class IndexedClass
{
    public string SomeProperty { get; set; }

    public int[] SomeArray { get; set; } = new int[] { 3, 4, 5 };

    Hashtable _items = new Hashtable();
    public object this[object key]
    {
        get
        {
            Console.WriteLine("object key");
            return _items[key];
        }
        set
        {
            _items[key] = value;

        }
    }

    public object this[int key]
    {
        get
        {
            Console.WriteLine("int key");
            return _items[key];
        }
        set
        {
            _items[key] = value;
        }
    }
}

accessing the indexer normally:

IndexedClass ic = new IndexedClass();
ic["some string"] = "some string value";
Console.WriteLine(ic["some string"]);
ic[1] = 10;
Console.WriteLine(ic[1]);
Console.WriteLine(ic[2]==null);

choosing and accessing the correct indexer via reflection:

object index = 1;
object myIndexedObject = ic;

Type myIndexType = index.GetType();
var myIndexerProperty = myIndexedObject.GetType().GetProperties().FirstOrDefault(a =>
{
    var p = a.GetIndexParameters();    

    // this will choose the indexer with 1 key 
    // <<public object this[int key]>>, 
    // - of the EXACT type:
    return p.Length == 1 
        && p.FirstOrDefault(b => b.ParameterType == myIndexType) != null;

    // notice that if you call the code below instead,
    // then the <<public object this[object key]>> indexer 
    // will be chosen instead, as it is first in the class,
    // and an <<int>> is an <<object>>

    //return p.Length == 1 
    //    && p.FirstOrDefault(b => b.ParameterType.IsAssignableFrom(myIndexType)) != null;
});

if (myIndexerProperty != null)
{
    object myValue = myIndexerProperty
        .GetValue(myIndexedObject, new object[] { index });

    Console.WriteLine(myValue);
}

If you always have only one indexer with one key, you could do this instead to get your indexer, as the default name of an indexer property is "Item":

var myIndexerProperty = myIndexedObject.GetType().GetProperty("Item");

Beware though that theoretically there could be classes with a property called Item that is not an indexer, so you should check if myIndexerProperty.GetIndexParameters().Length == 1 anyway.

Upvotes: 1

Related Questions