Scott Hanselman

NuGet Package of the Week #6 - Dynamic, Malleable, Enjoyable Expando Objects with Clay

May 07, 2011 Comment on this post [26] Posted in Learning .NET | NuGet | NuGetPOW
Sponsored By

Hey, have you implemented the NuGet Action Plan? Get on it, it'll take only 5 minutes: NuGet Action Plan - Upgrade to 1.2, Setup Automatic Updates, Get NuGet Package Explorer. NuGet 1.3 is out, so make sure you're set to automatically update!

The Backstory: I was thinking since the NuGet .NET package management site is starting to fill up that I should start looking for gems (no pun intended) in there. You know, really useful stuff that folks might otherwise not find. I'll look for mostly open source projects, ones I think are really useful. I'll look at how they built their NuGet packages, if there's anything interesting about the way the designed the out of the box experience (and anything they could do to make it better) as well as what the package itself does.

This weeks Package of the Week is "Clay." It makes working with dynamic objects even more fun. It was written for the open source Orchard Project by Louis DeJardin with an assist from Bertrand LeRoy.

image

Enjoyable Dynamics in a Static Language

Here's a little copy/paste from a post two years ago I did on the dynamic keyword in C#. I thought it was good, so I'll include it again here.

So I asked this guy, what's up with the dynamic keyword, and what type was it exactly? I mean, C# isn't dynamic, right? He says:

"Oh, well it's statically-typed as a dynamic type."

Then my brain exploded and began to leak out my ears. Honestly, though, it took a second. Here's a good example from some of Anders' slides:

Calculator calc = GetCalculator();
int sum = calc.Add(10, 20);

That's the creation of an object, invokation of a method, and the collection of a return value. This is the exact same code, as the "var" type is figured out at compile time.

var calc = GetCalculator();
int sum = calc.Add(10, 20);

If you wanted to do the exact same thing, except with Reflection (like if it were some other class, maybe old-COM interop, or something where the compiler didn't know a priori that Add() was available, etc) you'd do this:

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add",
BindingFlags.InvokeMethod, null,
new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

It's pretty horrible to look at, of course. If the object is some dynamic thing (from any number of sources), we can do this:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

And get the dynamic method invocation and conversion of the return type. Basically it looks just like we're calling any other object.

My buddy Rob Conery and I love dynamic languages, but we also love the .NET CLR. If we had our way, there'd be a lot more support for the Dynamic Language Runtime and the Iron.NET languages. We wrote the http://thisdeveloperslife.com website using ASP.NET Web Pages largely because it uses the Razor template engine - which feels very dynamic - and we used dynamics throughout the code.

Some folks think that static languages have no business dipping their toes into the dynamic pool, but I disagree. Successful compilation is just the first unit test, as they say, and I like the ability to pick and choose between static and dyanamic.

Expandos and Dynamic

In .NET, the Expando object is a dynamic type that lets you add and remove members to it, ahem, dynamically. It's great for dealing with dynamic data. You might do this:

dynamic myObject = new ExpandoObject();
myObject.WhateverMakesMeHappy = "Scott";

And boom, I've got a new property. You can even "cast" Expandos as other types and start using them like that type. It's crazy. Play with it.

Anonymous objects via object initalizers are nice, but once you've made one, it's stuck that way. For example, from Bertrand Le Roy's blog

Html.TextBoxFor(m => m.CurrentUser, new {
title = "User Name",
style = "float:left;"
})

See the object intializer? It makes an anonymous object, but it'll have that shape with title and style, forever.

Why is Clay needed?

In Bertrand's words:

In terms of API usability [ExpandoObject is] not very daring and in particular it does not do much to help you build deep dynamic object graphs. Its behavior is also fixed and can’t be extended.

Clay on the other hand is highly extensible and focuses on creation and consumption of deep graphs.

Clay has a clever naming convention (although you may hate it. Relax, it's a convention) where you name the ClayFactory instance "New." Yes, capital-N "New." *brain explodes again*

You can do the usual stuff with Clay that you can also do with Expando, of course. But, you can use several different techniques depending on the situation you're in, and that's where it gets interesting. Here's some examples from Bertrand and Lou, starting with the ClayFactory creation:

dynamic New = new ClayFactory();

Now this “New” object will help us create new Clay objects, as the name implies (although this name is just a convention). Then:

var person = New.Person();
person.FirstName = "Louis";
person.LastName = "Dejardin";

For instance in Clay, indexer syntax and property accessors are equivalent, just as they are in JavaScript. This is very useful when you are writing code that accesses a property by name without knowing that name at compile-time:

var person = New.Person();
person["FirstName"] = "Louis";
person["LastName"] = "Dejardin";

You can also use properties as chainable setters, jQuery-style:

var person = New.Person()
    .FirstName("Louis")
    .LastName("Dejardin");

Or you can pass an anonymous object and it will become a Clay object:

var person = New.Person(new {
    FirstName = "Louis",
    LastName = "Dejardin"
});

Even better, Clay also understands named arguments, which enables us to write this:

var person = New.Person(
    FirstName: "Louis",
    LastName: "Dejardin"
);

Or even this as an array:

var people = New.Array(
New.Person().FirstName("Louis").LastName("Dejardin"),
New.Person().FirstName("Bertrand").LastName("Le Roy")
);

All of this also means that these are all equivalent:

person.FirstName
person["FirstName"]
person.FirstName()

To get started, rather than using NuGet to "install-package Clay," I'd recommend you install Clay.Sample. This is a common convention for open source projects to include sample packages that have a dependency on the project itself. Install the sample and you'll get both packages.

Here's some other cool samples that really give you an idea of how you can move like clay between the dynamic and static worlds:

public interface IPerson {
string FirstName { get; set; }
string LastName { get; set; }
}

public static void CastToCLRInterface() {
dynamic New = new ClayFactory();

var person = New.Person();
person.FirstName = "Louis";
person.LastName = "Dejardin";

// Concrete interface implementation gets magically created!
IPerson lou = person;

// You get intellisense and compile time check here
Console.WriteLine("{0} {1}", lou.FirstName, lou.LastName);
}

I'd like the see folks in power *cough* Anders *cough* check out things like Clay and make them built in. Yum.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
May 07, 2011 3:50
Hey if you can get something named "Magic Unicorn Edition" thru the powers that be, Clay inclusion should be a snap!
May 07, 2011 3:54
Damn right! Microsoft Playdough!
May 07, 2011 6:20
I think I just threw up in my mouth. I'd hate to inherit that code.
May 07, 2011 6:54
Great pick Scott! Been building orchard modules, parts and types for the past few days and loving Clay.
May 07, 2011 7:19
Excellent. Followed the links ... read the blogs ... very, very useful and elegant. For those who don't quite get why it's required ... (looking at you Josh) ... well, you have to consider things like JSON, XML, HTML DOM, and many other domains. A bit of experience dealing with dynamic types will change your tune.
May 07, 2011 10:33
Did I miss the link to the clay website? Or doesn't it have one.
May 07, 2011 10:42
Clay does have a website, http://clay.codeplex.com but you won't be blown away by the documentation or disucssions ;)

Source code is available there.
May 07, 2011 11:38
maybe one day javascript and c# will be one and the same
May 07, 2011 12:05
And here is the VB.Net version of the above.

http://blogs.lessthandot.com/index.php/DesktopDev/MSTech/using-clay-in-vb-net

Not sure why I can't get it to work in all cases, but I'll try some more and see what I can come up with that isn't to ugly.
May 07, 2011 14:57
Holy moly! This is the first time in 7 months I get excited about anything C#! This should definitely be built into the language! I had not in my wildest imagination thought this would be possible even with the dynamic keyword.

Downloading it as I write this.
May 08, 2011 7:03
Nice. Another tool for the toolkit. Something definitely not to be overused, but then, doesn't that apply to just about everything!
May 08, 2011 13:18
"Those that do not learn from history are doomed to repeat it."

The dark side of ClaySharp is that when you make an error, or spelling mistake, it can take forever to figure out what went wrong.

I have been getting into Orchard CMS, which uses ClaySharp extensively. In creating my own module, I misspelled the optional parameter name for Model. Instead of Model, I wrote Models. 2 days of trying to debug the thing got me nowhere because this dynamic system is almost impossible to debug.

This is the big problem that VB used to have before "Option Strict" was invented, and it's the kind of thing that makes you pull your hair out when something goes wrong. Very powerful indeed, but also very easy to shoot yourself in the foot with.
May 08, 2011 17:49
I'd actually really like to update the Clay package to support Castle 2.5.3 rather than 2.1 which is currently uses, so bug the team to get this pull request accepted (http://clay.codeplex.com/SourceControl/network/Forks/slace/Clay/contribution/1046) and I'll get a new NuGet package pushed.
May 09, 2011 20:11
Not sure how these two could be equivalent:

person.FirstName, person.FirstName()

What if I'd done this?

person.FirstName = new Action(() => blah... );

Given that person.FirstName would return my delegate, therefore person.FirstName() should invoke the delegate.
May 09, 2011 21:52
Scott:

You forgot to include your unit tests. ;)
May 10, 2011 2:06
@mystere.man: when you are saying that it is impossible to debug, I understand what you are saying and I sympathize with that, but it's not exactly true. First, we added the shape tracing feature that does drill into the object graph. Second, it's always been the intent to later provide debugger support, which is entirely feasible (and would be an excellent contribution to the project, which is open source).

@Daniel: this is not currently supported. As you will see if you try, there are a few things in the current CLR that get in the way of dynamic and Lambdas working well enough together. At least that was my understanding last time I asked Lou about this (which was a while ago). There are a few things that could be done to approach this kind of thing but it's not trivial. The method FirstName() is explicitly wired by a Clay behavior.
May 14, 2011 11:42
Bertrand,

I didn't say it was impossible, I said "almost impossible", which basically means "very difficult". Yes, you can debug anything at one level or another. I'm just saying that the current implementation makes it very hard to figure out what's going on.

The way Orchard uses ClaySharp is paticularly difficult because it extensively uses lambdas in ClaySharp methods that get invoked in other parts of the system, and like I said.. if you misspell something your lambda doesn't get called, and various defaults are assumed (particularly in the case of the optional parameters).

Shape Tracing is great, except in the case where exceptions get thrown before the page can be rendered.. so you can't get to the Shape Tracing functionality. I'm not going to get into a big discussion on this on Scott's blog, and I don't want to Hijack it. My point is merely that this all seems so familiar, and seems like a lesson was not learned.
May 24, 2011 4:34
One other feature I am missing from all of this is a lightweight method to, at runtime, enumerate the list of properties and methods that exist (or are added dynamically) on the given object (and retrieve their values) without having to resort to reflection (performance concerns).

Since I see that you can set a property via an array index (cool!), can you also enumerate the array of property names somehow in Clay, or some other approach that I am missing?
Jon
May 24, 2011 15:26
yes really nice tool for toolkit.. i like d it very much .
May 25, 2011 9:28
To help the problem of mistyping perhaps there could be a "lock" method that would prevent further changes to the object model. And an "unlock" when dynamic changes are intended again. This could prevent many errors.
June 21, 2011 12:48
I'm not sure I understand the benefits. Could you give us a use case?
June 21, 2011 21:43
Well, it does guarantee lifetime employment--nobody will be able to figure out your code.
June 22, 2011 15:54
Haskell keeps improving its type system to handle new kinds of problems. Lisp/Scheme already could do this in 1960. Now we have C#/Clay turning into Ada...or perhaps C...or perhaps any type system you want...or no type system. If I sound confused, I am. And so is Microsoft and its developer community.
June 28, 2011 19:35
Hi Scott,

There is another nuget package called ImpromptuInterface that has some overlap with clay. It can do the same deep prototype object graph initialization syntax it's 99% compatible syntax wise with clay but get's you much better performance. The implementation is general enough that it also lets you use Microsoft's ExpandoObjects as the prototype object too which will give you amazing performance, but you pretty much have to stick to the named argument syntax at that point.

Impromtu also lets you put an interface at run time on any IDynamicMetaObjectProvider (or poco class).
It has several functions that simplify accessing the DLR library while maintaining performance, which makes all of the above possible without having to require a specific dynamic object, in fact it will work with clay objects just as well. So if Clay's behavior injection pattern is too complex for a specific dynamic implementation you can use ImpromptuInterface and your own DynamicObject subclass and still be able to use clay objects in that same way.



June 28, 2011 22:27
impromptu-interface works better for VB.Net for me. and does the same thing.

http://blogs.lessthandot.com/index.php/DesktopDev/MSTech/impromptu-interface-instead-of-clay
June 28, 2011 22:30
Just noticed someone already mentioned that here, oops. I promise I have nothing to do with that project.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.