访问未知对象类型的索引器

Access the indexer of an unknown object type

如果给定一个未知对象,有什么方法可以检查它是否有索引器,以及它是否确实从中访问值。

背景是我正在尝试为 WPF 编写一个自定义转换器,它允许按照索引从对象中拉出一个项目。

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
}

找出一个值的类型是否有索引器的唯一两种方法是:

1) 检查是否 value is IList list 如果是,则执行 return list[index]

2) 通过反射找到一个索引器,因为一个类型不需要实现 IList 接口就可以拥有一个。

我们以这个class为例:

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];
        }
    }     
}

用于索引器的名称是 Item,请注意,如果 class 有一个索引器,它不能有一个名为 [=15= 的 属性 ] 因为它会与他们发生冲突。

要找到接受 int index 的索引器,唯一万无一失的方法是这样的:

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;

你可以通过反射来完成。

下面是使用两个具有不同类型键的索引器访问 class 的示例,如果您始终确定您拥有哪种类型的索引器,它会稍微简单一些。但我认为值得注意的是,class 具有多个索引器或具有多个键的索引器是可能的。

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;
        }
    }
}

正常访问索引器:

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);

通过反射选择和访问正确的索引器:

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);
}

如果您始终只有一个索引器和一个键,您可以这样做来获取您的索引器,因为索引器 属性 的默认名称是 "Item":

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

请注意,虽然理论上可能有 class 带有 属性 的 Item 不是索引器,因此无论如何您都应该检查 myIndexerProperty.GetIndexParameters().Length == 1