private int ReportField(object root, string depth, FieldInfo fieldInfo)
{
var v = fieldInfo.GetValue( root );
int res = 0;
var ftype = v==null?null:v.GetType();
writer.WriteLine( depth + "<field type=\"{0}\" name=\"{1}\" runtimetype=\"{2}\">",
SecurityElement.Escape( fieldInfo.FieldType.GetFormattedName() ),
SecurityElement.Escape( fieldInfo.Name ),
SecurityElement.Escape( v==null?"-null-":ftype.GetFormattedName())
);
if(v==null)
{
res += IntPtr.Size;
}
else if( ftype.IsArray )
{
// arrays have special treatment b/c we have to work on every array element
// just like a single value.
// TODO refactor this so that arry item and non-array field value share code
var val = v as Array;
res += IntPtr.Size; // reference size
if( val != null && !seenObjects.Contains( val ))
{
seenObjects.Add( val );
var length = GetTotalLength( val );
writer.WriteLine( depth + " <array length=\"{0}\"/>", length );
var eltype = ftype.GetElementType();
if( eltype.IsValueType )
{
if( eltype.IsEnum )
eltype = Enum.GetUnderlyingType( eltype );
try
{
res += Marshal.SizeOf( eltype ) * length;
}
catch( Exception )
{
writer.WriteLine( depth + " <error msg=\"Marshal.SizeOf() failed\"/>" );
}
}
else if( eltype == typeof( string ) )
{
// special case
res += IntPtr.Size * length; // array itself
foreach( string item in val )
{
if( item != null )
{
writer.WriteLine( depth + " <string length=\"{0}\"/>", item.Length );
if(!seenObjects.Contains( val ))
{
seenObjects.Add( val );
res += sizeof( char ) * item.Length + sizeof( int );
}
}
}
}
else
{
res += IntPtr.Size * length; // array itself
foreach( var item in val )
{
if( item != null )
{
writer.WriteLine( depth + " <item type=\"{0}\">", SecurityElement.Escape( item.GetType().GetFormattedName() ) );
res += GatherFromRootRecursively( item, depth + " " );
writer.WriteLine( depth + " </item>");
}
}
}
}
else
{
writer.WriteLine( depth + " <null/>" );
}
}
else if( ftype.IsValueType )
{
if( ftype.IsPrimitive )
{
var val = fieldInfo.GetValue( root );
res += Marshal.SizeOf( ftype );
writer.WriteLine( depth + " <value value=\"{0}\"/>", val );
}
else if( ftype.IsEnum )
{
var val = fieldInfo.GetValue( root );
res += Marshal.SizeOf( Enum.GetUnderlyingType( ftype ) );
writer.WriteLine( depth + " <value value=\"{0}\"/>", val );
}
else
{
// this is a struct. This code assumes that all structs contain only primitive types,
// which is very strong. Structs that contain references will break, and fail to traverse these
// references properly
int s = 0;
try
{
s = Marshal.SizeOf( ftype );
}
catch( Exception )
{
// this breaks if struct has a reference member. We should probably never have such structs, but we'll see...
writer.WriteLine( depth + " <error msg=\"Marshal.SizeOf() failed\"/>" );
}
writer.WriteLine( depth + " <struct size=\"{0}\"/>", s );
res += s;
}
}
else if( ftype == typeof( string ) )
{
// special case
res += IntPtr.Size; // reference size
var val = fieldInfo.GetValue( root ) as string;
if( val != null )
{
writer.WriteLine( depth + " <string length=\"{0}\"/>", val.Length );
if(!seenObjects.Contains( val ))
{
seenObjects.Add( val );
res += sizeof( char ) * val.Length + sizeof( int );
}
}
else
writer.WriteLine( depth + " <null/>" );
}
else
{
// this is a reference
var classVal = fieldInfo.GetValue( root );
res += IntPtr.Size; // reference size
if( classVal != null )
{
res += GatherFromRootRecursively( classVal, depth + " " );
}
else
{
writer.WriteLine( depth + " <null/>" );
}
}
writer.WriteLine( depth + " <total size=\"{0}\"/>", res );
writer.WriteLine( depth + "</field>" );
return res;
}