tags:

views:

131

answers:

4

I want to write the property names and matching data to a delimited file, I've copied some code from the c# objectdumper help file and it all seems to work OK but I dont understand reflection enough to be confident to use it. What I'm worried about is an incorrect value being placed in the incorrect column, is it possible for this to happen e.g.

Field1,Field2
Val1,Val2
Val1,Val2
Val2,Val1  << Could this ever happen ?

Also what does this piece of code mean?

f != null ? f.GetValue(this) : p.GetValue(this, null)

Code below:

public string returnRec(bool header, string delim)
{
    string returnString = "";
    bool propWritten = false;
    MemberInfo[] members = this.GetType().GetMembers(BindingFlags.Public | BindingFlags.Instance);
    foreach (MemberInfo m in members)
    {
        FieldInfo f = m as FieldInfo;  
        PropertyInfo p = m as PropertyInfo;
        if (f != null || p != null)
        {
            if (propWritten)
            {
                returnString += delim;
            }
            else
            {
                propWritten = true;
            }
            if (header)
                returnString += m.Name;
            else
            {
                Type t = f != null ? f.FieldType : p.PropertyType;
                if (t.IsValueType || t == typeof(string))
                {
                    returnString += f != null ? f.GetValue(this) : p.GetValue(this, null);
                }
            }
        }
    }
    return returnString;
}
+1  A: 

Hi

Type t = f != null ? f.FieldType : p.PropertyType;

this is an inline if, asking is f != null then f.FieldType else p.PropertyType

can be written as

Type t;
if (f != null)
    t = f.FieldType;
else 
    t = p.PropertyType;
astander
+1: good answer, so I left it out of mine.
Fredrik Mörk
Thanks, couldnt remember what is was called to even look it up.
gary proudfoot
A: 

@astander has already given you an answer on the Type t = f != null ? f.FieldType : p.PropertyType; question, so I will leave that one out. Regarding getting the values into the correct columns, I don't know wheter reflection guarantees to list the members of a type in a specific order, but you can guarantee it by sorting the list before using it (using Linq):

MemberInfo[] members = typeof(Item).GetMembers(BindingFlags.Public | BindingFlags.Instance);
IEnumerable<MemberInfo> sortedMembers = members.OrderBy(m => m.Name);
foreach (MemberInfo member in sortedMembers)
{
    // write info to file
}

Or if you prefer a non-Linq approach (works with .NET Framework 2.0):

MemberInfo[] members = typeof(Item).GetMembers(BindingFlags.Public | BindingFlags.Instance);
Array.Sort(members, delegate(MemberInfo x, MemberInfo y){
    return x.Name.CompareTo(y.Name);
});

foreach (MemberInfo member in members)
{
    // write info to file
}
Fredrik Mörk
Was looking for "proof" of the order in which the GetMembers return, but did not find anything telling. So i agree to the approach mentioned above
astander
Thanks for that, puts my mind at rest.
gary proudfoot
A: 

@astander and @Frederik have essentially answered the questions and concerns that you specifically voiced, but I'd like to suggest doing things in a slightly more efficient manner. Depending on the number of object instances that you wish to write to your file, the method that you've presented may end up being quite inefficient. That's because you're gleaning type and value information via reflection on every iteration, which is unnecessary.

What you're looking for is something that looks up type information once, and then only uses reflection to get the value of properties and fields, e.g. (.NET 3.5),

public static IEnumerable<string> ReturnRecs(IEnumerable items, bool returnHeader, string delimiter)
{
    bool haveFoundMembers = false;
    bool haveOutputHeader = false;
    PropertyInfo[] properties = null;
    FieldInfo[] fields = null;
    foreach (var item in items)
    {
     if (!haveFoundMembers)
     {
      Type type = item.GetType();
      properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
       .Where(pi => pi.PropertyType.IsValueType || pi.PropertyType == typeof (string)).ToArray();
      fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance)
       .Where(fi => fi.FieldType.IsValueType || fi.FieldType == typeof(string)).ToArray();
      haveFoundMembers = true;
     }
     if (!haveOutputHeader)
     {
      yield return String.Join(delimiter, properties.Select(pi => pi.Name)
            .Concat(fields.Select(pi => pi.Name)).ToArray());
      haveOutputHeader = true;
     }
     yield return String.Join(delimiter,
                              properties.Select(pi => pi.GetValue(item, null).ToString())
                               .Concat(fields.Select(fi => fi.GetValue(item).ToString())).ToArray());
    }

The above code only ever performs a GetProperties and GetFields once per group of records--also, because of this, there's no need to explicitly sort the properties and fields as was @Frederik's suggestion.

Eric Smith
Thanks, although it will probably only save seconds I'll use this.
gary proudfoot
A: 

Just to add some thoughts re the accepted answer, in particular for large data volumes:

  • PropertyInfo etc can be unnecessarily slow; there are ways to avoid this, for example using HyperDescriptor or other dynamic code
  • rather than building lots of intermediate strings, it may be more efficient to write output directly to a TextWriter

As a tweaked version that adopts these approaches, see below; note that I haven't enabled HyperDescriptor in this example, but that is just:

HyperTypeDescriptionProvider.Add(typeof(YourType));

Anyway...

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
static class Program {
    static void Main() { // just some test data...
        var data = new[] { new { Foo = "abc", Bar = 123 }, new { Foo = "def", Bar = 456 } };
        Write(data, Console.Out, true, "|");
    }
    public static void Write<T>(IEnumerable<T> items, TextWriter output, bool writeHeaders, string delimiter) {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
        foreach (T item in items) {
            bool firstCol = true;
            if (writeHeaders) {                
                foreach (PropertyDescriptor prop in properties) {
                    if (firstCol) {
                        firstCol = false;
                    } else {
                        output.Write(delimiter);
                    }
                    output.Write(prop.Name);                    
                }
                output.WriteLine();
                writeHeaders = false;
                firstCol = true;
            }
            foreach (PropertyDescriptor prop in properties) {
                if (firstCol) {
                    firstCol = false;
                } else {
                    output.Write(delimiter);
                }
                output.Write(prop.Converter.ConvertToString(prop.GetValue(item)));
            }
            output.WriteLine();
        }
    }
}
Marc Gravell