I'm using reflection to retrieve a list of available classes and I want to adapt the input object to the most specific class that matches; i.e. I want to do something like:
RawData input = new RawData() { ... };
Type targetAdapter = AdapterSelector.SelectAdapterFor(input);
AdapterSelector.TryGetAdaptMethod(targetAdapter, out Func<RawData, Adapter> fAdapt);
Adapter adapted = fAdapt(input);
Each applicable class has the following signature:
public class Adapter
{
public static Adapter Adapt(RawData input) { ... }
public static bool Matches(RawData input) { ... }
}
Retrieving the list of classes with the required methods can be done as follows using reflection1:
public partial class AdapterSelector
{
public static IEnumerable<Type> GetApplicableAdapterTypes()
{
IEnumerable<Type> allTypes = typeof(AdapterSelector).Assembly.GetTypes();
IEnumerable<Type> applicableTypes = from t in allTypes
where TryGetAdaptMethod(t, _) &&
TryGetMatchMethod(t, _)
select t;
return applicableTypes;
}
private static bool TryGetAdaptMethod(Type type, out Func<RawData, Adapter> adapt)
{
MethodInfo adaptMethod = type.GetMethod("Adapt", BindingFlags.Static|BindingFlags.Public, null, new Type[] { typeof(RawData) }, null);
if (adaptMethod != null)
{
adapt = (Func<RawData, Adapter>)Delegate.CreateDelegate(typeof(Func<RawData, Adapter>), null, adapterMethod);
return true;
}
else
{
adapt = null;
return false;
}
}
private static bool TryGetMatchMethod(Type type, out Func<RawData, bool> match)
{
MethodInfo matchMethod = type.GetMethod("Match", BindingFlags.Static|BindingFlags.Public, null, new Type[] { typeof(RawData) }, null);
if (matchMethod != null)
{
match = (Func<RawData, bool>)Delegate.CreateDelegate(typeof(Func<RawData, bool>), null, matchMethod);
return true;
}
else
{
match = null;
return false;
}
}
}
I assumed that I could use the following IComparer<Type>
to order this list of adapters:
public class TypeHierarchyComparer: IComparer<Type>
{
public int Compare(Type lhs, Type rhs)
{
if (rhs.IsSubclassOf(lhs))
{
return -1;
}
else if (lhs.IsSubclassOf(rhs))
{
return +1;
}
else
{
// Return arbitrary but fixed value for siblings, cousins, etc.
return lhs.FullName.CompareTo(rhs.FullName);
}
}
}
Note that this is intended to be a partial ordering: if one type is a subclass of the other that type should be ordered after2 the latter. If neither type is a subclass of the other, the order is undefined.
With this ordering I hoped that the following hierarchy of adapters would be sorted correctly3:
Adapter
N
F
H
I
K
E
G
L
M
Q
R
O
B
C
J
P
AA
AB
D
S
T
However, the results of:
AdapterSelector.GetApplicableAdapterTypes().OrderBy((x) => x, new TypeHierarchyComparer());
are:
Adapter
, AA
, AB
, B
, C
, D
, N
, F
, H
, I
, J
, K
, P
, E
, G
, L
, M
, O
, P
, Q
, R
, S
, T
Is the correct order O
and P
should be ordered before AA
, AB
, D
, S
, and T
and O
should be before B
, C
, and J
. It appears that subclasses are ordered after one of their parents but not necessarily after all their parents.
How can I sort classes by specificity? (I.e. ordering the subclasses before/after all their parents)
1) See Assembly.GetTypes()
, Type.GetMethod(string, BindingFlags, Binder, Type[], ParameterModifier[])
, Can you get a Func<T> (or similar) from a MethodInfo object?, and Delegate.CreateDelegate(Type, object, methodInfo)
2) I'm using Enumerable.OrderByDescending<TSource, TKey>(this IEnumerable<TSource>, Func<TSource, TKey>, IComparer<TKey>)
to get the matcher to apply first in the front of the list.
3) The class hierarchy as shown by Visual Studio's Solution Explorer. I cannot disclose the names of the adapters, therefore I used AA
to T
, I preserved the alphabetical order of the names.
Aucun commentaire:
Enregistrer un commentaire