KeyValuePair‹string,object› Изброим ли е обектът?

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

Трябва да знам дали даден обект в полето за стойност е списък или масив. Трябва да мога да определя какъв вид изброим тип е обектът (напр. списък от низове или масив от int)

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 списък.

Имайте предвид, че според вашата спецификация, това разглежда САМО типовете списък и масив; други изброими типове (като низове, 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

Това работи за повечето типове Enumerable:

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 Трябва да включите израз за използване в 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 попита за масив и списъци. Това работи. BTW работи върху множество изброими типове. - person Teejay; 19.02.2014