tdwright / contabs

Simple yet flexible tables for console apps.
MIT License
54 stars 20 forks source link

How can we make ConTabs work with anonymous types? #35

Closed tdwright closed 6 years ago

Ravikc commented 6 years ago

I'd like to take this forward. So suppose we declare our anonymous list something like this:

var list = new ArrayList()
   {
        new { Prop1 = "valueX" },
        new { Prop1 = "valueY", Prop2 = 3.0 },
        new { prop3 = "valueZ" }
    };

or this:

var list = new List<object>()
            {
                new { Prop1 = "valueX" },
                new { Prop1 = "valueY", Prop2 = 3.0 },
                new { prop3 = "valueZ" }
            };

What kind to table should it produce? I was thinking something like this:

Prop1 Prop2 Prop3
valueX
valueY 3.0
valueZ

Let's decide the expected output first and then we can discuss the implementation. What do you think?

tdwright commented 6 years ago

The problem is that ConTabs determines the columns via reflection.

If I do:

var Data = new List<object>()
{
    new { Prop1 = "valueX" },
    new { Prop1 = "valueY", Prop2 = 3.0 },
    new { prop3 = "valueZ" }
};

var table = Table<Object>.Create(Data);
Console.WriteLine(table.ToString());

I get an exception:

ConTabs.Exceptions.PublicPropertiesNotFoundException On Table creation, no valid properties were identified. Check access modifiers.

So I suppose we may need to think about a new construction. I guess if we took an ArrayList (or similar) and derived the columns by reading the property names, then doing a Unique(). We can assume in this case that all properties should be included, as they otherwise wouldn't have been included.

The other problem this may expose is what we do with nulls. The example you've used would currently throw a NullReferenceException for the missing values. This may not be the desired behaviour even with non-anonymous objects.

Ravikc commented 6 years ago

I wanted to ask if that's how the table should look (assuming that the program works correctly). And in order to handle anonymous types we could have a wrapper/Factory that spits out all the columns.

I am not suggesting to feed anonymous types to the current implementation.

tdwright commented 6 years ago

Ah, I see. Well that table looks pretty much as I'd expect, yes.

tdwright commented 6 years ago

Hi @Ravikc - have you had any further thoughts on this? It felt like you were heading in a pretty interesting direction.

Ravikc commented 6 years ago

I got a little busy at work. But basically the path I had chosen required some code duplication. So i am trying to find a more elegant way to achieve the same.

tdwright commented 6 years ago

Ah, cool. There's absolutely no rush with this - we're all doing this in our spare time after all!

sixlettervariables commented 6 years ago

I was able to get this to work with a helper class and method:

    public static class Table
    {
        public static Table<TResult> Create<TResult>(IEnumerable<TResult> source) where TResult : class
        {
            return Table<TResult>.Create(source);
        }
    }

I found this works fine and the demo works with with only a small change (note the L modifier to the integer literals in DistanceFromSun):

            // Get some data (can be an IEnumerable of anything)
            var Data = new[] {
                new { Name = "Mercury", DistanceFromSun = 57909227L, OrbitalPeriod = 88F, Diameter = 4879, Fact = "Mercury is the smallest planet." },
                new { Name = "Venus", DistanceFromSun = 108209475L, OrbitalPeriod = 225F, Diameter = 12104, Fact = "Venus is the hottest planet." },
                new { Name = "Earth", DistanceFromSun = 149598262L, OrbitalPeriod = 365.24F, Diameter = 12742, Fact = "Earth is where we live" },
                new { Name = "Mars", DistanceFromSun = 227943824L, OrbitalPeriod = 693.5F, Diameter = 6779, Fact = "Mars is also often described as the “Red Planet” due to its reddish appearance." },
                new { Name = "Jupiter", DistanceFromSun = 778340821L, OrbitalPeriod = 4343.5F, Diameter = 139822, Fact = "Jupiter is the largest planet." },
                new { Name = "Saturn", DistanceFromSun = 1426666422L, OrbitalPeriod = 10767.5F, Diameter = 116464, Fact = "Saturn is best known for its fabulous ring system that was first observed in 1610 by the astronomer Galileo Galilei." },
                new { Name = "Uranus", DistanceFromSun = 2870658186L, OrbitalPeriod = 30660F, Diameter = 50724, Fact = "Uranus became the first planet discovered with the use of a telescope." },
                new { Name = "Neptune", DistanceFromSun = 4498396441L, OrbitalPeriod = 60152F, Diameter = 49244, Fact = "On average Neptune is the coldest planet" },
            };

            // Create a table object
            var table = Table.Create(Data);

Optionally, you could add an extension method:

        public static Table<TResult> AsTable<TResult>(this IEnumerable<TResult> source) where TResult : class
        {
            return Table<TResult>.Create(source);
        }

Which would allow the demo to change to:

            // Create a table object
            var table = Data.AsTable();
tdwright commented 6 years ago

Hi @sixlettervariables. Thanks for picking this up.

Very neat application of generics. It hadn't occurred to me that it could be this easy!

I also like the flexibility that this dual approach (new ctor and ext method) allows.

Would love to see a pull request for this!

Tom

sixlettervariables commented 6 years ago

Please see #47. We would likely use the AsTable extension.

tdwright commented 6 years ago

Fixed by #47