6

Let's say, I have a Print method like this:

private static void Print(IEnumerable items)
{
    // Print logic here
}

I want to pass a collection class to this Print method, which should print all the fields like a table. For example, my input collection can be "Persons" or "Orders" or "Cars" etc.

If I pass the "Cars" collection to the Print method, it should print the list of "Car" details such as: Make, Color, Price, Class etc.

I won't know the type of the collection until run-time. I tried and achieved a solution using TypeDescriptors and PropertyDescriptorCollection. But, I don't feel that is a good solution. Is there any other way to achieve this using expressions or generics?

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Prince Ashitaka
  • 8,623
  • 12
  • 48
  • 71
  • 1
    About the best you can do with generics is have everything implement some `IMyToString` interface (assuming ToString isn't overridden directly) and then take in `IEnumerable`. There are other methods, sure, but generally rely on a mapping (TypeConverters are a form of mapping) or utilizing the RTTI in other ways. –  Feb 25 '11 at 05:46

4 Answers4

7

Override the object's ToString() method to include all the information you would like to display, and then simply call it whenever you like. No need for expressions or generics.

BinaryTox1n
  • 3,486
  • 1
  • 36
  • 44
  • 1
    +1 for suggesting the standard polymorphism pattern. If the classes are sealed, then the "Visitor Pattern" (verbose inverted pattern match ;-) can be used. –  Feb 25 '11 at 05:44
  • And in .net visitor pattern is nothing but extension methods. – Pradeep Feb 25 '11 at 05:46
  • The above solution is really good. But, I have another issue. Say, for example, what if I pass a unknow collection like `var result = select p from Persons join o in Orders on p.ID = o.PersonID select new { p.Name, o.Name };` `Print(result);` In this case, how can I achieve this? – Prince Ashitaka Feb 25 '11 at 06:09
  • 1
    @Prince, In that case you can create a struct to house that datatype instead of using anonymous type, and again override the struct's `ToString()`. You do not need to use reflection just for this. Also, anonymous types are automatically given a `ToString()` method which enumerates all of the properties and corresponding values of the type. Since both your reflection option and this are equally inflexible, there is **still** no reason to use reflection – BinaryTox1n Feb 25 '11 at 06:22
7

You could implement Print like this:

static void Print<T>(IEnumerable<T> items)
{
    var props = typeof(T).GetProperties();

    foreach (var prop in props)
    {
        Console.Write("{0}\t", prop.Name);
    }
    Console.WriteLine();

    foreach (var item in items)
    { 
        foreach (var prop in props)
        {
            Console.Write("{0}\t", prop.GetValue(item, null));
        }
        Console.WriteLine();
    }
}

It simply loops over each property of the class to print the name of the property, then prints over each item and for each item it prints the values of the properties.

I would argue that you should use generics here (as opposed to suggestions in other answers); you want the items in the collection to be of a single type so that you can print table headers.

For table formatting you can check the answers to this question.

Community
  • 1
  • 1
Markus Johnsson
  • 3,949
  • 23
  • 30
  • This works for what I am looking for, which is to print `Object`'s Property Name(s) once, and then print values for each item in the List. Thanks! – Shiva Jun 10 '14 at 21:28
3

If you don't know what type - i.e. it could be any type - then the best option is to use reflection. That's a good solution. Why do you feel it's not a good solution?

Kirk Broadhurst
  • 27,836
  • 16
  • 104
  • 169
1

This example caches a Print method for you for performance using Expressions:

    class MyObject
    {
        public int Property1 { get; set;}
        public string Property2 { get; set; }
    }
    class MyOtherObject
    {
        public int Property3 { get; set; }
        public string Property4 { get; set; }
    }
    static void Main(string[] args)
    {
        Array.ForEach(new[] 
        { 
            new MyObject { Property1 = 1, Property2 = "P" },
            new MyObject { Property1 = 2, Property2 = "Q" } 
        }, Print);
        Array.ForEach(new[]
        {
            new MyOtherObject { Property3 = 3, Property4 = "R" },
            new MyOtherObject { Property3 = 4, Property4 = "S" } 
        }, Print);
        Console.ReadKey();
    }
    static void Print<T>(T item)
    {
        ObjectPrinter<T>.PrintAction(item, Console.Out);
    }
    static class ObjectPrinter<T>
    {
        public static readonly Action<T, TextWriter> PrintAction = CreatePrintAction();
        private static Action<T, TextWriter> CreatePrintAction()
        {
            ParameterExpression item = Expression.Parameter(typeof(T), "item");
            ParameterExpression writer = Expression.Parameter(typeof(TextWriter), "writer");
            var writeLineMethod = typeof(TextWriter).GetMethod("WriteLine", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null);
            var concatMethod = typeof(string).GetMethod("Concat", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(object), typeof(object) }, null);
            var writeDashedLine = Expression.Call(
                writer,
                writeLineMethod,
                Expression.Constant(
                    new String('-', 50)
                )
            );
            var lambda = Expression.Lambda<Action<T, TextWriter>>(
                Expression.Block(
                    writeDashedLine,
                    Expression.Block(
                        from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
                        where property.GetGetMethod().GetParameters().Length == 0
                        select Expression.Call(
                            writer,
                            writeLineMethod,
                            Expression.Call(
                                null,
                                concatMethod,
                                Expression.Constant(
                                    property.Name + ":"
                                ),
                                Expression.Convert(
                                    Expression.Property(
                                        item,
                                        property
                                    ),
                                    typeof(object)
                                )
                            )
                        )
                    ),
                    writeDashedLine
                ),
                item,
                writer
            );
            return lambda.Compile();
        }
    }
Double Down
  • 898
  • 6
  • 13