Download Source Code: InstanceBrowser.zip - 36.23KB
Metadata Class
A custom Metadata class will help us encapsulate all extended functionality suited for instance browsing. It also contains TreeNode-related methods, but other than this, is does not depend on any particular UI.
Here are the filters for "navigable" types, the only types we'll show in our navigation hierarchy:
// True is the type will appear as node
// Excluded types: generics, interfaces,
// primitive types (int, bool etc),
// delegates and EventArg-derived, Exception-derived,
// Attribute-derived
public bool IsNavigable(Type type)
{
return (!type.IsGenericType && !type.IsInterface
&& !IsPrimitive(type)
&& !IsDelegate(type) && !IsEventArgs(type)
&& !IsException(type) && !IsAttribute(type));
}
// Helper functions for a type
public bool IsPrimitive(Type type)
{
return (type.IsPrimitive || type.IsEnum
|| typeof(string).Equals(type));
}
public bool IsDelegate(Type type)
{
return InheritsFrom(type, typeof(System.Delegate));
}
public bool IsException(Type type)
{
return InheritsFrom(type, typeof(System.Exception));
}
public bool IsEventArgs(Type type)
{
return InheritsFrom(type, typeof(System.EventArgs));
}
public bool IsAttribute(Type type)
{
return InheritsFrom(type, typeof(System.Attribute));
}
public bool InheritsFrom(Type type, Type baseType)
{
return baseType.IsAssignableFrom(type);
}As we said, we should be able to navigate from our top-level types. They should either have public static or instance constructors, or expose some static members we can call, with no instantiation required. Once we obtain an object, we will no longer show its static members, they are already available at its class node. For object instances, we will show only public instance methods, "get" properties and fields. For instant navigation, for this version, we will also skip all members that require parameters, including indexed properties.
Here are other helper functions, to get all "navigable" members, for either a class or object, and a simple wrapper to get the right return type:
// "Navigable" members are type members that return
// non-primitive values you can further expand
public bool IsNavigable(MemberInfo member)
{
Type type = ReturnType(member);
return (IsNavigable(type)
&& !InheritsFrom(type, typeof(System.TimeSpan))
&& !InheritsFrom(type, typeof(System.DateTime))
&& !InheritsFrom(type, typeof(System.IO.BinaryWriter))
&& !InheritsFrom(type, typeof(System.IO.TextWriter))
&& !InheritsFrom(type, typeof(System.IO.BinaryReader))
&& !InheritsFrom(type, typeof(System.IO.TextReader))
&& !InheritsFrom(type, typeof(System.IO.Stream))
&& !type.Equals(typeof(object)));
}
// List of instance or static type Members
public List<MemberInfo> GetMembers(Type type, object instance)
{
BindingFlags flags;
List<MemberInfo> members = new List<MemberInfo>();
// Show (default) ctors, only under a Type node
if (instance == null)
{
flags = BindingFlags.Public
| (_showInherited ? 0 : BindingFlags.DeclaredOnly)
| (_showStatic ? BindingFlags.Static : 0)
| (_showConstructors ? BindingFlags.Instance : 0)
| BindingFlags.CreateInstance;
foreach (ConstructorInfo member
in type.GetConstructors(flags))
if (!member.ContainsGenericParameters
&& member.GetParameters().Length == 0)
members.Add(member);
}
// Methods (with no params only)
flags = BindingFlags.Public
| (_showInherited ? 0 : BindingFlags.DeclaredOnly)
| (instance == null ? (_showStatic
? BindingFlags.Static : 0) : BindingFlags.Instance)
| BindingFlags.InvokeMethod;
foreach (MethodInfo member in type.GetMethods(flags))
if (!member.IsSpecialName
&& !member.ContainsGenericParameters
&& member.GetParameters().Length == 0
&& (!_showNavigationOnly || IsNavigable(member))
&& !typeof(void).Equals(ReturnType(member)))
members.Add(member);
// Properties (Get, not indexed)
flags = BindingFlags.Public
| (_showInherited ? 0 : BindingFlags.DeclaredOnly)
| (instance == null ? (_showStatic
? BindingFlags.Static : 0) : BindingFlags.Instance)
| BindingFlags.GetProperty;
foreach (PropertyInfo member in type.GetProperties(flags))
if (!member.IsSpecialName
&& (!_showNavigationOnly || IsNavigable(member))
&& member.GetIndexParameters().Length == 0)
members.Add(member);
// Fields
flags = BindingFlags.Public
| (_showInherited ? 0 : BindingFlags.DeclaredOnly)
| (instance == null ? (_showStatic
? BindingFlags.Static : 0) : BindingFlags.Instance)
| BindingFlags.GetField;
foreach (FieldInfo member in type.GetFields(flags))
if (!member.IsSpecialName
&& (!_showNavigationOnly || IsNavigable(member)))
members.Add(member);
return members;
}
// Return Type of a Member
public Type ReturnType(MemberInfo member)
{
if (member == null)
return null;
if (member is ConstructorInfo)
return (member as ConstructorInfo).DeclaringType;
if (member is MethodInfo)
return (member as MethodInfo).ReturnType;
if (member is PropertyInfo)
return (member as PropertyInfo).PropertyType;
if (member is FieldInfo)
return (member as FieldInfo).FieldType;
Debug.Assert(false);
return null;
}For each tree node representing a type or type member, we'll show a friendly name. The TypeName helps us deal with aliases of primitive types, strings, arrays and generics. MemberSignature will format a simple declaration of a type member, with parameters or not:
// Returns a "nice" type name
public string TypeName(Type type, bool full)
{
// arrays or references
if (type.HasElementType)
return TypeName(type.GetElementType(), full)
+ (type.IsArray ? "[]" : "&");
// primitive type aliases
if (type.IsPrimitive)
{
if (type.Equals(typeof(sbyte))) return "sbyte";
if (type.Equals(typeof(short))) return "short";
if (type.Equals(typeof(int))) return "int";
if (type.Equals(typeof(long))) return "long";
if (type.Equals(typeof(byte))) return "byte";
if (type.Equals(typeof(ushort))) return "ushort";
if (type.Equals(typeof(uint))) return "uint";
if (type.Equals(typeof(ulong))) return "ulong";
if (type.Equals(typeof(float))) return "float";
if (type.Equals(typeof(decimal))) return "decimal";
if (type.Equals(typeof(char))) return "char";
if (type.Equals(typeof(bool))) return "bool";
}
if (type.Equals(typeof(object))) return "object";
if (type.Equals(typeof(void))) return "void";
if (type.Equals(typeof(string))) return "string";
string typename = (full ? type.FullName : type.Name);
if (type.IsGenericType) // generics
{
string s = type.UnderlyingSystemType.ToString();
s = s.Substring(s.IndexOf('[') + 1);
s = s.Substring(0, s.IndexOf(']'));
typename = typename.Substring(0, typename.IndexOf('`'))
+ '<' + s + '>';
}
return typename;
}
// "Nice" display for a Member, including param types
public string MemberSignature(MemberInfo member)
{
string parameters = "";
// Ctors --> add params
if (member.MemberType == MemberTypes.Constructor)
{
ConstructorInfo ctor = member as ConstructorInfo;
ParameterInfo[] parInfos = ctor.GetParameters();
foreach (ParameterInfo parInfo in parInfos)
parameters += (parameters.Length == 0 ? "" : ", ")
+ TypeName(parInfo.ParameterType, false);
parameters = "(" + parameters + ")";
}
// Methods --> add params
else if (member.MemberType == MemberTypes.Method)
{
MethodInfo method = member as MethodInfo;
ParameterInfo[] parInfos = method.GetParameters();
foreach (ParameterInfo parInfo in parInfos)
parameters += (parameters.Length == 0 ? "" : ", ")
+ TypeName(parInfo.ParameterType, false);
parameters = "(" + parameters + ")";
}
return (member.MemberType == MemberTypes.Constructor
? "new " + member.DeclaringType.Name : member.Name)
+ parameters;
}Type and instance members will always be invoked in late-bound mode, at runtime. Our generic Invoke method does just that, for members with no parameters. There will be three specific cases for the returned result: null values, collection objects and errors. In last situation, we intercept and return an Exception instance with the details, ignoring the initial TargetInvocationException, which is always thrown on top, for late-bound calls:
// Runtime dynamic execution of an instance or static type Member
public object Invoke(MemberInfo member, object instance)
{
object result = null;
try
{
Cursor.Current = Cursors.WaitCursor;
if (member is ConstructorInfo)
result = (member as ConstructorInfo).Invoke(null);
else if (member is MethodInfo)
result = (member as MethodInfo).Invoke(instance, null);
else if (member is PropertyInfo)
result = (member as PropertyInfo).GetValue(
instance, null);
else if (member is FieldInfo)
result = (member as FieldInfo).GetValue(instance);
}
catch (Exception ex)
{
result = (ex is TargetInvocationException
&& ex.InnerException != null ? ex.InnerException : ex);
}
finally
{
Cursor.Current = Cursors.Default;
}
return result;
}"Collection" objects should automatically expand into their containing elements. Collections are instances derived from IEnumerable interface, which include arrays, ICollection, IList, IDictionary-based instances. While strings expand into character arrays, we will exclude them from this category. Here are our utilities to determine if a type returns a collection, and to return child nodes with collection elements:
// Collection type, if it has IEnumerable as base class.
// This includes arrays, lists, collections and dictionaries
public bool IsCollection(Type type)
{
return (!typeof(string).Equals(type)
&& InheritsFrom(type, typeof(IEnumerable)));
}
// Fill collection object node with its child objects
public void FillCollection(TreeNode parentNode, object instance)
{
try
{
Cursor.Current = Cursors.WaitCursor;
IEnumerator enumerator
= (instance as IEnumerable).GetEnumerator();
while (enumerator.MoveNext())
AddNode(parentNode.Nodes, enumerator.Current);
}
catch (Exception ex)
{
AddNode(parentNode.Nodes, ex);
}
finally
{
Cursor.Current = Cursors.Default;
}
}Some other functions from our utility Metadata class deal with filling tree child nodes for a particular object. When nodes are deleted, a recursively called ClearWithDispose method will ensure that all disposable objects (i.e. which implement IDisposable), associated with the nodes, will get a Dispose call.