Reputation: 3725
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
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
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
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 ()
Upvotes: 2