Saturday 31 May 2014

Reflecting Types

Lately I've been posting about using IL generation to connect elements.

I know I was about to post series about Scene Graph, but since I like to do things in an non ordered fashion (and at the end of the day, it's my blog ;) , I'll go a bit deeper into objects.

In most graph oriented architecture, we have to create nodes, and connectors, so we have to build inputs/outputs...

We have several ways to do it:

  • Big factory: You have calls like CreateIntParameter, CreateValueInput, or generic version like CreateInput<T> ...
  • Injection : You use decorators in order to inject some implementation. (So doing ISpread<int> internally generates a IntInputPin or something like that).
Going deeper, why do we even need pins? Let's take the following example:

Code Snippet
  1. public class Prop
  2. {
  3.     public double Hello { get; set; }
  4.  
  5.     public int Integer { get; set; }
  6.  
  7.     public string ReadOnly { get { return "ReadOnly"; } }
  8. }
  9.  
  10. public class Prop2
  11. {
  12.     public double Double { get; set; }
  13.  
  14.     public string DoubleOutput { get; set; }
  15. }

This is pretty easy to reflect properties, since we know about get/set, we can create a "virtual pin" automatically, so why even wrap the property?

In most systems we would work it like that:

  • Wrap hello as input and Output Pin. 
  • Wrap ReadOnly as Output pin.
  • Create a link that wraps InputPin<double> and OutputPin<double>

So let's do this in a simpler way, let's say we want to connect Hello (output) to Prop2 "DoubleOutput"

First we need a Func to access Property:

Code Snippet
  1. public static Func<T> BuildGetter<T>(object instance, PropertyInfo property)
  2. {
  3.     return (Func<T>)Delegate.CreateDelegate(typeof(Func<T>), instance, property.GetGetMethod());
  4. }

And a way to set property:

Code Snippet
  1. static Action<T> BuildSetter<T>(object instance, PropertyInfo property)
  2. {
  3.     return (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), instance, property.GetSetMethod());
  4. }

Now we have to connect both, eg: read from source and write to destination:

Code Snippet
  1. static Action Connect<T>(object source, PropertyInfo getter, object dest, PropertyInfo setter)
  2. {
  3.     var getFunc = BuildGetter<T>(source, getter);
  4.     var setAction = BuildSetter<T>(dest, setter);
  5.     return () => setAction(getFunc());
  6. }

That was that easy, now we can simply have:

Code Snippet
  1. Prop p = new Prop()
  2. {
  3.     Hello = 20.0,
  4.     Integer = 20
  5. };
  6.  
  7. Prop2 p2 = new Prop2();
  8.  
  9. var connect = Connect<double>(p, typeof(Prop).GetProperty("Hello"), p2, typeof(Prop2).GetProperty("Double"));
  10. connect();

All done, out connector is ready ;)

If we need conversion, this is as easy, we can use a small interface:

Code Snippet
  1. public interface ITypeConverter<TSource,TDest>
  2. {
  3.     TDest Convert(TSource source);
  4. }
  5.  
  6. public class DoubleToStringConverter : ITypeConverter<double, string>
  7. {
  8.     public string Convert(double source)
  9.     {
  10.         return source.ToString() + " from converter";
  11.     }
  12. }

Then our connector can either use this small interface, or we can just set a lambda is case we can't be bother ed adding one file + namespaces + new class, get rid of object oriented verbosity basically ;)

Code Snippet
  1. static Action Connect<TSource, TDest>(object source, PropertyInfo getter, object dest, PropertyInfo setter, Func<TSource,TDest> converter)
  2. {
  3.     var getFunc = BuildGetter<TSource>(source, getter);
  4.     var setAction = BuildSetter<TDest>(dest, setter);
  5.     return () => setAction(converter(getFunc()));
  6. }
  7.  
  8. static Action Connect<TSource, TDest>(object source, PropertyInfo getter, object dest, PropertyInfo setter, ITypeConverter<TSource, TDest> converter)
  9. {
  10.     var getFunc = BuildGetter<TSource>(source, getter);
  11.     var setAction = BuildSetter<TDest>(dest, setter);
  12.     return () => setAction(converter.Convert(getFunc()));
  13. }

In action:

Code Snippet
  1. var connectConverter = Connect<double, string>(p, typeof(Prop).GetProperty("Hello"), p2, typeof(Prop2).GetProperty("DoubleOutput"), (d) => d.ToString() + " Hello");
  2.  
  3. connectConverter();
  4.  
  5. var connectInterface = Connect<double, string>(p, typeof(Prop).GetProperty("Hello"), p2, typeof(Prop2).GetProperty("DoubleOutput"), new DoubleToStringConverter());
  6.  
  7. connectInterface();

Now we can actually simply reflect type, create IO on rules, but operate directly on properties.
Direct access costs us a few virtual calls, but since we haven't got a layer on top, this is even more minimized.


Once we have this, let's looks at it further, let's show how to display our element, with custom formatted string per type, in this example I just log to console, bu this is easily extendable to user interface, writing, serialization...

Code Snippet
  1. public interface IPropertyDisplayFactory
  2. {
  3.     Type PropertyType { get; }
  4.     IPropertyDisplay DisplayObject(object instance, PropertyInfo property);
  5. }
  6.  
  7. public interface IPropertyDisplay
  8. {
  9.     void Display();
  10. }

First we do some simple defaults:

Code Snippet
  1. public class BasicDisplay : IPropertyDisplay
  2. {
  3.     private readonly Action display;
  4.  
  5.     public BasicDisplay(Action display)
  6.     {
  7.         this.display = display;
  8.     }
  9.  
  10.     public void Display()
  11.     {
  12.         display();
  13.     }
  14. }
  15.  
  16. public abstract class TypedDisplayFactory<T> : IPropertyDisplayFactory
  17. {
  18.     public Type PropertyType
  19.     {
  20.         get { return typeof(T); }
  21.     }
  22.  
  23.     public abstract IPropertyDisplay DisplayObject(object instance, PropertyInfo property);
  24. }

Then two stupid int and double implementations:

Code Snippet
  1. public class DoubleDisplayFactory : TypedDisplayFactory<double>
  2. {
  3.     public override IPropertyDisplay DisplayObject(object instance, PropertyInfo property)
  4.     {
  5.         Func<double> getter = Program.BuildGetter<double>(instance, property);
  6.  
  7.         Action action = () =>
  8.             {
  9.                 Console.WriteLine(property.Name + " Double: " + getter().ToString());
  10.             };
  11.  
  12.         return new BasicDisplay(action);
  13.     }
  14. }
  15.  
  16. public class IntDisplayFactory : TypedDisplayFactory<int>
  17. {
  18.     public override IPropertyDisplay DisplayObject(object instance, PropertyInfo property)
  19.     {
  20.         Func<int> getter = Program.BuildGetter<int>(instance, property);
  21.  
  22.         Action action = () =>
  23.         {
  24.             Console.WriteLine(property.Name + "Int: " + getter().ToString());
  25.         };
  26.  
  27.         return new BasicDisplay(action);
  28.     }
  29. }

Now we need to know if a property is capable of display of not:

Code Snippet
  1. public class DisplayPropertyRegistry
  2. {
  3.     private readonly IEnumerable<IPropertyDisplayFactory> factories;
  4.  
  5.     public DisplayPropertyRegistry(IEnumerable<IPropertyDisplayFactory> factories)
  6.     {
  7.         this.factories = factories;
  8.     }
  9.  
  10.     public DisplayPropertyRegistry(params IPropertyDisplayFactory[] factories)
  11.     {
  12.         this.factories = factories;
  13.     }
  14.  
  15.     private IEnumerable<IPropertyDisplayFactory> SearchCandidate(Type type)
  16.     {
  17.         return factories.Where(f => f.PropertyType == type);
  18.     }
  19.  
  20.     public bool CanDisplay(Type type)
  21.     {
  22.         return SearchCandidate(type).FirstOrDefault() != null;
  23.     }
  24.  
  25.     public IPropertyDisplayFactory GetFactory(Type type)
  26.     {
  27.         return SearchCandidate(type).First();
  28.     }
  29. }


Add filtering support:

Code Snippet
  1.   public class DisplayPropertyFilter
  2.   {
  3.       private readonly DisplayPropertyRegistry registry;
  4.  
  5.       public DisplayPropertyFilter(DisplayPropertyRegistry registry)
  6.       {
  7.           this.registry = registry;
  8.       }
  9.  
  10.       public IEnumerable<Tuple<PropertyInfo,IPropertyDisplayFactory>> ReflectType(Type type)
  11.       {
  12.           var props = type.GetProperties().Where(p => p.GetGetMethod() != null && registry.CanDisplay(p.PropertyType));
  13.  
  14.             return props.Select(p => newTuple<PropertyInfo,IPropertyDisplayFactory>(p, registry.GetFactory(p.PropertyType)));
  15.       }
  16.   }

From here we know from a given type, every property that we can display, and how to create them. We only used object type so far, so now we need to add instance in the mix:

Code Snippet
  1. public class DisplayObjectFactory
  2. {
  3.     private readonly DisplayPropertyFilter filter;
  4.  
  5.     public DisplayObjectFactory(DisplayPropertyFilter filter)
  6.     {
  7.         this.filter = filter;
  8.     }
  9.  
  10.     public ObjectDisplay GetDisplay(object instance)
  11.     {
  12.         var factories = filter.ReflectType(instance.GetType());
  13.  
  14.         var elements = factories.Select(f => f.Item2.DisplayObject(instance, f.Item1));
  15.  
  16.         return new ObjectDisplay(elements);
  17.     }
  18. }

And the final object that displays our information:

Code Snippet
  1. public class ObjectDisplay
  2. {
  3.     private readonly IEnumerable<IPropertyDisplay> displayElements;
  4.  
  5.     public ObjectDisplay(IEnumerable<IPropertyDisplay> displayElements)
  6.     {
  7.         this.displayElements = displayElements;
  8.     }
  9.  
  10.     public void Display()
  11.     {
  12.         foreach (IPropertyDisplay display in displayElements)
  13.         {
  14.             display.Display();
  15.         }
  16.     }
  17. }

Our final code, that gets an instance and displays any supported data:

Code Snippet
  1. DisplayPropertyRegistry registry = new DisplayPropertyRegistry(
  2.     new DoubleDisplayFactory(),
  3.     new IntDisplayFactory());
  4.  
  5. DisplayPropertyFilter filter = new DisplayPropertyFilter(registry);
  6.  
  7. DisplayObjectFactory builder = new DisplayObjectFactory(filter);
  8.  
  9. Prop p = new Prop()
  10. {
  11.     Hello = 20.0,
  12.     Integer = 20
  13. };
  14.  
  15. var display = builder.GetDisplay(p);
  16. display.Display();

You can easily think that's a lot of classes and a lot of code to just display properties of a class (we could just iterate through property list and do some "tostring"

But...

  • This is more extensible, and if you need to access your properties a lot, you get a pretty big gain over reflection.
  • You can register any type, replace as you wish, so you avoid the dreaded massive switch.
  • By having more classes, and less work per class, you have much more invariants, pretty much every class is ready to go once constructed, no temporal coupling (only edge case is CanDisplay/GetFactory which could create a related exception).
  • You are not limited to just a "tostring", create a controller, network serializer, add a proxy for automatic property change dispatch, possibilities are endless ;)
Here we are for now, promised I'll get back into Scene Graph next post (or maybe not ;)



No comments:

Post a Comment