Skip to content

Instantly share code, notes, and snippets.

@jnm2
Created May 16, 2018 02:11
Show Gist options
  • Save jnm2/9389a83611ac8ce1748fa6e352527ba9 to your computer and use it in GitHub Desktop.
Save jnm2/9389a83611ac8ce1748fa6e352527ba9 to your computer and use it in GitHub Desktop.
Duck-typed ContainsKey
public bool ContainsKey(object actual, object expectedKey)
{
if (actual == null) throw new ArgumentNullException(nameof(actual));
var instanceType = actual.GetType();
var method = FindContainsKeyMethod(instanceType)
?? instanceType
.GetInterfaces()
.Concat(GetBaseTypes(instanceType))
.Select(FindContainsKeyMethod)
.FirstOrDefault(m => m != null)
?? throw new NotImplementedException("Fall back to old implementation");
return (bool)method.Invoke(actual, new[] { expectedKey });
}
private static MethodInfo FindContainsKeyMethod(Type type)
{
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var method = methods.FirstOrDefault(m =>
m.ReturnType == typeof(bool)
&& m.Name == "ContainsKey"
&& m.GetParameters().Length == 1);
if (method == null && type.IsGenericType)
{
var definition = type.GetGenericTypeDefinition();
var tKeyGenericArg = definition.GetGenericArguments().FirstOrDefault(typeArg => typeArg.Name == "TKey");
if (tKeyGenericArg != null)
{
method = definition
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.FirstOrDefault(m =>
m.ReturnType == typeof(bool)
&& m.Name == "Contains"
&& m.GetParameters() is var parameters
&& parameters.Length == 1
&& parameters[0].ParameterType == tKeyGenericArg);
if (method != null)
method = methods.Single(m => m.MetadataToken == method.MetadataToken);
}
}
return method;
}
private static IEnumerable<Type> GetBaseTypes(Type type)
{
for (;;)
{
type = type.BaseType;
if (type == null) break;
yield return type;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment