1) Creating a new XML file for a particular type. ex: mono stub.exe --stub Gdk.Rectangle,gdk-sharp > path/to/Rectangle.xml
2) Updating an XML file. Deleted members are removed from the xml file. New members are added. Each deleted/added member is printed to stdout. ex:
mono stub.exe --update ../../gtk-sharp/doc/en Gdk.Rectangle,gdk-sharp > path/to/new/Rectangle.xml
Unfortunately, BindingFlags.IgnoreCase seems to not be working, so I couldn't quickly add a check to see if the case of the member names changed.
3) Creating a fresh XML file for a particular type, but importing the Docs nodes from an existing XML file. Unlike (2), this guarantees the output XML file is properly structured. But, it results in a file that's got a lot of whitespace differences from the original, which would make it difficult to make sure that the program hasn't deleted docs by accident.
ex:
mono stub.exe --regen ../../gtk-sharp/doc/en Gdk.Rectangle,gdk-sharp > path/to/new/Rectangle.xml
John Luke wrote:
It would be super sweet if it is capable of updating all the monodoc/class/* docs as well. They really need an update, since they haven't been updated in ages.
Yup, it could do that.
Good luck to whoever uses this. (Back up all of the docs first!)
-- - Joshua Tauberer
http://taubz.for.net
** Nothing Unreal Exists **
using System; using System.Collections; using System.Text; using System.Reflection; using System.Xml;
public class Stub {
static XmlDocument doc = new XmlDocument();
public static void Main(string[] args) {
if (args.Length == 0) {
Console.WriteLine("Usage: mono stub.exe command");
Console.WriteLine("commands:");
Console.WriteLine("\t--stub type\tCreates a type stub file on stdout.");
Console.WriteLine("\t--update basedir type\tUpdates the file for type to stdout, with the file located in basedir/type.Namespace/type.Name.xml, by adding and removing new members as necessary.");
Console.WriteLine("\t--regen basedir type\tCreates a type stub file on stdout, importing the Docs nodes from the file located in basedir/type.Namespace/type.Name.xml as appropriate.");
return;
}
switch (args[0]) {
case "--stub":
if (args.Length != 2) throw new InvalidOperationException();
DoStubType(args[1]);
break;
case "--update":
if (args.Length != 3) throw new InvalidOperationException();
DoUpdateType(args[1], args[2]);
break;
case "--regen":
if (args.Length != 3) throw new InvalidOperationException();
DoRegenType(args[1], args[2]);
break;
default:
Console.WriteLine("Invalid command. Run with no arguments for help.");
break;
}
}
private static void WriteXml(XmlElement element, System.IO.TextWriter output) {
XmlTextWriter writer = new XmlTextWriter(output);
writer.Formatting = Formatting.Indented;
writer.Indentation = 1;
writer.IndentChar = '\t';
element.WriteTo(writer);
output.WriteLine();
}
public static void DoStubType(string type) {
WriteXml(StubType(type), Console.Out);
}
public static void DoUpdateType(string basepath, string typename) {
Type type = Type.GetType(typename, true);
XmlDocument basefile = new XmlDocument();
basefile.PreserveWhitespace = true;
basefile.Load(basepath + "/" + type.Namespace + "/" + type.Name + ".xml");
XmlElement newfile = StubType(type);
Hashtable seenmembers = new Hashtable();
// Look for deleted members, remember what members are in the file
foreach (XmlElement oldmember in basefile.SelectNodes("Type/Members/Member")) {
MemberInfo oldmember2 = GetMember(type, oldmember);
if (oldmember2 == null) {
// Deleted (or signature changed)
Console.Error.WriteLine("Member Deleted: " + oldmember.SelectSingleNode("MemberSignature/@Value").InnerText);
oldmember.ParentNode.RemoveChild(oldmember);
continue;
}
seenmembers[oldmember2] = 1;
}
XmlNode members = basefile.SelectSingleNode("Type/Members");
foreach (MemberInfo m in type.GetMembers(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance|BindingFlags.DeclaredOnly)) {
if (m is Type) continue;
if (seenmembers.ContainsKey(m)) continue;
XmlElement mm = MakeMember(m);
if (mm == null) continue;
members.AppendChild( basefile.ImportNode(mm, true) );
members.AppendChild( members.OwnerDocument.CreateWhitespace("\n") );
Console.Error.WriteLine("Member Added: " + mm.SelectSingleNode("MemberSignature/@Value").InnerText);
}
WriteXml(basefile.DocumentElement, Console.Out);
}
public static void DoRegenType(string basepath, string typename) {
Type type = Type.GetType(typename, true);
XmlDocument basefile = new XmlDocument();
basefile.PreserveWhitespace = true;
basefile.Load(basepath + "/" + type.Namespace + "/" + type.Name + ".xml");
XmlElement newfile = StubType(type);
// Copy old Type/Docs into new doc
newfile.ReplaceChild(newfile.OwnerDocument.ImportNode(basefile.SelectSingleNode("Type/Docs"), true), newfile.SelectSingleNode("Docs"));
// Copy old Type/Members/Member/Docs into new doc
foreach (XmlElement oldmember in basefile.SelectNodes("Type/Members/Member")) {
XmlElement newmember = FindMatchingMember(type, newfile, oldmember);
if (newmember == null) {
Console.Error.WriteLine("Member Deleted: " + oldmember.SelectSingleNode("MemberSignature/@Value").InnerText);
continue;
}
newmember.ReplaceChild(newmember.OwnerDocument.ImportNode(oldmember.SelectSingleNode("Docs"), true), newmember.SelectSingleNode("Docs"));
}
WriteXml(newfile, Console.Out);
}
// UPDATE HELPER FUNCTIONS
private static XmlElement FindMatchingMember(Type type, XmlElement newfile, XmlElement oldmember) {
MemberInfo oldmember2 = GetMember(type, oldmember);
if (oldmember2 == null) return null;
string membername = oldmember.GetAttribute("MemberName");
foreach (XmlElement newmember in newfile.SelectNodes("Members/[EMAIL PROTECTED]'" + membername + "']")) {
if (GetMember(type, newmember) == oldmember2) return newmember;
}
return null;
}
private static MemberInfo GetMember(Type type, XmlElement member) {
string membertype = member.SelectSingleNode("MemberType").InnerText;
// Get list of parameter types for member
ArrayList memberparams = new ArrayList();
foreach (XmlElement param in member.SelectNodes("Parameters/Parameter"))
memberparams.Add(param.GetAttribute("Type"));
// Loop through all members in this type with the same name
MemberInfo[] mis = type.GetMember(member.GetAttribute("MemberName"), BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (MemberInfo mi in mis) {
if (mi is Type) continue;
if (GetMemberType(mi) != membertype) continue;
ParameterInfo[] pis = null;
if (mi is MethodInfo || mi is ConstructorInfo)
pis = ((MethodBase)mi).GetParameters();
else if (mi is PropertyInfo)
pis = ((PropertyInfo)mi).GetIndexParameters();
if (pis == null)
pis = new ParameterInfo[0];
if (pis.Length != memberparams.Count) continue;
bool good = true;
for (int i = 0; i < pis.Length; i++)
if (pis[i].ParameterType.FullName != (string)memberparams[i]) { good = false; break; }
if (!good) continue;
return mi;
}
return null;
}
// CREATE A STUB OF A DOCUMENTATION FILE
public static XmlElement StubType(string type) {
return StubType(Type.GetType(type, true));
}
public static XmlElement StubType(Type type) {
string typesig = MakeTypeSignature(type);
if (typesig == null) return null; // not publicly visible
XmlElement root = doc.CreateElement("Type");
root.SetAttribute("Name", type.Name);
root.SetAttribute("FullName", type.FullName);
XmlElement sig = doc.CreateElement("TypeSignature");
root.AppendChild(sig);
sig.SetAttribute("Language", "C#");
sig.SetAttribute("Value", typesig);
XmlElement ass = doc.CreateElement("AssemblyInfo");
root.AppendChild(ass);
ass.AppendChild(SimpleElement("AssemblyName", type.Assembly.GetName().Name));
ass.AppendChild(SimpleElement("AssemblyVersion", type.Assembly.GetName().Version.ToString()));
ass.AppendChild(SimpleElement("AssemblyCulture", type.Assembly.GetName().CultureInfo.Name));
XmlElement assattributes = MakeAttributes(type.Assembly);
if (assattributes != null) ass.AppendChild(assattributes);
XmlElement basetype = doc.CreateElement("Base");
root.AppendChild(basetype);
basetype.AppendChild(SimpleElement("BaseTypeName", type.BaseType.FullName));
if (!IsDelegate(type) && !type.IsInterface) {
XmlElement interfaces = doc.CreateElement("Interfaces");
root.AppendChild(interfaces);
foreach (Type i in type.GetInterfaces()) {
XmlElement iface = doc.CreateElement("Interface");
interfaces.AppendChild(iface);
iface.AppendChild(SimpleElement("InterfaceName", i.FullName));
}
}
XmlElement attributes = MakeAttributes(type);
if (attributes != null) root.AppendChild(attributes);
if (IsDelegate(type)) {
root.AppendChild(MakeParameters(type.GetMethod("Invoke").GetParameters()));
root.AppendChild(MakeReturnValue(type.GetMethod("Invoke").ReturnType));
}
if (!IsDelegate(type)) {
XmlElement members = doc.CreateElement("Members");
root.AppendChild(members);
foreach (MemberInfo m in type.GetMembers(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance|BindingFlags.DeclaredOnly)) {
if (m is Type) continue;
XmlElement mm = MakeMember(m);
if (mm == null) continue;
members.AppendChild( mm );
}
}
root.AppendChild(MakeDocNode(null, null, true));
return root;
}
// STUB HELPER FUNCTIONS
private static XmlElement SimpleElement(string name, string val) {
XmlElement e = doc.CreateElement(name);
e.InnerText = val;
return e;
}
private static XmlElement MakeDocNode(ParameterInfo[] parameters, Type returntype, bool returnisreturn) {
XmlElement e = doc.CreateElement("Docs");
e.AppendChild(SimpleElement("summary", "To be added."));
if (parameters != null) {
foreach (ParameterInfo p in parameters) {
XmlElement pe = SimpleElement("param", "To be added.");
pe.SetAttribute("name", p.Name);
e.AppendChild(pe);
}
}
if (returntype != null && returntype != typeof(void)) {
e.AppendChild(SimpleElement(returnisreturn ? "returns" : "value", "To be added."));
}
e.AppendChild(SimpleElement("remarks", "To be added."));
return e;
}
private static XmlElement MakeAttributes(ICustomAttributeProvider attributes) {
object[] at = attributes.GetCustomAttributes(false);
if (at.Length == 0) return null;
bool b = false;
XmlElement e = doc.CreateElement("Attributes");
foreach (Attribute a in at) {
if (GetTypeVisibility(a.GetType().Attributes) == null) continue; // hide non-visible attributes
b = true;
ArrayList fields = new ArrayList();
foreach (PropertyInfo f in a.GetType().GetProperties(BindingFlags.Public|BindingFlags.Instance)) {
if (f.Name == "TypeId") continue;
object v = f.GetValue(a, null);
if (v == null) v = "null";
else if (v is string) v = "\"" + v + "\"";
else if (v is Type) v = "typeof(" + ((Type)v).FullName + ")";
else if (v is Enum) v = v.GetType().FullName + "." + v;
fields.Add(f.Name + "=" + v);
}
string a2 = String.Join(", ", (string[])fields.ToArray(typeof(string)));
if (a2 != "") a2 = "(" + a2 + ")";
XmlElement ae = doc.CreateElement("Attribute");
e.AppendChild(ae);
string name = a.GetType().FullName;
if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length-"Attribute".Length);
ae.AppendChild( SimpleElement("AttributeName", name + a2) );
}
if (!b) return null;
return e;
}
private static XmlElement MakeParameters(ParameterInfo[] parameters) {
XmlElement e = doc.CreateElement("Parameters");
foreach (ParameterInfo p in parameters) {
XmlElement pe = doc.CreateElement("Parameter");
e.AppendChild(pe);
pe.SetAttribute("Name", p.Name);
pe.SetAttribute("Type", p.ParameterType.FullName);
if (p.ParameterType.IsByRef) {
if (p.IsOut) pe.SetAttribute("RefType", "out");
else pe.SetAttribute("RefType", "ref");
}
XmlElement attributes = MakeAttributes(p);
if (attributes != null) pe.AppendChild(attributes);
}
return e;
}
private static XmlElement MakeParameters(MemberInfo mi) {
if (mi is ConstructorInfo) return MakeParameters(((ConstructorInfo)mi).GetParameters());
if (mi is MethodInfo) return MakeParameters(((MethodInfo)mi).GetParameters());
if (mi is PropertyInfo) {
ParameterInfo[] parameters = ((PropertyInfo)mi).GetIndexParameters();
if (parameters.Length > 0)
return MakeParameters(parameters);
else
return null;
}
if (mi is FieldInfo) return null;
if (mi is EventInfo) return null;
throw new ArgumentException();
}
private static XmlElement MakeReturnValue(Type type, ICustomAttributeProvider attributes) {
XmlElement e = doc.CreateElement("ReturnValue");
e.AppendChild( SimpleElement("ReturnType", type.FullName) );
if (attributes != null) {
XmlElement a = MakeAttributes(attributes);
if (a != null) e.AppendChild(a);
}
return e;
}
private static XmlElement MakeReturnValue(MemberInfo mi) {
if (mi is ConstructorInfo) return null;
if (mi is MethodInfo) return MakeReturnValue(((MethodInfo)mi).ReturnType, ((MethodInfo)mi).ReturnTypeCustomAttributes);
if (mi is PropertyInfo) return MakeReturnValue(((PropertyInfo)mi).PropertyType, null);
if (mi is FieldInfo) return MakeReturnValue(((FieldInfo)mi).FieldType, null);
if (mi is EventInfo) return MakeReturnValue(((EventInfo)mi).EventHandlerType, null);
throw new ArgumentException();
}
private static XmlElement MakeMember(MemberInfo mi) {
string sigs = MakeMemberSignature(mi);
if (sigs == null) return null; // not publicly visible
// no documentation for property/event accessors. Is there a better way of doing this?
if (mi.Name.StartsWith("get_")) return null;
if (mi.Name.StartsWith("set_")) return null;
if (mi.Name.StartsWith("add_")) return null;
if (mi.Name.StartsWith("remove_")) return null;
if (mi.Name.StartsWith("raise_")) return null;
XmlElement me = doc.CreateElement("Member");
me.SetAttribute("MemberName", mi.Name);
XmlElement sig = doc.CreateElement("MemberSignature");
me.AppendChild(sig);
sig.SetAttribute("Language", "C#");
sig.SetAttribute("Value", sigs);
me.AppendChild( SimpleElement("MemberType", GetMemberType(mi)) );
XmlElement a = MakeAttributes(mi);
if (a != null) me.AppendChild(a);
XmlElement retval = MakeReturnValue(mi);
if (retval != null) me.AppendChild(retval);
XmlElement parameters = MakeParameters(mi);
if (parameters != null) me.AppendChild(parameters);
if (mi is FieldInfo && (((FieldInfo)mi).IsLiteral || (((FieldInfo)mi).IsStatic && ((FieldInfo)mi).IsInitOnly))) {
object val = ((FieldInfo)mi).GetValue(null);
if (val is IConvertible)
me.AppendChild( SimpleElement("MemberValue", ((IConvertible)val).ToString(null)) );
}
if (mi is MethodInfo)
me.AppendChild(MakeDocNode(((MethodInfo)mi).GetParameters(), ((MethodInfo)mi).ReturnType, true));
else if (mi is ConstructorInfo)
me.AppendChild(MakeDocNode(((ConstructorInfo)mi).GetParameters(), null, false));
else if (mi is PropertyInfo)
me.AppendChild(MakeDocNode(((PropertyInfo)mi).GetIndexParameters(), ((PropertyInfo)mi).PropertyType, false));
else
me.AppendChild(MakeDocNode(null, null, false));
return me;
}
static bool IsDelegate(Type type) {
return typeof(System.Delegate).IsAssignableFrom (type) && !type.IsAbstract;
}
/// SIGNATURE GENERATION FUNCTIONS
static string GetTypeKind (Type t) {
if (t.IsEnum) return "enum";
if (t.IsClass) return "class";
if (t.IsInterface) return "interface";
if (t.IsValueType) return "struct";
throw new ArgumentException();
}
static string GetTypeVisibility (TypeAttributes ta) {
switch (ta & TypeAttributes.VisibilityMask){
case TypeAttributes.Public:
case TypeAttributes.NestedPublic:
return "public";
case TypeAttributes.NestedFamily:
case TypeAttributes.NestedFamORAssem:
return "protected";
default:
return null;
}
}
static string MakeTypeSignature (Type type) {
StringBuilder sig = new StringBuilder();
string visibility = GetTypeVisibility(type.Attributes);
if (visibility == null) return null;
sig.Append(visibility);
sig.Append(" ");
if (type.IsAbstract) sig.Append("abstract ");
if (type.IsSealed && !IsDelegate(type)) sig.Append("sealed ");
if (IsDelegate(type)) {
MethodInfo invoke = type.GetMethod ("Invoke");
string arguments = GetMethodParameters(invoke.GetParameters());
string return_value = ConvertCTSName(invoke.ReturnType.FullName);
sig.Append(return_value);
sig.Append(" ");
sig.Append(type.Name);
sig.Append("(");
sig.Append(arguments);
sig.Append(")");
return sig.ToString();
}
sig.Append(GetTypeKind(type));
sig.Append(" ");
sig.Append(type.Name);
if (!type.IsValueType && !type.IsEnum) {
if ((type.BaseType != null && type.BaseType != typeof(object)) || type.GetInterfaces().Length > 0)
sig.Append(" : ");
if (type.BaseType != null && type.BaseType != typeof(object)) {
sig.Append(type.BaseType.FullName);
if (type.GetInterfaces().Length > 0)
sig.Append(", ");
}
Type [] interfaces = type.GetInterfaces ();
for (int i = 0; i < interfaces.Length; i ++){
if (i != 0) sig.Append(", ");
sig.Append(interfaces [i].FullName);
}
}
return sig.ToString();
}
static string GetFieldVisibility (FieldInfo field) {
if (field.IsPublic) return "public";
if (field.IsFamily) return "protected";
return null;
}
static string MakeFieldSignature (FieldInfo field) {
string visibility = GetFieldVisibility (field);
if (visibility == null) return null;
string type = ConvertCTSName (field.FieldType.FullName);
string modifiers = String.Empty;
if (field.IsStatic) modifiers += " static";
if (field.IsInitOnly) modifiers += " readonly";
if (field.IsLiteral) modifiers += " const";
return String.Format ("{0}{1} {2} {3}",
visibility, modifiers, type, field.Name);
}
static string GetMethodVisibility (MethodBase method) {
if (method.IsPublic) return "public";
if (method.IsFamily) return "protected";
return null;
}
static string GetMethodParameters (ParameterInfo[] pi) {
if (pi.Length == 0) return "";
StringBuilder sb = new StringBuilder ();
int i = 0;
string modifier;
foreach (ParameterInfo parameter in pi) {
if (i != 0) sb.Append (", ");
if (parameter.ParameterType.IsByRef) {
if (parameter.IsOut) sb.Append("out ");
else sb.Append("ref ");
}
string param = ConvertCTSName (parameter.ParameterType.FullName, parameter.ParameterType.IsByRef);
sb.Append (param);
sb.Append (" ");
sb.Append (parameter.Name);
i++;
}
return sb.ToString();
}
static string MakeMethodSignature (MethodInfo method) {
string visibility = GetMethodVisibility (method);
if (visibility == null)
return null;
string modifiers = String.Empty;
if (method.IsStatic) modifiers += " static";
if (method.IsVirtual) {
if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
else modifiers += " override";
}
if (method.IsAbstract) modifiers += " abstract";
if (method.IsFinal) modifiers += " sealed";
string return_type = ConvertCTSName (method.ReturnType.FullName);
string parameters = GetMethodParameters (method.GetParameters());
string method_name = method.Name;
// operators, default accessors need name rewriting
return String.Format ("{0}{1} {2} {3}({4})",
visibility, modifiers, return_type, method_name, parameters);
}
static string MakeConstructorSignature (ConstructorInfo constructor) {
string visibility = GetMethodVisibility (constructor);
if (visibility == null)
return null;
string name = constructor.DeclaringType.Name;
string parameters = GetMethodParameters (constructor.GetParameters());
return String.Format ("{0} {1}({2})",
visibility, name, parameters);
}
static string MakePropertySignature (PropertyInfo property) {
// pick an accessor
MethodBase method = property.GetSetMethod (true);
if (method == null)
method = property.GetGetMethod (true);
string visibility = GetMethodVisibility(method);
if (visibility == null)
return null;
string modifiers = String.Empty;
if (method.IsStatic) modifiers += " static";
if (method.IsVirtual) {
if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
else modifiers += " override";
}
if (method.IsAbstract) modifiers += " abstract";
if (method.IsFinal) modifiers += " sealed";
string name = property.Name;
string type_name = property.PropertyType.FullName;
type_name = ConvertCTSName (type_name);
string parameters = GetMethodParameters (property.GetIndexParameters());
if (parameters != "") parameters = "[" + parameters + "]";
string accessors = null;
if (property.CanRead && property.CanWrite)
accessors = "{ set; get; }";
else if (property.CanRead)
accessors = "{ get; }";
else if (property.CanWrite)
accessors = "{ set; }";
return String.Format ("{0}{1} {2} {3}{4} {5}",
visibility, modifiers, type_name, name, parameters, accessors);
}
static string MakeEventSignature (EventInfo ev) {
MethodInfo add = ev.GetAddMethod ();
string visibility = GetMethodVisibility(add);
if (visibility == null)
return null;
string modifiers = String.Empty;
if (add.IsStatic) modifiers += " static";
if (add.IsVirtual) {
if ((add.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
else modifiers += " override";
}
if (add.IsAbstract) modifiers += " abstract";
if (add.IsFinal) modifiers += " sealed";
string name = ev.Name;
string type = ConvertCTSName(ev.EventHandlerType.FullName);
return String.Format ("{0}{1} event {2} {3}",
visibility, modifiers, type, name);
}
static string MakeMemberSignature(MemberInfo mi) {
if (mi is ConstructorInfo) return MakeConstructorSignature((ConstructorInfo)mi);
if (mi is MethodInfo) return MakeMethodSignature((MethodInfo)mi);
if (mi is PropertyInfo) return MakePropertySignature((PropertyInfo)mi);
if (mi is FieldInfo) return MakeFieldSignature((FieldInfo)mi);
if (mi is EventInfo) return MakeEventSignature((EventInfo)mi);
throw new ArgumentException(mi.ToString());
}
static string GetMemberType(MemberInfo mi) {
if (mi is ConstructorInfo) return "Constructor";
if (mi is MethodInfo) return "Method";
if (mi is PropertyInfo) return "Property";
if (mi is FieldInfo) return "Field";
if (mi is EventInfo) return "Event";
throw new ArgumentException();
}
static string ConvertCTSName (string type, bool shorten)
{
if (shorten)
type = type.Substring (0, type.Length - 1);
string retval = ConvertCTSName (type);
return retval;
}
//
// Utility function: converts a fully .NET qualified type name into a C#-looking one
//
static string ConvertCTSName (string type) {
if (type.EndsWith ("[]"))
return ConvertCTSName(type.Substring(0, type.Length - 2).TrimEnd()) + "[]";
if (type.EndsWith ("&"))
return ConvertCTSName(type.Substring(0, type.Length - 1).TrimEnd()) + "&";
if (type.EndsWith ("*"))
return ConvertCTSName(type.Substring(0, type.Length - 1).TrimEnd()) + "*";
if (!type.StartsWith ("System."))
return type;
switch (type) {
case "System.Byte": return "byte";
case "System.SByte": return "sbyte";
case "System.Int16": return "short";
case "System.Int32": return "int";
case "System.Int64": return "long";
case "System.UInt16": return "ushort";
case "System.UInt32": return "uint";
case "System.UInt64": return "ulong";
case "System.Single": return "float";
case "System.Double": return "double";
case "System.Decimal": return "decimal";
case "System.Boolean": return "bool";
case "System.Char": return "char";
case "System.Void": return "void";
case "System.String": return "string";
case "System.Object": return "object";
}
return type;
}
}
