KeyValuePair‹string,object› Является ли объект перечисляемым?

Как программно определить, является ли объект в паре значений ключа перечислимым?

Мне нужно знать, является ли объект в поле значения списком или массивом. Я должен иметь возможность определить тип перечисляемого типа объекта (например, список строк или массив целых чисел)

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

Что я пробовал:

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

person BossRoss    schedule 19.02.2014    source источник


Ответы (3)


Используя dynamic

Вы можете использовать ключевое слово dynamic для этого, но я думаю, что это может быть слишком медленным для вас.

Тем не менее, вот как вы могли бы это сделать. Это вызовет строго типизированный метод enumerate() для List<T> и T[] или, если значение не является ни списком, ни массивом, вызовет перегрузку 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();
        }
    }
}
person Matthew Watson    schedule 19.02.2014
comment
При использовании вашего кода отражения я получаю ссылку на объект, не отправленную экземпляру объекта в строке genericEnumerator =listEnumerator.MakeGenericMethod(listElemType); - person BossRoss; 20.02.2014
comment
@BossRoss Вы не получите эту ошибку, если запустите мой пример кода без каких-либо изменений. Если вы получаете эту ошибку при изменении кода, это связано с тем, что в классе нет статического закрытого метода с именем enumerateList, и поэтому GetMethod() возвращает значение null. Вам нужно будет изменить этот вызов, чтобы указать соответствующее имя метода и флаги привязки для метода, который вы хотите вызвать. - person Matthew Watson; 20.02.2014
comment
Я использовал код отражения, он работает хорошо (когда правильные методы статичны). Спасибо за помощь и хороший ответ. - person BossRoss; 21.02.2014

Вы должны просмотреть реализованные интерфейсы 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 вообще не является коллекцией.

person Georg    schedule 19.02.2014
comment
GetGenericTypeArguments недоступен, вы должны использовать GetGenericArguments. Кстати, ваш работает нормально. - person Teejay; 19.02.2014

Это работает для большинства перечислимых типов:

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;

}
person Teejay    schedule 19.02.2014
comment
@Georg, да, я написал большинство - person Teejay; 19.02.2014
comment
@ANeves я использую его для списков - person Teejay; 19.02.2014
comment
@ANeves Вы должны включить оператор using в System.Collections. - person Georg; 19.02.2014
comment
Истинный; У меня он был только для System.Collections.Generic. - person ANeves thinks SE is evil; 19.02.2014
comment
@Teejay Да, но, на мой взгляд, наборы - это очень важный класс коллекций (хотя у Microsoft, по-видимому, другое мнение). Ваш подход действителен только для списков - person Georg; 19.02.2014
comment
@Georg OP спросил о массиве и списках. Это работает. Кстати, он работает с несколькими перечислимыми типами. - person Teejay; 19.02.2014