【问题标题】:Access the indexer of an unknown object type访问未知对象类型的索引器
【发布时间】:2019-09-24 03:05:35
【问题描述】:

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

背景是我正在尝试为 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
}

【问题讨论】:

  • object[] values 是您将 WPF 元素绑定到的集合/对象。在转换器中,首先将其转换为您拥有的集合类型。然后访问属性。
  • values 集合对多重绑定中的每个绑定都有一个项目,是的,我知道我可以转换其中一个值(与我为索引所做的相同)。但是有没有办法知道传入的这些对象之一是否有索引器,无论它是实现索引器的列表、数组还是自定义类(并且没有接口)。
  • 一定要用is IEnumerable
  • 我认为 IEnumerable 不包含索引器访问,(仅枚举)。我认为 IList 包含索引器要求,但我必须处理的一些类没有实现 IList。
  • 最简单的方法是让类实现IList。当类没有索引器时你会怎么做?

标签: c# wpf ivalueconverter indexer


【解决方案1】:

你可以通过反射来做到这一点。

下面是一个使用两个具有不同类型键的索引器访问类的示例,如果您始终确定您拥有什么类型的索引器,那么它会不那么复杂。但我认为值得注意的是,具有多个索引器的类或具有多个键的索引器是可能的。

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

请注意,尽管理论上可能存在具有名为Item 的属性的类不是索引器,因此您应该检查myIndexerProperty.GetIndexParameters().Length == 1 是否仍然存在。

【讨论】:

  • 感谢您的回答,这正是我所追求的,我怀疑某种反射魔法可能是解决方案,但还不够了解。
【解决方案2】:

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

1) 检查value is IList list 是否为return list[index],如果是。

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

我们以这个类为例:

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,请注意,如果一个类有一个索引器,则它不能有一个名为Item 的属性,因为它会与它们发生冲突。

要找到接受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;

【讨论】:

  • 投了赞成票,但我决定采用标记的答案,因为它提供了更多关于它正在做什么的细节。尽管使用了更冗长的语言,但您的回答确实有助于使事情更清楚。
猜你喜欢
  • 1970-01-01
  • 2016-12-31
  • 2022-01-21
  • 1970-01-01
  • 2021-11-16
  • 2022-01-25
  • 1970-01-01
  • 2015-09-15
  • 2023-03-06
相关资源
最近更新 更多