Reputation: 1646
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
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
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