tsunami

log in
history

C# 3.0: Anonymous Cheating, optimized

Luke Breuer
2008-03-17 20:14 UTC

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace LinqCheating
{
    class Program
    {
        static T CastExact<T>(object obj, T template)
        {
            return (T)obj;
        }

        static Tuple<MethodInfo[], ConstructorInfo> ConverterPartial(Tuple<Type, Type> tuple)
        {
            var propPairs = tuple.Second
                .GetProperties()
                .GroupJoin(
                    tuple.First.GetProperties(),
                    o => new { o.Name, o.PropertyType },
                    i => new { i.Name, i.PropertyType },
                    (o, ii) => new Tuple<PropertyInfo, PropertyInfo>(ii.FirstOrDefault(), o));

            if (propPairs.Any(el => el.First == null))
                throw new InvalidCastException(string.Format(
                    "Attempted to cast {0} to {1}; the following properties were missing from {0}:\n" +
                    propPairs
                        .Where(el => el.First == null)
                        .Select(el => string.Format("  {0} {1}", el.Second.PropertyType.Name, el.Second.Name))
                        .Join("\n"),
                    tuple.First.Name, tuple.Second.Name));

            return new Tuple<MethodInfo[], ConstructorInfo>(
                propPairs.Select(t => t.First.GetGetMethod()).ToArray(), 
                tuple.Second.GetConstructors().Single());
        }

        static Func<Tuple<Type, Type>, Tuple<MethodInfo[], ConstructorInfo>> _converterPartial =
            Functional.Memoize<Tuple<Type, Type>, Tuple<MethodInfo[], ConstructorInfo>>(ConverterPartial);

        static T CastPartial<T>(object obj, T template)
        {
            var cached = _converterPartial(new Tuple<Type, Type>(obj.GetType(), typeof(T)));

            return (T)cached.Second.Invoke(
                cached.First.Select(el => el.Invoke(obj, null)).ToArray());
        }

        static IEnumerable<T> Pull<T>(object obj)
        {
            var props =
                from p in obj.GetType().GetProperties()
                where p.PropertyType == typeof(T)
                select p;

            return props.Select(p => (T)p.GetValue(obj, null));
        }

        static IEnumerable<T> Pull<T>(object obj, T template)
        {
            var props =
                from p in obj.GetType().GetProperties()
                where p.PropertyType == typeof(T)
                select p;

            return props.Select(p => (T)p.GetValue(obj, null));
        }


        static void CheatCastExact(object obj)
        {
            var cheated = CastExact(obj, new { A = default(string), B = default(string) });
            
            Console.WriteLine("  " + cheated.B);
        }

        static void CheatCastPartial(object obj)
        {
            var cheated = CastPartial(obj, new { 
                A = default(string), 
                Q = new { S = default(int), T = default(string) } 
            });

            Console.WriteLine("  " + cheated);
        }

        static void CheatPull(object obj)
        {
            var cheated = Pull<string>(obj);

            foreach(var s in cheated)
                Console.WriteLine("  " + s);

            var other = Pull(obj, new { S = default(int), T = default(string) });

            foreach (var v in other)
                Console.WriteLine("  " + v);
        }

        static void Time(string description, int iterations, Action a)
        {
            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < iterations; i++)
                a();

            sw.Stop();

            // so we can redirect stdout to null if we want
            Console.Error.WriteLine(
                "{0,7:0} ticks {1,7:0.000} ms ({2,6}) {3}", 
                sw.Elapsed.Ticks, sw.Elapsed.TotalMilliseconds, iterations, description);
        }

        static void Execute<T>(string description, Action<T> a, T arg)
        {
            Console.WriteLine(description);
            Console.WriteLine("input: " + arg);
            Console.WriteLine("output:");

            a(arg);

            Console.WriteLine();
        }

        static void Main(string[] args)
        {
            var w = new { A = "a", B = "b", N = 1, Q = new { S = 1, T = "a" } };

            Execute("CheatPull",        CheatPull,        w);
            Execute("CheatCastExact",   CheatCastExact,   new { A = "a", B = "b" });
            Execute("CheatCastPartial", CheatCastPartial, w);

            //Time("CheatCastPartial", 100000, () => CheatCastPartial(w));

            Console.SetOut(TextWriter.Null);

            Time("CheatCastPartial", 100000, () => CheatCastPartial(w));
        }
    }

    static class Functional
    {
        public static string Join(this IEnumerable<string> strings, string delimiter)
        {
            return strings.Any()
                ? string.Join(delimiter, strings as string[] ?? strings.ToArray())
                : "";
        }

        public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
        {
            var cache = new Dictionary<A, R>();

            return Memoize(f, cache);
        }

        public static Func<A, R> Memoize<A, R>(this Func<A, R> f, IDictionary<A, R> cache)
        {
            return delegate(A a)
            {
                R value;

                if (!cache.TryGetValue(a, out value))
                {
                    value = f(a);
                    cache.Add(a, value);
                }

                return value;
            };
        }
    }

    public class Tuple<T1, T2>
    {
        public T1 First { get; private set; }
        public T2 Second { get; private set; }

        public Tuple(T1 first, T2 second)
        {
            First = first;
            Second = second;
        }

        public override int GetHashCode()
        {
            return First.GetHashCode() ^ Second.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            var t = obj as Tuple<T1, T2>;

            return
                t != null &&
                t.First.Equals(this.First) &&
                t.Second.Equals(this.Second);
        }

        public override string ToString()
        {
            return string.Format("First: {0}  Second: {1}", First, Second);
        }
    }
}