JavaScript Style Objects in C#
Posts in the HyperJS Series- HyperJS Episode 1 - The Phantom Project
- HyperJS Episode 2 - Attack of the Accessors
- HyperJS Episode 3 - Revenge of the Script
- HyperJS Episode 4 - A New Hope?
- HyperJS Episode 5 - The Prototype Strikes Back
Last time, I discussed the second step on the road to JavaScript in C# in my HyperJS Episode 2 - Attack of the Accessors post. It covers the deficiencies of ExpandoObject and the simple dynamic class I created to allow JavaScript-style getter and setter notation in C# - HyperDynamo. All of the projects I'll talk about in this series are available on (my first) GitHub repos: http://github.com/tchype.
In part 3, the fun with crappy names continues. I'll be covering how combining the classes created in parts 1 and 2--along with closures in C#--allows for JavaScript-style Object declaration instead of class-based specification.
HyperJS Part 3: HyperHypo
Now that I have a HyperDynamo class that allows getters and setters to a dictionary via dynamic dot-notation syntax (foo.bar) and via indexer syntax (foo["bar"]), and I have a HyperDictionary class that allows for prototype-style inhertance and overrides of key/value pairs, let's combine them and see what kind of fun we can have.
DumpEnumerable
Here's a helper function I use often to display the members of a dynamic, enumerable class so we can see exactly what's defined where:
private static void DumpEnumerable(dynamic thing) { foreach (object o in thing) { Console.WriteLine(o); } }
Using HyperDynamo and HyperDictionary Manually
In part 2 of this series, I created a three-level set of HyperDictionaries: top, second, and third. Here's a quick synopsis:
- top: [eyes, brown], [hair, pointy], [things, { "first thing", "second thing" }]
- second (inherits from top): [hair, straight]
- third (inhertis from second): [things (extend), { 3, 4, 5 }], [hair (remove)]
Console.WriteLine("Using HyperDynamo with HyperDictionary MemberProvider to show\ndynamic mappings and inheritance!\n=================================================="); dynamic dynoThird = new HyperDynamo(third); //Manually use HyperDictionary with HyperDynamo dynoThird.toes = "third toes set through dynamic property"; Console.WriteLine("eyes:\t{0}", dynoThird.eyes); Console.WriteLine("toes:\t{0}", dynoThird["toes"]); Console.WriteLine(); try { //Should throw an exception since it got removed at this level Console.WriteLine("hair:\t{0}", dynoThird.hair); } catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException rbe) { Console.WriteLine("EXPECTED EXCEPTION SINCE PROPERTY WAS REMOVED:\n{0}", rbe); } Console.WriteLine("Properties in the HyperDynamo object built off of 3 levels of HyperDictionary\ninheritance and a couple dynamic property setters\n========================================================================"); DumpEnumerable(dynoThird);
Which outputs:
Using HyperDynamo with HyperDictionary MemberProvider to show dynamic mappings and inheritance! ================================================== eyes: brown toes: third toes set through dynamic property EXPECTED EXCEPTION SINCE PROPERTY WAS REMOVED: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'TonyHeupel.HyperCore.Hyp erDynamo' does not contain a definition for 'hair' at CallSite.Target(Closure , CallSite , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T 0 arg0) at HyperActive.ConsoleApp.Program.CombiningHyperDictionaryWithHyperDynamo(Obj ect third) in C:\Users\Tony\Documents\Visual Studio 2010\Projects\HyperActive\Hy perActive.ConsoleApp\Program.cs:line 168 Properties in the HyperDynamo object built off of 3 levels of HyperDictionary inheritance and a couple dynamic property setters ======================================================================== [things, System.Linq.Enumerable+<UnionIterator>d__88`1[System.Object]] [toes, third toes set through dynamic property] [eyes, brown]
HyperHypo - More Than C#, Less Than JavaScript
The naming suck-fest continues, but there is thought behind it regardless. I created a sublcass of HyperDynamo called HyperHypo with these two key characteristics:
- Requires a HyperDictionary for the MembershipProvider
- Has a Prototype property, where you can set the Prototype of the object, ala JavaScript, but the Prototype is another HyperHypo instance.
HyperHypo With Functions
There were some great additions to .NET 4 in the realm of dynamic capabilities, especially the Func<...> type. Basically, there's like 17 types of Func<> declared, where Func is a delegate that returns a type. In each of the definitions, the last type declaration is the return type and any ones previous are the types of the arguments (ordered). So for example, creating a function that takes no arguments and returns a string would be of type Func<string>. A function that takes an integer and returns a Foo object would be Func<int, Foo>. And so on...
It turns out, the "value" part of the key/value pair can be any object--including Func<>. So hold on, buckaroo: inheritance and functions and lambdas, oh my!
... // SayHello function defined in same class... public static string SayHello() { return "Hello"; } ... Console.WriteLine("Using HyperDynamo with HyperDictionary membership provider\n=========================================================="); Console.WriteLine("First example: prototype inheritance where only one.Whassup is set"); dynamic one = new HyperHypo(); one.Whassup = new Func<string>(SayHello); Console.WriteLine("one.Whassup(): {0}", one.Whassup()); //two inherits from one (set's it's prototype) dynamic two = new HyperHypo(one); two.HowsItGoing = new Func<string, string>(name => String.Format("How's it going, {0}?", name)); Console.WriteLine("two.Whassup(): {0}", two.Whassup()); Console.WriteLine("two.HowsItGoing(\"buddy\"): {0}", two.HowsItGoing("buddy"));
Outputs:
Using HyperDynamo with HyperDictionary membership provider ========================================================== First example: prototype inheritance where only one.Whassup is set one.Whassup(): Hello two.Whassup(): Hello two.HowsItGoing("buddy"): How's it going, buddy?
HyperHypo With Closures
First, a brief detour into JavaScript. Here's how you define an object with private properties in JavaScript (if you aren't familiar with this, there are plenty of books and web sites that describe how this works in detail):
<script type="text/javascript"> function Person(firstName, lastName) { var _firstName = firstName; var _lastName = lastName; this.getFirstName = function() { return _firstName; }; this.getLastName = function() { return _lastName; }; this.setFirstName = function(value) { _firstName = value; }; this.setLastName = function(value) { _lastName = value; }; this.toString = function() { return _firstName + " " + _lastName; } } ... var me = new Person("Tony", "Heupel") var singer = new Person(); singer.setFirstName("Paul"); singer.setLastName("Hewson"); alert(me); // Outputs "Tony Heupel" alert(singer); // Outputs "Paul Hewson" (but should have made it say Bono, somehow) </script>
A couple keys:
- Variables with function scope (those declared using "var") are private variables and retain their state because of the closure of the function
- The public properties/methods on the Person class instance are created by adding them to "this"
- When using the "new" keyword, you get a new object, which is an instance of the Person() function (the "this" inside the function) with it's closure intact, so each copy has it's own set of variables.
So let's do this with HyperHypo in C#!
class Program { static void Main(string[] args) { Console.WriteLine("Using HyperHypo (HyperDynamo with HyperDictionary)\nand closures to create JavaScript-style object declarations\n=========================================================================="); // Define the class as a function constructor and // private variables using closures (inline, without a separate class!) dynamic Person = new Func<string, string, dynamic>(delegate(string firstName, string lastName) { var _firstName = firstName; var _lastName = lastName; dynamic p = new HyperHypo(); p.getFirstName = new Func<dynamic>(delegate() { return _firstName; }); p.getLastName = new Func<dynamic>(delegate() { return _lastName; }); p.setFirstName = new Func<string, object>(value => _firstName = value); p.setLastName = new Func<string, object>(value => _lastName = value); p.toString = new Func<string>(delegate() { return String.Format("{0} {1}", _firstName, _lastName); }); return p; }); dynamic me = Person("Tony", "Heupel"); dynamic singer = Person(null, null); singer.setFirstName("Paul"); singer.setLastName("Hewson"); // Now output stuff and make sure the closures and functions work! Console.WriteLine("me.getFirstName():\t{0}", me.getFirstName()); Console.WriteLine("me.getLastName():\t{0}", me.getLastName()); Console.WriteLine("singer.getFirstName():\t{0}", singer.getFirstName()); Console.WriteLine("singer.getLastName():\t{0}", singer.getLastName()); Console.WriteLine("me:\t{0}", me.toString()); Console.WriteLine("singer:\t{0}", singer.toString()); Console.WriteLine(); // Notice that with the closure at the time the constructor was called, // each Person has it's own variable scope (closure) that does not // interfere -- even if the constructor is a static method! DumpEnumerable(me); DumpEnumerable(singer); } }
Which outputs:
Using HyperHypo (HyperDynamo with HyperDictionary) and closures to create JavaScript-style object declarations ========================================================================== me.getFirstName(): Tony me.getLastName(): Heupel singer.getFirstName(): Paul singer.getLastName(): Hewson me: Tony Heupel singer: Paul Hewson [getFirstName, System.Func`1[System.Object]] [getLastName, System.Func`1[System.Object]] [setFirstName, System.Func`2[System.String,System.Object]] [setLastName, System.Func`2[System.String,System.Object]] [toString, System.Func`1[System.String]] [getFirstName, System.Func`1[System.Object]] [getLastName, System.Func`1[System.Object]] [setFirstName, System.Func`2[System.String,System.Object]] [setLastName, System.Func`2[System.String,System.Object]] [toString, System.Func`1[System.String]]
Holy Crap Balls, Batman!
OK...so that actually works! Certainly, the static nature of C# adds some funky limitations at first glance, but this is pretty freaking sweet! I've always been a little jealous of the Node.js crowd (mainly because I haven't had time to try to it out on my Mac yet) getting to do server-side JavaScript with awesome, non-blockingness, but this really made me think that some form of something resembling JavaScript in C# is possible!
Up Next - HyperJS Project
Well, now that we can create objects dynamically in a way very similar to JavaScript, it only seems like a good idea to see what the limitations are. What better way, I think, than to see if I can actually create JavaScript in C#, including Object, Boolean, valueOf, toString, Image, etc.. That should quickly tell me how off my rocker I am.
Again, why? Mainly, "because I think I can." Really, I should look at using F# Power Tools with their Lexer/Parser to just make a compiler for JavaScript that runs on .NET or something. But really, wouldn't that still just generate C# code anyway?
Every class covered to date is located in my HyperCore project on GitHub. The idea is that these are small and potentially distinctly usable classes for different purposes. I really see any venture into HyperJS or JavaScript type work itself a separate concern that builds on top of HyperCore.
In my fourth post in this series, I'll cover the (VERY early) attempts at creating HyperJS (or JS.cs) and the significant decisions to make and issues to overcome. Mixing dynamic with statics, singletons, extension methods for seamless integration of libraries, and all kinds of weirdness that has basically been fun but bizarre. Stay tuned!