Pergunta

How do I programmatically figure out if an object in a Key value pair is enumerable?

I need to know if a object in the value field is a List or Array. I should be able to determine what kind of enumerable type the object is(eg a List of strings or an array of ints)

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");
    }
 }
Foi útil?

Solução

Using dynamic

You could leverage the dynamic keyword to do this, but I think it might be too slow for you.

However, this is how you could do it. This will call a strongly-typed enumerate() method for List<T> and T[], or if the value is neither a List nor an array, it will call the overload of enumerate() which just takes an object.

I'm not entire sure this is what you are after, but it does give you a strongly-typed enumeration for the lists and arrays in your KVP list.

Note that as per your specification, this ONLY considers List and Array types; other enumerable types (such as strings, HashSet and so on) are not considered:

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

Using explicit reflection

Here's a way of doing it using reflection that avoids using dynamic, which means it's a lot faster - but as you can see it's significantly more fiddly!

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

Outras dicas

You have to review the implemented interfaces of kvp.Value. This is only possible through reflection.

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

This way, you can determine the element type of the collection at runtime. However, to retrieve the items, it is best to use the non-generic IEnumerable interface, because you don't need the (expensive) reflection overhead. Checking for IEnumerableis also a good starting point, so it doesn't make too much sense to check the element type if kvp.Valueis not a collection at all.

This works for most Enumerable types:

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;

}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top