desautelsj
desautelsj

Reputation: 3725

How to iterate through an enumeration of KeyValuePair without knowing the type of the key and the type of the value

Say I have a method like this:

public void Foo(object arguments)

and say I need to detect if the type of arguments is actually an enumeration. I would write something like this:

if (arguments is IEnumerable)

Now, let's say I need to detect if it's an enumeration of KeyValuePair (regardless of the type of the key and the type of the value). My instinct would be to write something like this:

if (arguments is IEnumerable<KeyValuePair<,>>)

but visual studio complains that Using the generic type 'KeyValuePair<TKey, TValue>' requires 2 type arguments.

I also tried:

if (arguments is IEnumerable<KeyValuePair<object, object>>)

but it returns false if the key is anything but an object (such as a string for example) or if the value is anything but an object (such as an int for example).

Does anybody have suggestions how I could determine if an enumeration contains KeyValuePairs regardless of the key type and the value type and if so, how can I loop through these pairs?

Upvotes: 5

Views: 1438

Answers (3)

desautelsj
desautelsj

Reputation: 3725

Thank you @tommaso-belluzzo and @sarumanfor for investing the time to help me figure this out. I ended up accepting Tommaso's answer because I really like his idea of using the dynamic type to enumerate items in the enumeration.

I also wanted to contribute my solution (which is largely based on Tommasso's suggestions)

/// <summary>Get the key=>value pairs represented by a dictionary, enumeration of KeyValue pairs or an anonymous object.</summary>
private IEnumerable<KeyValuePair<string, object>> GetArguments(object arguments)
{
    // null
    if (arguments == null)
        return Enumerable.Empty<KeyValuePair<string, object>>();

    // dictionary
    if (arguments is IDictionary dictionary)
        return dictionary
            .Cast<dynamic>()
            .Select(item => new KeyValuePair<string, object>(item.Key.ToString(), item.Value));

    // enumeration
    if (arguments is IEnumerable enumeration)
    {
#if NETFULL
        var argumentsType = enumeration.GetType();
        var itemType = argumentsType.GetElementType();
        var isGeneric = itemType.IsGenericType;
        var enumeratedType = isGeneric ? itemType.GetGenericTypeDefinition() : null;
#else
        var argumentsTypeInfo = enumeration.GetType().GetTypeInfo();
        var itemTypeInfo = argumentsTypeInfo.GetElementType().GetTypeInfo();
        var isGeneric = itemTypeInfo.IsGenericType;
        var enumeratedType = isGeneric ? itemTypeInfo.GetGenericTypeDefinition() : null;
#endif

        if (enumeratedType == typeof(KeyValuePair<,>))
        {
            return enumeration
                .Cast<dynamic>()
                .Select(item => new KeyValuePair<string, object>(item.Key.ToString(), item.Value));
        }
        else
        {
            throw new ArgumentException("You must provide an enumeration of KeyValuePair", nameof(arguments));
        }
    }

    // object
    return arguments.GetType()
        .GetRuntimeProperties()
        .Where(p => p.CanRead)
        .Select(p => new KeyValuePair<string, object>(p.Name, p.GetValue(arguments)));
}

Upvotes: 0

Tommaso Belluzzo
Tommaso Belluzzo

Reputation: 23685

You need some reflection here:

Boolean isKeyValuePair = false;

Type type = arguments.GetType();

if (type.IsGenericType)
{
    Type[] genericTypes = type.GetGenericArguments();

    if (genericTypes.Length == 1)
    {
        Type underlyingType = genericTypes[0];

        if (underlyingType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
            isKeyValuePair = true;
    }
}

In order to rebuild an Enumerable and iterate over it, you could use the following approach that uses dynamic:

List<KeyValuePair<Object, Object>> list = new List<KeyValuePair<Object, Object>>();

foreach (dynamic kvp in (IEnumerable)arguments)
    list.Add(new KeyValuePair<Object, Object>(kvp.Key, kvp.Value));

or, with LINQ:

List<KeyValuePair<Object, Object>> list = (from dynamic kvp in (IEnumerable)arguments select new KeyValuePair<Object, Object>(kvp.Key, kvp.Value)).ToList();

I also found another solution, but this is pure madness:

Boolean isKeyValuePair = false;

Type type = arguments.GetType();

if (type.IsGenericType)
{
    Type[] genericTypes = type.GetGenericArguments();

    if (genericTypes.Length == 1)
    {
        Type underlyingType = genericTypes[0];

        if (underlyingType.GetGenericTypeDefinition() == typeof (KeyValuePair<,>))
        {
            Type[] kvpTypes = underlyingType.GetGenericArguments();

            Type kvpType = typeof(KeyValuePair<,>);
            kvpType = kvpType.MakeGenericType(kvpTypes);

            Type listType = typeof (List<>);
            listType = listType.MakeGenericType(kvpType);

            dynamic list = Activator.CreateInstance(listType);

            foreach (dynamic argument in (IEnumerable)arguments)
                list.Add(Activator.CreateInstance(kvpType, argument.Key, argument.Value));
        }
    }
}

References:

Upvotes: 3

TheGeneral
TheGeneral

Reputation: 81583

You can use reflection with GetGenericTypeDefinition

var pair = new KeyValuePair<string, int>("asd", 123);

var isOf = pair.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>);

Note : It will throw an exception if it isn't a generic type

InvalidOperationException

The current type is not a generic type. That is, IsGenericType returns false.

You can check this with Type.IsGenericType

I'm not sure really want you want to achieve here however,

Some nonsensical examples in a foreach

private bool Get<T>(IEnumerable<T> list, Type someType) 
{
   foreach (var item in list)
   {
      if (item.GetType()
               .GetGenericTypeDefinition() == someType)
      {
         return true;
      }
   }
   return false;
}

private List<T> Get2<T>(IEnumerable<T> list, Type someType)
{
   return list.Where(
         item => item.GetType()
                     .GetGenericTypeDefinition() == someType)
      .ToList();
}}

Usage

var blah = Get2(list, typeof(KeyValuePair<,>));

Note : All this really depends on what you want to do, its hard to tell, both of these above are only silly examples of not knowing the type beforehand, and may not work for what you want.

Additional information

Type.GetGenericTypeDefinition Method ()

Type.IsGenericType Property

Upvotes: 2

Related Questions