-
Notifications
You must be signed in to change notification settings - Fork 3
Using the AspectObjectDumper
The dumper is implemented by the class ObjectTextDumper
in the namespace vm.Aspects.Diagnostics
.
Here is the first usage example that we are going to improve on further down:
using System;
using System.IO;
using vm.Aspects.Diagnostics;
namespace ObjectDumperSamples
{
class MyClass
{
public bool BoolProperty { get; set; }
public int IntProperty { get; set; }
public Guid GuidProperty { get; set; }
public Uri UriProperty { get; set; }
}
class Program
{
static void Main()
{
int anInt = 5;
var anObject = new MyClass
{
BoolProperty = true,
IntProperty = 3,
GuidProperty = Guid.NewGuid(),
};
using (var writer = new StringWriter())
{
var dumper = new ObjectTextDumper(writer);
// dump a primitive value:
dumper.Dump(anInt);
// dump complex value:
dumper.Dump(anObject);
Console.WriteLine(writer.GetStringBuilder().ToString());
}
Console.ReadKey(true);
}
}
}
This is the output from this program:
5
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
BoolProperty = True
GuidProperty = 6e27359b-c1b1-48c5-bf69-967b7fda886c
IntProperty = 3
UriProperty = <null>
I prefer the following two simple facades implemented as extension methods to System.Object. Just replace the using statement above with this code snippet:
using (var writer = new StringWriter())
{
anInt.DumpText(writer);
anObject.DumpText(writer);
Console.WriteLine(writer.GetStringBuilder().ToString());
}
Or replace the entire using statement with:
Console.WriteLine(anInt.DumpString());
Console.WriteLine(anObject.DumpString());
If you don't have better ideas for overriding MyClass.ToString()
, why not implement it like this:
public override string ToString()
{
return this.DumpString();
}
And now we can replace the last line in the snippet with:
Console.WriteLine(anObject);
The programmer has control over the dump through attributes associated with the classes, properties and fields of the dumped objects. The attributes can be applied:
-
Directly on the class and its properties and fields.
-
Indirectly in a so called buddy class - class referred to in a
MetadataTypeAttribute
on your class. -
By using the parameters of the Dump method associate a type of objects with after-the-fact written buddy-class.
-
By using the method of the static class
ClassMetadataResolver.SetClassDumpData
. The signature of the method is self-explanatory:public static void SetClassDumpData( Type type, Type metadata = null, DumpAttribute dumpAttribute = null);
If the second and third parameters are null-s their respective values are extracted from the attributes applied to the target type and if not found there - default values are assumed. The ClassMetadataResolver
contains a static cache associating types with dumping metadata. This facility is most useful for BCL or third party classes.
Let's go back to the constructor of the ObjectTextDumper class:
public ObjectTextDumper(
TextWriter writer,
int indentLevel = 0,
int indentLength = 2,
int maxDumpLength = 4*1024*1024,
BindingFlags propertiesBindingFlags = BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance|BindingFlags.DeclaredOnly,
BindingFlags fieldsBindingFlags = BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly);
It takes an object of type descending from TextWriter
; the initial value for the indent level; the size of the indent - the number of spaces in a single indent, the fourth parameter sets a limit on the dumped text. This would be useful when dumping objects with enormous graphs - you may run out of memory. The default value is 4 million characters (~ 8MB). The last two parameters control which properties and fields should be dumped with respect to their visibility and lifetime modifiers. By default all public and non-public instance properties and all public instance fields are being dumped. From that point on properties and fields are treated equally and for the sake of brevity in this document I will use only the word "property" and unless explicitly stated otherwise, please assume the same applies to fields.
Let's look at the Dump method too:
public object Dump(
object value,
Type dumpMetadata = null,
DumpAttribute dumpAttribute = null);
The method returns the dumper object itself, just in case you want to chain the Dump calls. As you can see here, it takes actually three parameters, where the last two default to null-s:
- The first parameter is the object that we want to dump.
- The second parameter is supposed to be a Type object representing the dumping metadata for the type of the dumped object.
- The third parameter applies a
DumpAttribute
to the type of the dumped object.
The last two parameters override the dumped object's buddy class and DumpAttribute
.
The heart of the customization features of the dumper is the attribute class DumpAttribute
. For a more detailed description take a look at the documentation comments in the source code or the class documentation generated off of them.
The DumAttribute
attribute can be applied to class
-es, struct
-s, properties and fields. When applied to class
-es or struct
-s, only a few of the parameters are applicable and they affect the dump of all properties and fields from the type, unless overridden by DumAttribute applied to a specific property or field.
It is much more common though to apply the attribute to properties and public fields. Let's look at specific scenarios that will demonstrate the customization of the dump behavior.
[Dump(false)] public string Unimportant { get; set; }
Alternatively:
[Dump(Skip=ShouldDump.Skip)]
public string Unimportant { get; set; }
class MyClass
{
[Dump(2)] // or [Dump(Order=2)]
public bool BoolProperty { get; set; }
[Dump(1)]
public int IntProperty { get; set; }
[Dump(-1)]
public Guid GuidProperty { get; set; }
[Dump(0)]
public Uri UriProperty { get; set; }
}
This change to the type of the dumped object produces the following output:
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
UriProperty = <null>
IntProperty = 3
BoolProperty = True
GuidProperty = d8de41d8-14f3-4cf0-a1b6-fb18396be0e6
These are the recursive ordering rules:
- First are dumped the properties of the base class (if any) with positive or non-specified order. Properties with the same order number are dumped in alphabetical order.
- Next are dumped the current class properties with positive or unspecified order.
- Follow the properties with positive or unspecified order of the derived class (if any.)
- Then are dumped the properties with negative order in the opposite order: the derived, the current, the base class'.
- Finally are dumped all properties from all classes with dump order int.MinValue. This pushes some properties really to the end of the dump, for example the stack in an Exception object.
So if we inherit from MyClass
in MyClassDescendant
:
class MyClassDescendant : MyClass
{
[Dump(0)]
public string StringProperty { get; set; }
}
a dump of an object of type MyClassDescendant will look like this:
MyClassDescendant (ObjectDumperSamples.MyClassDescendant, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
UriProperty = <null>
IntProperty = 3
BoolProperty = True
StringProperty = StringProperty
GuidProperty = fe0e72b2-196f-424a-a011-7f8119c04ead
[Dump(0, DumpNullValues=ShouldDump.Skip)]
public Uri UriProperty { get; set; }
With this modification the last dump becomes:
MyClassDescendant (ObjectDumperSamples.MyClassDescendant, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
StringProperty = StringProperty
GuidProperty = e6559163-6b53-4c9a-aaf5-8bf620d9155a
Note that the property DumpNullValues
can be applied also on a class level. Then any property with value null will be skipped in the dump output.
[Dump(Mask=true)]
public string SSN { get; set }
If we add this property with its attribute to MyClass the dump will look something like this:
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
GuidProperty = 1b862d91-ccb1-4b92-977d-d4f723c4d39d
SSN = ******
If SSN is null the output would be:
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
GuidProperty = 64d2112c-b8d9-4a67-922e-87d02e5da3ca
SSN = <null>
If you want different string for mask, use the DumpAttribute
's property MaskValue
:
[Dump(Mask=true, MaskValue="------")]
public string SSN { get; set }
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
GuidProperty = 4e7fe09e-81b7-4527-b4b6-32e42ade950c
SSN = ------
Clearly, these features were introduced with logging in mind.
When applied to strings it specifies to dump only the first MaxLength
characters. By default the dumper will dump the entire string. Let's add this property:
[Dump(MaxLength=25)]
public string Description { get; set; }
and assign a long value to it:
var anObject = new MyClass
{
...
Description = "This is one very very very very very long description",
};
The output would be:
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
Description = This is one very very ver...
GuidProperty = 07d23136-1ae5-40d8-bebc-8ff2a4fefb44
The property can be applied to sequences of objects too - arrays, lists, etc. By default the dumper outputs no more than the first 10 elements of the sequence. If you want to dump them all (at your own risk) specify a negative value, e.g. -1.
By default the property label is dumped using the format string "{0,-24} = ", where the property’s name is passed as parameter 0. Let's reuse the previous example:
[Dump(MaxLength=25, LabelFormat="{0,-12} (Truncated)")]
public string Description { get; set; }
The property will be dumped like this:
Description (Truncated) = This is one very very ver...
The default value of this property is "{0}". I.e. the default format of the value is used. Let's add another property with custom format for the value, e.g.
[Dump(ValueFormat="{0:o}")]
public DateTime CreatedAt { get; set; }
The dump is:
MyClass (ObjectDumperSamples.MyClass, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
CreatedAt = 2013-08-25T21:25:54.7103441-04:00
GuidProperty = 04dee1cf-1027-444b-87c8-639504461abf
This property recognizes a special format string ToString()
, which inserts the result of the property's method ToString()
into the output stream.
These two properties allow you to plug in you own custom dumping for certain properties or fields of your object. They define where to find your method which will be invoked in order to produce the text representation of the property to which it is applied. The attribute's property DumpClass
is required if the method is implemented in a class different from the property's class. In this case the method must be public, static, must return string, and must take a single argument of the type of the property or base class of the property's type. The DumpMethod
property specifies the name of the method. If it is omitted the object dumper will assume default dump method name Dump
.
If the DumpClass
property is not specified but the DumpMethod
is, the object dumper will assume that the class containing the method is the class of the property and will try to find either an instance method with the specified name that returns string and takes no parameters or a static method which returns String and has a single parameter of the type of the property. In other words these two attributes are equivalent:
[Dump(DumpMethod="ToString")]
[Dump(ValueFormat="ToString()")]
This property controls whether to go into the complex property or just dump the type of it. When applied to a class the property affects dumping of all complex and sequence properties of the class. The type of this parameter is enum ShouldDump which has three values:
- Default - use the default dump behavior.
- Dump - indent and dump recursively the properties of the associated object.
- Skip - do not dump the associated object, just output its type or its "default property".
If the dump of a property of complex type is suppressed with RecurceDump=ShouldDump.Skip
besides of its type you can also dump one of its properties which you may consider characteristic or identifying for this type. For the purpose use the DefaultProperty
property with string value the name of the property, e.g.
[Dump(RecurceDump=ShouldDump.Skip, DefaultProperty="Key")]
public ComplexType Associate { get; }
11. If the chain of associated objects is too deep, you can cut it short with the property MaxDepth
on a class level:
[Dump(MaxDepth=3)]
class MyClassDescendant : MyClass
{
...
Here is the dump that I got for this scenario:
MyClassDescendant (ObjectDumperSamples.MyClassDescendant, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
IntProperty = 3
BoolProperty = True
CreatedAt = 2013-09-10T20:43:48.9853660-04:00
StringProperty = StringProperty
Description = This is one very very ver...
Associate = ComplexType (ObjectDumperSamples.ComplexType, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
Key = IL.TX.2013-09-10:20:23:34.85930
UniqueId = d481f7fe-2094-4e0c-b753-f99188db18eb
Other = ComplexType (ObjectDumperSamples.ComplexType, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
Key = IL.TX.2013-09-10:20:23:34.85931
UniqueId = c914a5a2-201c-4f48-8159-0e75a2809031
Other = ComplexType (ObjectDumperSamples.ComplexType, ObjectDumperSamples, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null):
Key = IL.TX.2013-09-10:20:23:34.85932
UniqueId = 9c4d4937-a1c0-4b2f-b719-daadd12c29c5
Other = ...object dump reached the maximum depth level. Use the DumpAttribute.MaxDepth to increase the depth level if needed.
GuidProperty = bdbda70b-95c8-4a62-a8df-dcdf4f8e2912
Generally the dump recurces only into arrays and sequences from the FCL. The reason is that recursing into custom sequences may have unintended side effects. However the author of a custom sequence object may override this by using the Enumerate=ShouldDump.Dump
property on a class level applied DumpAttribute:
[Dump(Enumerate=ShouldDump.Dump)]
class MyCollection : IEnumerable<Item>
{
. . .
}
vm.Aspects Copyright © vm 2013-2017