Now that I teach the Essential .NET class for DevelopMentor, I find myself scouring the MSDN reference documentation looking for specific types. After doing this for an hour or so, I got to thinking – there has to be a better way. So I started out with a fairly simple application which used reflection and a simple string.Contains to browse for certain classes in the framework. Because of the naming convention of ending the class name with the name of the class you are inheriting from this worked quite nicely.
The form looked like this:
However, I soon needed more features like:
- looking for certain methods
- being able to delete items from the tree manually, when I found they didn’t apply
- displaying the count of elements found at any location
So I kept adding functionality.
Then I needed the ability to combine multiple filters at one time, like looking for Asynchronous Programming Model (APM) methods. This is where things got interesting. To do this I created a set of filters. A filter is really just class that stores some information and contains boolean function that acts upon a type to figure out if it should be included in the result set. I created in interface for this purpose which looked like this:
interface IFilter { bool IsTypeAllowed(Type t); }
The implementation of this interface is pretty easy. Basically checking each the name of each type against a set of regular expressions
public bool IsTypeAllowed(Type t) { if ((Type & TypeSearchType.Namespace) != 0) { if (t.Namespace == null) return false; if (!Regex.Match(t.Namespace).Success) return false; } if ((Type & TypeSearchType.Type) != 0) { if (!Regex.Match(t.Name).Success) return false; } if ((Type & TypeSearchType.Member) != 0) { found = false; foreach (MemberInfo mi in t.GetMembers()) { if (Regex.Match(mi.Name).Success) found = true; } if (!found) return false; } return true; }
This too worked really well until I needed to filter out methods. I needed a way of returning a subset of the methods on the type which met the filter criteria. To accomplish this I had to hackmodify the interface to look like this:
interface IFilter { bool IsTypeAllowed(Type t, out List<MemberInfo> mis); }
Then I had to modify the behavior to look like this (I added inheritance checking and parameter type checking while I was in there poking around)
public bool IsTypeAllowed(Type t, out List<MemberInfo> mis) { mis = null; if (filterNullNamespaces && t.Namespace == null) return false; if ((Type & TypeSearchType.Inherits) != 0) { if (!IsBaseTypeAllowed(t)) return false; } if ((Type & TypeSearchType.Namespace) != 0) { if (!Regex.Match(t.Namespace).Success) return false; } if ((Type & TypeSearchType.Type) != 0) { if (!Regex.Match(t.Name).Success) return false; } if ((Type & TypeSearchType.Member) != 0) { if (!IsMemberAllowed(t, out mis)) return false; } return true; } bool IsBaseTypeAllowed(Type t) { bool foundType = false; while (t.BaseType != null) { t = t.BaseType; if (Regex.Match(t.Name).Success) { foundType = true; break; } } return foundType; } bool IsMemberAllowed(Type t, out List<MemberInfo> mis) { mis = null; bool foundType = false; foreach (MemberInfo mi in t.GetMembers()) { bool foundMethod = false; if (((mi.MemberType & (MemberTypes.Constructor | MemberTypes.Method)) != 0) && (Type == TypeSearchType.ParameterType)) { MethodBase mb = (MethodBase)mi; foreach (ParameterInfo pi in mb.GetParameters()) { if (Regex.Match(pi.ParameterType.Name).Success) { foundMethod = true; break; } } } else { foundMethod = Regex.Match(mi.Name).Success; } if (foundMethod) { if (mis == null) mis = new List<MemberInfo>(); mis.Add(mi); foundMethod = false; foundType = true; } } return foundType; }
Then on to the boolean logic piece. There are essentially 3 binary operators (that I cared about): NOT, AND, and OR. NOT is a unary operator, whereas both AND and OR are binary. I chose to create three new classes that contain the simple Filter that I had created earlier. The benefit of containing rather than inheriting/overriding is that I was able to encapsulate all of the logic in one and only one place.
The other problem was that regardless of how complicated the final expression was I wanted to be able to perform a NOT on it to produce the sets complement. To do that I had to Add another method on the interface so that all filter types could produce a NOT. The result of all that refactoring looked like this:
class NotFilter : IFilter { IFilter subFilter; public NotFilter(IFilter subFilter) { this.subFilter = subFilter; } public IFilter CreateNot() { return subFilter; } public bool IsTypeAllowed(Type t, out List<MemberInfo> mis) { mis = null; List<MemberInfo> subMis; bool subAllowed = subFilter.IsTypeAllowed(t, out subMis); if (subMis != null) { mis = new List<MemberInfo>(); foreach (MemberInfo mi in t.GetMembers()) { if (!subMis.Contains(mi)) mis.Add(mi); } } return !subAllowed; } public override string ToString() { return "!" + subFilter.ToString(); } } class AndFilter : IFilter { IFilter sub1; IFilter sub2; public AndFilter(IFilter sub1, IFilter sub2) { this.sub1 = sub1; this.sub2 = sub2; } public IFilter CreateNot() { return new OrFilter(sub1.CreateNot(), sub2.CreateNot()); } public bool IsTypeAllowed(Type t, out List<MemberInfo> mis) { mis = null; List<MemberInfo> mis1; List<MemberInfo> mis2 = null; bool allowed = sub1.IsTypeAllowed(t, out mis1) && sub2.IsTypeAllowed(t, out mis2); if (allowed) { if (mis1 != null) mis = mis1; if ((mis == null) && (mis2 != null)) mis = mis2; if ((mis1 != null) && (mis2 != null)) { mis = new List<MemberInfo>(); foreach (MemberInfo mi in mis2) { if (mis1.Contains(mi)) mis.Add(mi); } } } return allowed; } public override string ToString() { return string.Format("({0}) && ({1})", sub1.ToString(), sub2.ToString()); } } class OrFilter : IFilter { IFilter sub1; IFilter sub2; public OrFilter(IFilter sub1, IFilter sub2) { this.sub1 = sub1; this.sub2 = sub2; } public IFilter CreateNot() { return new AndFilter(sub1.CreateNot(), sub2.CreateNot()); } public bool IsTypeAllowed(Type t, out List<MemberInfo> mis) { mis = null; List<MemberInfo> mis1 = null; List<MemberInfo> mis2 = null; bool allowed1 = sub1.IsTypeAllowed(t, out mis1); bool allowed2 = sub2.IsTypeAllowed(t, out mis2); bool allowed = allowed1 || allowed2; if (allowed) { if (mis1 != null) mis = mis1; if ((mis == null) && (mis2 != null)) mis = mis2; if ((mis1 != null) && (mis2 != null)) { foreach (MemberInfo mi in mis2) { if (!mis.Contains(mi)) mis.Add(mi); } } } return allowed; } public override string ToString() { return string.Format("({0}) || ({1})", sub1.ToString(), sub2.ToString()); } }
I also had to refactor the UI a little bit. The form now looks like this:
Notice that I got a little bit carried away and placed a Flags button on the UI (initially disabled).
There was one caveat. If you were to give me any random boolean expression, I wouldn’t necessarily be able to express it in the program. Expressed in tree form, my program could only produce expressions that looked like:
However, the program was working great; I had no major flaws in the design (that I could see), and I was able to perform some pretty nifty expressions which I will discuss in another blog entry.