对象是否可枚举?KeyValuePair<string,object>

6

我如何编程地确定键值对中的对象是否可枚举?

我需要知道值字段中的对象是列表还是数组。我应该能够确定对象是哪种可枚举类型(例如字符串列表或整数数组)。

List<KeyValuePair<string, object>> lKVP = new List<KeyValuePair<string, object>>();
List<string> lS = new List<string> { "s1", "s2" };

lKVP.Add(new KeyValuePair<string, object>("PassPhrase", "E92D8719-38A6-0000-961F-0E66FCB0A363"));
lKVP.Add(new KeyValuePair<string, object>("Test", lS));

What I have tried:

1)

foreach(KeyValuePair<string,object> kvp in _ParameterReplacement)
{
    if(kvp.Value is Enumerable)
    {
        Console.WriteLine("Yes");
    }
    else
    {
        Console.WriteLine("No");
    }
 }

2)

foreach(KeyValuePair<string,object> kvp in _ParameterReplacement)
{
    if(kvp.Value.GetType() == typeof(IEnumerable<object>))
    {
        Console.WriteLine("Yes");
    }
    else
    {
        Console.WriteLine("No");
    }
 }
3个回答

3

使用 dynamic

你可以利用 dynamic 关键字来实现这一点,但我认为这可能会导致速度变慢。

然而,以下是如何实现的。这将调用一个针对 List<T>T[] 的强类型 enumerate() 方法,或者如果值既不是 List 也不是数组,则调用只接受对象的 enumerate() 的重载函数。

我不完全确定这是否符合你的要求,但它确实为你的 KVP 列表中的列表和数组提供了强类型枚举。

请注意,根据您的规定,它仅考虑 List 和 Array 类型;其他可枚举类型(例如字符串、HashSet 等)不被考虑:

using System;
using System.Collections.Generic;

namespace ConsoleApp1
{
    sealed class Program
    {
        void test()
        {
            List<KeyValuePair<string, object>> lKVP = new List<KeyValuePair<string, object>>();
            List<string> lS = new List<string> { "s1", "s2" };
            string[] aS = {"a1", "a2"};

            lKVP.Add(new KeyValuePair<string, object>("String", "E92D8719-38A6-0000-961F-0E66FCB0A363"));
            lKVP.Add(new KeyValuePair<string, object>("Test", lS));
            lKVP.Add(new KeyValuePair<string, object>("IntNotEnumerable", 12345));
            lKVP.Add(new KeyValuePair<string, object>("Array", aS));

            foreach (KeyValuePair<string,object> kvp in lKVP)
            {
                enumerate((dynamic) kvp.Value);
            }
        }

        static void enumerate<T>(List<T> list)
        {
            Console.WriteLine("Enumerating list of " + typeof(T).FullName);

            foreach (var item in list)
                Console.WriteLine(item);

            Console.WriteLine();
        }

        static void enumerate<T>(T[] array)
        {
            Console.WriteLine("Enumerating array of " + typeof(T).FullName);

            foreach (var item in array)
                Console.WriteLine(item);

            Console.WriteLine();
        }

        static void enumerate(object obj)
        {
            Console.WriteLine("Not enumerating type " + obj.GetType().FullName + " with value " + obj);
            Console.WriteLine();
        }

        static void Main(string[] args)
        {
            new Program().test();
        }
    }
}

使用显式反射

这是一种使用反射来实现的方法,避免了使用dynamic,这意味着它速度更快 - 但正如您所看到的,它需要更多的操作!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace ConsoleApp1
{
    sealed class Program
    {
        void test()
        {
            List<KeyValuePair<string, object>> lKVP = new List<KeyValuePair<string, object>>();
            List<string> lS = new List<string> { "s1", "s2" };
            string[] aS = {"a1", "a2"};

            lKVP.Add(new KeyValuePair<string, object>("String", "E92D8719-38A6-0000-961F-0E66FCB0A363"));
            lKVP.Add(new KeyValuePair<string, object>("Test", lS));
            lKVP.Add(new KeyValuePair<string, object>("IntNotEnumerable", 12345));
            lKVP.Add(new KeyValuePair<string, object>("Array", aS));

            var listEnumerator  = this.GetType().GetMethod("enumerateList",  BindingFlags.NonPublic | BindingFlags.Static);
            var arrayEnumerator = this.GetType().GetMethod("enumerateArray", BindingFlags.NonPublic | BindingFlags.Static);

            foreach (KeyValuePair<string, object> kvp in lKVP)
            {
                MethodInfo genericEnumerator = null;
                var arrayElemType = arrayElementType(kvp.Value);

                if (arrayElemType != null)
                {
                    genericEnumerator = arrayEnumerator.MakeGenericMethod(arrayElemType);
                }
                else
                {
                    var listElemType = listElementType(kvp.Value);

                    if (listElemType != null)
                        genericEnumerator = listEnumerator.MakeGenericMethod(listElemType);
                }

                if (genericEnumerator != null)
                    genericEnumerator.Invoke(null, new[] { kvp.Value });
                else
                    Console.WriteLine("Not enumerating type: " + kvp.Value.GetType().FullName + "\n");
            }
        }

        static Type arrayElementType(object sequence)
        {
            if (sequence is IEnumerable)
            {
                var type = sequence.GetType();

                if (type.IsArray)
                    return type.GetElementType();
            }

            return null;
        }

        static Type listElementType(object sequence)
        {
            if (sequence is IEnumerable)
            {
                var type = sequence.GetType();

                if (typeof(IList).IsAssignableFrom(type) && type.IsGenericType)
                    return type.GetProperty("Item").PropertyType;
            }

            return null;
        }

        static void enumerateList<T>(List<T> list)
        {
            Console.WriteLine("Enumerating list of " + typeof(T).FullName);

            foreach (var item in list)
                Console.WriteLine(item);

            Console.WriteLine();
        }

        static void enumerateArray<T>(T[] array)
        {
            Console.WriteLine("Enumerating array of " + typeof(T).FullName);

            foreach (var item in array)
                Console.WriteLine(item);

            Console.WriteLine();
        }

        static void Main(string[] args)
        {
            new Program().test();
        }
    }
}

当我使用你的反射代码时,我在genericEnumerator =listEnumerator.MakeGenericMethod(listElemType)行上遇到了一个对象引用未设置为对象实例的错误。 - BossRoss
@BossRoss 如果你在不进行任何更改的情况下运行我的示例代码,就不会出现那个错误。如果你在更改了代码后仍然出现该错误,那么这是因为类中没有名为“enumerateList”的静态私有方法,因此 GetMethod() 返回 null。你需要更改该调用以指定适当的方法名称和绑定标志,以便调用所需的方法。 - Matthew Watson
我已经使用了反射代码,它运行良好(当正确的方法是静态时)。谢谢您的帮助和好答案。 - BossRoss

1

您需要审查 kvp.Value 的实现接口。这只能通过反射完成。

var type = kvp.Value.GetType();
if (type.IsArray) return type.GetElementType();
foreach (var i in type.GetInterfaces())
{
    if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
    {
        return i.GetGenericTypeArguments()[0];
    }
}
// not a generic collection
return typeof(object);

这样,您可以在运行时确定集合的元素类型。但是,为了检索项目,最好使用非泛型的 IEnumerable接口,因为您不需要(昂贵的)反射开销。检查 IEnumerable也是一个很好的起点,所以如果 kvp.Value根本不是集合,则检查元素类型没有太多意义。

GetGenericTypeArguments 不可访问,您应该使用 GetGenericArguments。顺便说一句,您的代码运行良好。 - Teejay

1

这适用于大多数可枚举类型:

Type objListType = null;

if (kvp.Value is IEnumerable) {

    if (kvp.Value.GetType().IsArray) 
        objListType = kvp.Value.GetType().GetElementType();
    else
        objListType = kvp.Value.GetType().GetProperty("Item").PropertyType;

}

@Georg 是的,我写了“大多数”。 - Teejay
@ANeves 我用它来处理列表。 - Teejay
@ANeves 你必须包含一个 using 语句到 System.Collections。 - Georg
真的;我只有System.Collections.Generic - ANeves
@Teejay 是的,但我认为集合是集合类中非常重要的一类(虽然微软显然有不同的看法)。你的方法仅适用于列表。 - Georg
@Georg OP 问到了数组和列表。这个方法可以解决问题。顺便说一下,它适用于多种可枚举类型。 - Teejay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接