3

this is my first question in stackoverflow and I am a beginner in using reflection.

I would like to dump all values of an object instance for reference (to keep track about used values on a test). I am using Compact Framework 3.5 not the full framework. Keep that in mind for your suggestions.

Imagine following classes:

public class Camera : IDisposable
{
    public Camera.FilenameProperties SnapshotFile;
    public double DigitalZoomFactor { get; set; }
    public bool DisplayHistogram { get; set; }
    public int ImageUpdateInterval { get; set; }
    public Camera.ImprintCaptionPosType ImprintCaptionPos { get; set; }
    public string ImprintCaptionString { get; set; }
}

where the 'special' types are:

    public class FilenameProperties
    {
        public string Directory { get; set; }
        public string Filename { get; set; }
        public Camera.FilenamePaddingType FilenamePadding { get; set; }
        public Camera.ImageType ImageFormatType { get; set; }
        public Camera.ImageResolutionType ImageResolution { get; set; }
        public int JPGQuality { get; set; }

        public void Restore();
        public void Save();

        public enum Fnametype
        {
            tSnapshot = 0,
            tCircularCapture = 1,
        }
    }
    public enum ImprintCaptionPosType
    {
        Disabled = 0,
        LowerRight = 1,
        LowerLeft = 2,
        LowerCenter = 3,
        UpperRight = 4,
        UpperLeft = 5,
        UpperCenter = 6,
        Center = 7,
    }

Now, I can get the 'base' names and properties and the field names of an instance of camera:

Camera cam = new Camera();
dumpProperties(cam);
...
    void dumpProperties(object oClass)
    {
        System.Diagnostics.Debug.WriteLine(oClass.ToString());

        FieldInfo[] _Info = oClass.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
        for(int i = 0; i<_Info.Length; i++)
        {
            System.Diagnostics.Debug.WriteLine(_Info[i].Name + ":'" + _Info[i].GetValue(oClass).ToString()+"'");
        }

        foreach (PropertyInfo pi in oClass.GetType().GetProperties()) 
        {
            System.Diagnostics.Debug.WriteLine(pi.Name + ":'" + pi.GetValue(oClass, null) 
                + "' Type=" + pi.PropertyType.ToString());
        }
    }

and then get soemthing like this:

Intermec.Multimedia.Camera
SnapshotFile:'Intermec.Multimedia.Camera+FilenameProperties'
DigitalZoomFactor:'1' Type=System.Double
DisplayHistogram:'False' Type=System.Boolean
ImageUpdateInterval:'1' Type=System.Int32
ImprintCaptionPos:'Disabled' Type=Intermec.Multimedia.Camera+ImprintCaptionPosType
ImprintCaptionString:'' Type=System.String

Now, for simple properties like DigitalZoomFactor and ImageUpdateInterval I get what I need, but for the nested class (correct wording?) I only get the type as for example with SnapshotFile. For the nested enum I get the value as with 'ImprintCaptionPos'.

How can I get the values of the nested values like FilenameProperties.Filename of the SnapshotFile field/property?

If I use dumpProperties(cam.SnapshotFile), I get the output I am looking for:

Intermec.Multimedia.Camera+FilenameProperties
Directory:'\Program Files\FrmCamera' Type=System.String
Filename:'myphoto' Type=System.String
ImageFormatType:'JPG' Type=Intermec.Multimedia.Camera+ImageType
FilenamePadding:'None' Type=Intermec.Multimedia.Camera+FilenamePaddingType
ImageResolution:'Medium' Type=Intermec.Multimedia.Camera+ImageResolutionType
JPGQuality:'100' Type=System.Int32

But how can I automate that?

I did a lot of search and test coding but was unable to find a solution. The problem seems to be getting the instance of the field to be able to iterate thru it.

I do not have the source code of the Camera class, so I cannot add or remove code in there.

Can anyone help?

I need to get something like the debugger shows: enter image description here

josef
  • 5,951
  • 1
  • 13
  • 24
  • Repeat the same function on properties themselves? In your foreach loop, repeat the same function on the value you get from pi.GetValue. – Toni Petrina Nov 14 '12 at 13:11
  • 1
    Basically, when you get the object back from `SnapshotFile` (via reflection), you just do the same thing again *on that object*... recursion, essentially. – Marc Gravell Nov 14 '12 at 13:16
  • Sorry, but I don know how to do that. pi.GetValue() does not return an object. How can I can get an object back from a PropertyInfo? Do you have a concrete working code? – josef Nov 14 '12 at 13:46
  • Unlike forum sites, we don't use "Thanks", or "Any help appreciated", or signatures on [so]. See "[Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be-removed-from-posts). – John Saunders Nov 14 '12 at 13:53

3 Answers3

1

You simply need to use recursion, and loop back into the method if your property is a class. Here's an example of an XML Serialization routine we use, that effectively walks the properties of a target using reflection, and generates an XElement from it. Your logic would be somewhat different as you're not going to build up XML, but the structure of what you're going to do will be pretty similar.

public XElement Serialize(object source, 
                          string objectName, 
                          bool includeNonPublicProperties)
{
    XElement element;
    var flags = BindingFlags.Instance | BindingFlags.Public;
    if(includeNonPublicProperties) 
    {
        flags |= BindingFlags.NonPublic;
    }

    var props = source.GetType().GetProperties(flags);

    var type = source.GetType();

    string nodeName;
    if(objectName == null)
    {
        if (type.IsGenericType)
        {
            nodeName = type.Name.CropAtLast('`');
        }
        else
        {
            nodeName = type.Name;
        }            
    }
    else
    {
        nodeName = objectName;
    }

    element = new XElement(nodeName);

    foreach (var prop in props)
    {
        string name = prop.Name;
        string value = null;
        bool valIsElement = false;

        if (!prop.CanRead) continue;

        if(prop.PropertyType.IsEnum)
        {
            value = prop.GetValue(source, null).ToString();
        }
        else 
        {
            string typeName;

            if (prop.PropertyType.IsNullable())
            {
                typeName = prop.PropertyType.GetGenericArguments()[0].Name;
            }
            else
            {
                typeName = prop.PropertyType.Name;
            }

            switch (typeName)
            {
                case "String":
                case "Boolean":
                case "Byte":
                case "TimeSpan":
                case "Single":
                case "Double":
                case "Int16":
                case "UInt16":
                case "Int32":
                case "UInt32":
                case "Int64":
                case "UInt64":
                    value = (prop.GetValue(source, null) ?? string.Empty).ToString();
                    break;
                case "DateTime":
                    try
                    {
                        var tempDT = Convert.ToDateTime(prop.GetValue(source, null));
                        if (tempDT == DateTime.MinValue) continue;
                        value = tempDT.ToString("MM/dd/yyyy HH:mm:ss.fffffff");
                    }
                    catch(Exception ex)
                    {
                        continue;
                    }
                    break;
                default:
                    var o = prop.GetValue(source, null);
                    XElement child;
                    if (o == null)
                    {
                        child = new XElement(prop.Name);
                    }
                    else
                    {
                        child = Serialize(o, prop.Name, includeNonPublicProperties);
                    }

                    element.Add(child);
                    valIsElement = true;
                    break;
            }
        }

        if (!valIsElement)
        {
            element.AddAttribute(name, value);
        }
    }

    return element;
}
ctacke
  • 66,480
  • 18
  • 94
  • 155
  • That is nice code but will not help with my problem. In your code you know the object type (the class) and you can cast but in my problem, I dont know which class to use. I dont know how to tell C# to cast the value back to its class. - Another example: if a set a break point in the foreach loop and ricght click pi (PropertyInfo), the debugger is able to unfold even nested classes and values. In my code it shows the nested class FilenameProperties and its values of SnapShotFile. Now, that is what I want to get to dump within the compact framework code. – josef Nov 14 '12 at 19:18
  • Not sure what you mean. This method takes in a "object", which can be anything - the second and third parameters simply are convenience items to allow you to rename the node and hide/show non-public items. The key is the "default" in the switch, which handles interned classes by recurvicely calling the Serialize method, which turns the class instance into it's own child XElement. So if you just pass in an instance of any arbitrary class, you'll get an XML version of the class, meaing that it dynamically inspected the entire object. – ctacke Nov 14 '12 at 20:02
  • You could also make this method static. It's part of a larger BasicXmlSerializer class in my code base, but you didn't need the deserialization bits, so I left them out. This is *not* meant to be inside the class you're trying to serialize. – ctacke Nov 14 '12 at 20:05
  • Hi Chris, I will check today and let you know. ~~Josef – josef Nov 15 '12 at 04:01
  • Hello Chris, I am sorry, but your code crashes on an enum within child = Serialize(o, prop.Name, includeNonPublicProperties);. I assume it will also crash on other nested complex types like the FileNameProperties. Despite from that, the code is NOT compact framework 3.5 compatible. I had to add a CropAtLast function, needed to remove prop.PropertyType.IsNullable() and change element.AddAttribute(name, value); to element.Add(new XAttribute(name, value));. That was not very beginner's friendly. OK, it does even not work for me. Any other toughts? ~josef – josef Nov 15 '12 at 10:54
  • Well, since I use it in CF production code, I might beg to differ on compatibility. Not sure on the FileInfo, I can't say I've tested it with struct serialization (it something I will test) but the *point* here is not the pure code, but the idea of what it's doing. It inspects via reflection, and when it detects a class/struct interned in the instance, it recurses and calls itself on that new member. That's what you have to do, and what marc Gravell was alluding to in the comment above. – ctacke Nov 15 '12 at 12:21
1

OK, I find a way (a workaround) to get all properties (in an XML but who cares) using the code from here:

The output is xml like but acceptable for me. Here an excerpt:

<xml version="1.0" encoding="utf-8">
<Camera xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
...
    <ImprintCaptionPos>Disabled</ImprintCaptionPos>
        <SnapshotFile>
        <Directory>\\Program Files\\FrmCamera</Directory>
        <Filename>myphoto</Filename>
        <ImageFormatType>JPG</ImageFormatType>
        <FilenamePadding>None</FilenamePadding>
        <ImageResolution>Medium</ImageResolution>
        <JPGQuality>100</JPGQuality>
    </SnapshotFile>
...

In my code I just have to call

        string s = serialization.mySerialize.SerializeObject<Intermec.Multimedia.Camera>(cam);

To get a 'dump' of all current properties of the instance.

Thanks to all for your help. Possibly I was misunderstood with my question and reflection is unable to give what I want.

Thanks

Josef

josef
  • 5,951
  • 1
  • 13
  • 24
0

You have to do the iteration to all inner object members like you did for outer class. Will be an exercise for you to implement it for complete set of .NET types. Below is the pseudo code of simple implementation

void dumpProperties(object target)
{
    if (target.GetType().IsSimple() || target.GetType().IsMethodImplemented("ToString"))
        System.Diagnostics.Debug.WriteLine(target.ToString());
    else if (target is IEnumerable)
    {
        foreach (object item in (IEnumerable)target)
        {
            System.Diagnostics.Debug.WriteLine(dumpProperties(target));
        }
    }
    else
    {
        foreach (FieldInfo fieldInfo in target.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance))
        {
             System.Diagnostics.Debug.WriteLine(dumpProperties(fieldInfo.FieldHandle.Value));
        }
        foreach (PropertyInfo propertyInfo in oClass.GetType().GetProperties())
        {
                  System.Diagnostics.Debug.WriteLine(dumpProperties(propertyInfo.GetGetMethod().MethodHandle.Value));
        }
    }
}

Or you can use Cinchoo framework, which has built in function to dump any object.

Console.WriteLine(ChoObject.ToString(camera));
Patrick D'Souza
  • 3,491
  • 2
  • 22
  • 39
Cinchoo
  • 6,088
  • 2
  • 19
  • 34