Asp.Net MVC programming advice. Toodles, Evan Nagle.
Jul
26

If you don’t know much about prototypal inheritence, no worries. For starters, check out Douglas Crockford’s super-short germinal article on the subject. Then, watch Crockford wax prototypical in this excellent (albeit somewhat boring) Yahoo presentation. Then, if you’re still hungry for more, read the Wikipedia article about Prototype-based programming here. And, finally, if you’re a fan of example-based learning, go ahead and watch this excellent Google video on Advanced JavaScript techniques.

Wrapped your head around the concept yet? Good. Let’s begin by taking a quick look at a simple example of prototypal inheritance in JavaScript. This example comes from Tim Caswell’s aptly named post, Prototypal Inheritance. If you’re familiar with JavaScript, this snippet of code should seem pretty trivial.


var Animal = {
  eyes: 2,
  legs: 4,
  name: "Animal",
  toString: function () {
    return this.name + " with " + this.eyes + " eyes and " + this.legs + " legs."
  }
}

var Dog = Animal.spawn({
  name: "Dog"
});

var Insect = Animal.spawn({
  name: "Insect",
  legs: 6
});

var fred = Dog.spawn({});
var pete = Insect.spawn({});

sys.puts(fred);
sys.puts(pete);

Using anonymous types in C# 3.0, we could almost recreate an Animal object in the exact same manner that we created the JavaScript Animal above–that is, with one glaring and obvious exception: the this in the toString function. C# 3.0 will have none of it:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Create_JavaScript_Ish_Object()
        {
            var Animal = new
            {
                eyes = 2,
                legs = 4,
                name = "Animal",
                toString = (Func<string>)(() =>
                    this.Name + " with " + this.Eyes + " eyes and " + this.Legs + " legs.")
            };
        }
    }
}

The compiler resolves the “this” to Protopal.Tests.Core.ProtoTest (as it should). And, since the ProtoTest class contains no definition for name, eyes, or legs, the compiler lets us feel its wrath:

compiler wrath e1280096576438 Prototypal C#: Making C# Look and Work Like JavaScript

In C# 4.0, the dynamic type offers us an interesting workaround. For starters, we can create the following delegates–each of which contains a dynamically typed first parameter. More on that in a second:


namespace Protopal.Core
{
    public delegate TR PFunc<TR>(dynamic me);
    public delegate TR PFunc<T1, TR>(dynamic me, T1 arg1);
    public delegate TR PFunc<T1, T2, TR>(dynamic me, T1 arg1, T2 arg2);
    public delegate TR PFunc<T1, T2, T3, TR>(dynamic me, T1 arg1, T2 arg2, T3 arg3);

    //and the allstars...
}

Okay. We’re getting closer. Let’s go ahead and rework our Animal object so it compiles. Notice that we’re now setting the toString property to a PFunc instead of a Func, and in our PFunc, by convention, we’re assuming that the first passed parameter will be resolved to our not-yet-defined anonymous type (i.e. the “this” we wanted all along):


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Create_JavaScript_Ish_Object()
        {
            var Animal = new
            {
                eyes = 2,
                legs = 4,
                name = "Animal",
                toString = (PFunc<string>)
                    (me => me.name + " with " + me.eyes + " eyes and " + me.legs + " legs.")
            };
        }
    }
}

Now, we can feed our Animal object back into the toString() function and retrieve the string that we’d expect. Granted, this initial solution is uglier than any woman that I’ve ever dated (only by a little), but at least it works. Don’t cringe yet, friend. We’ll make it much, much prettier in just a moment:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Create_JavaScript_Ish_Object()
        {
            var Animal = new
            {
                eyes = 2,
                legs = 4,
                name = "Animal",
                toString = (PFunc<string>)
                    (me => me.name + " with " + me.eyes + " eyes and " + me.legs + " legs.")
            };

            Assert.AreEqual("Animal with 2 eyes and 4 legs.", Animal.toString(Animal));
        }
    }
}

Yes, it works, but that kind of ugliness won’t do. We can definitely do better.

Let’s start by creating our own DynamicObject class. We’ll call our class Proto. Proto will be very similar to ExpandoObject (which also derives from the DynamicObject class). The main difference: when a user invokes a PFunc delegate on a Proto class, we’ll intercept the call and insert the Proto class as the first argument.

Before I reveal the Proto class, let me preface the following code with two important sentiments. First, I’m just screwing around here. I think that a Proto-ish class could really be useful in certain situations, but I don’t share Rob Conery’s inner desire to DuckDiaper and Rubify everything. Second, as you’ll hopefully notice, the DynamicObject class is amazingly and dangerously flexible–so flexible, in fact, that you can really force the dynamic type to do whatever the heck you want, even if the thing you want to do is really, really stupid. So, beware. And don’t blame me if your boss fires you.


using System;
using System.Linq;
using System.Collections.Generic;
using System.Dynamic;

namespace Protopal.Core
{
    public sealed class Proto : DynamicObject
    {

        Dictionary<string, object> members;

        object obj;

        public static dynamic Pal(object members)
        {
            return new Proto(members);
        }

        Proto(object members)
        {
            if (members == null)
            {
                this.members = new Dictionary<string, object>();
                this.obj = new object { };
            }
            else
            {
                this.obj = members;
                Type objType = this.obj.GetType();

                if (objType == typeof(Proto))
                {
                    Proto parent = (Proto)this.obj;
                    this.members = new Dictionary<string, object>(parent.members);
                }
                else
                {
                    this.members = objType
                        .GetProperties()
                        .ToDictionary(k => k.Name, v => v.GetValue(members, null));
                }
            }
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            members[binder.Name] = value;
            return true;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (members.TryGetValue(binder.Name, out result))
            {
                return true;
            }
            else
            {
                result = new Proto(new object { });
                members[binder.Name] = result;
                return true;
            }
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder,
            object[] args, out object result)
        {
            object member;

            if (members.TryGetValue(binder.Name, out member))
            {
                Type memberType = member.GetType();

                if (memberType.IsGenericType)
                {
                    var genericType = memberType.GetGenericTypeDefinition();

                    if (genericType == typeof(PFunc<>) ||
                        genericType == typeof(PFunc<,>) ||
                        genericType == typeof(PFunc<,,>) ||
                        genericType == typeof(PFunc<,,,>))
                    {
                        var del = member as Delegate;

                        var delArgs = new List<object>();
                        delArgs.Add(this);
                        delArgs.AddRange(args);

                        result = del.DynamicInvoke(delArgs.ToArray());
                        return true;
                    }
                }
            }

            return base.TryInvokeMember(binder, args, out result);
        }
    }
}

Now, let’s reconfigure our test:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Create_JavaScript_Ish_Object()
        {
            var Animal = Proto.Pal(new
            {
                eyes = 2,
                legs = 4,
                name = "Animal",
                toString = (PFunc<string>)
                    (me => me.name + " with " + me.eyes + " eyes and " + me.legs + " legs.")
            });

            Assert.AreEqual("Animal with 2 eyes and 4 legs.", Animal.toString());
        }
    }
}

That works. Coolness. Cookies for all!

Okay, okay. Let’s not congratulate ourselves just quite yet. What about “spawning” all kinds of child classes from our Animal object–you know, like in good ol’ JavaScript? Hmm. Good question.

For starters, if we really want to accomplish this feat, we have to create an extension method for merging together dictionaries. I swiped this bit of code form StackOverflow:


using System.Collections.Generic;
using System.Linq;

namespace Protopal.Extensions
{
    public static class DictionaryExtensions
    {
        public static T MergeLeft<T, K, V>(this T me, params IDictionary<K, V>[] others)
            where T : IDictionary<K, V>, new()
        {
            T newMap = new T();
            foreach (IDictionary<K, V> src in
                (new List> { me }).Concat(others))
            {
                foreach (KeyValuePair<K, V> p in src)
                {
                    newMap[p.Key] = p.Value;
                }
            }
            return newMap;
        }
    }
}

Now, we can add a public Spawn method to our Proto class:


using System;
using System.Linq;
using System.Collections.Generic;
using System.Dynamic;
using Protopal.Extensions;

namespace Protopal.Core
{
    public sealed class Proto : DynamicObject
    {

        Dictionary<string, object> members;

        object obj;

        public static dynamic Pal(object members)
        {
            return new Proto(members);
        }

        Proto(object members)
        {
            if (members == null)
            {
                this.members = new Dictionary<string, object>();
                this.obj = new object { };
            }
            else
            {
                this.obj = members;
                Type objType = this.obj.GetType();

                if (objType == typeof(Proto))
                {
                    Proto parent = (Proto)this.obj;
                    this.members = new Dictionary<string, object>(parent.members);
                }
                else
                {
                    this.members = objType
                        .GetProperties()
                        .ToDictionary(k => k.Name, v => v.GetValue(members, null));
                }
            }
        }

        public dynamic Spawn(object members)
        {
            var pal = new Proto(members);
            pal.members = this.members.MergeLeft(pal.members);

            return pal;
        } 

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            members[binder.Name] = value;
            return true;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (members.TryGetValue(binder.Name, out result))
            {
                return true;
            }
            else
            {
                result = new Proto(new object { });
                members[binder.Name] = result;
                return true;
            }
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder,
            object[] args, out object result)
        {
            object member;

            if (members.TryGetValue(binder.Name, out member))
            {
                Type memberType = member.GetType();

                if (memberType.IsGenericType)
                {
                    var genericType = memberType.GetGenericTypeDefinition();

                    if (genericType == typeof(PFunc<>) ||
                        genericType == typeof(PFunc<,>) ||
                        genericType == typeof(PFunc<,,>) ||
                        genericType == typeof(PFunc<,,,>))
                    {
                        var del = member as Delegate;

                        var delArgs = new List<object>();
                        delArgs.Add(this);
                        delArgs.AddRange(args);

                        result = del.DynamicInvoke(delArgs.ToArray());
                        return true;
                    }
                }
            }

            return base.TryInvokeMember(binder, args, out result);
        }
    }
}

And, for the long-awaited finale, here’s some quasi-prototypal C#, which functions just as well (sans any possible issues of so-called “performance”) as Tim Caswell’s prototypal JavaScript code:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Create_JavaScript_Ish_Object()
        {
            var Animal = Proto.Pal(new
            {
                eyes = 2,
                legs = 4,
                name = "Animal",
                toString = (PFunc<string>)
                    (me => me.name + " with " + me.eyes + " eyes and " + me.legs + " legs.")
            });

            var Dog = Animal.Spawn(new
            {
                name = "Dog"
            });

            var Insect = Animal.Spawn(new
            {
                name = "Insect",
                legs = 6
            });

            var fred = Dog.Spawn(new {});
            var pete = Insect.Spawn(new {});

            Console.WriteLine(Animal.toString());
            Console.WriteLine(Dog.toString());
            Console.WriteLine(Insect.toString());
            Console.WriteLine(fred.toString());
            Console.WriteLine(pete.toString());
        }
    }
}

Looks like all of our animals are happy in their pens, which is nice:

animal dog insect e1280100311691 Prototypal C#: Making C# Look and Work Like JavaScript

Extra Credit

Interestingly enough, the Protopal class yields a couple of cool (and quasi-unintended) side benefits. For starters, we can “change” the values of properties in anonymous types. For example:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Try_To_Set_Anon_Type_Property()
        {
            var Animal = new
            {
                Name = "Animal"
            };

            //This is an error:
            //Animal.Name = "Manimal";

            var animalHijacked = Proto.Pal(Animal);
            animalHijacked.Name = "Manimal";

            Assert.AreEqual("Manimal", animalHijacked.Name);
        }
    }
}

Also, you can implement interesting IOC patterns, like this whopper:


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Protopal.Core;

namespace Protopal.Tests.Core
{
    [TestClass]
    public class ProtoTest
    {
        [TestMethod]
        public void Proto_Sandbox_Reference_Property_That_Doesnt_Exist()
        {
            var evan = new
            {
                Name = "Evan"
            };

            var jaimie = new
            {
                Name = "Jaimie"
            };

            var sayer = Proto.Pal(new
            {
                Say = (PFunc<string, string>)
                    ((me, x) => me.Name + " says, '" + x + "'")
            });

            string jaimieSays = sayer.Spawn(jaimie).Say("wudup");
            string evanSays = sayer.Spawn(evan).Say("yo yo");

            Assert.AreEqual("Jaimie says, 'wudup'", jaimieSays);
            Assert.AreEqual("Evan says, 'yo yo'", evanSays);
        }
    }
}

And that’s really just the tip of an extremely precarious iceberg…

What Now?

Sleep. Then, breakfast.

I think that this is an interesting, if not a super-stupid, POC. Surely, with a little bit of elbow grease and a hefty dose of refinement, a class like Protopal could really serve some kind of useful purpose. Maybe? Eh, I don’t know. Maybe you have a better idea of what that “purpose” might be (if anything). For now, I’ll leave the iceberg as it is, and let you chime in.

Read More

You can leave a response, or trackback from your own site.

6 Responses to “Prototypal C#: Making C# Look and Work Like JavaScript”

 
  1. This is pretty creative, and I dig it.

    Hopefully my boss doesn’t fire me when I started using it… I’ll be sure to litter this URL all over the code comments so future developers understand it.

  2. Evan says:

    No, Matt, No! :)

    If anything, you can help me refine it, and then we can plop a non-embarrassing version up on Codeplex.

    Cheers,
    Evan

  3. Scott Allen says:

    Nice work! I believe composition is a powerful force in software. I wish it was a first class citizen in C#.

  4. Loofy says:

    var Animal = {
    eyes: 2,
    legs: 4,
    name: “Animal”,
    toString: function () {
    return this.name + ” with ” + this.eyes + ” eyes and ” + this.legs + ” legs.”
    }
    }

    in your example the keyword this will not refer to the Animal. You have to say Animal.eyes and Animal.legs for this to work. the keyword this is kind of out of place in JavaScript since it’s a functional language, not object oriented.

  5. Loofy says:

    … I forgot to mention, thanks for the tips on making C# more JavaScripty. I’m still being forced to use it for work, and I find myself using var, wanting to pass callbacks, and make anonymous types.

  6. [...] in light of the vendor whose representative is making them. I note that C# can now be written to look like JS. Why shouldn’t any particular extension to C# be at least considered (not rubber-stamped of [...]

 

Leave a Reply