Quantcast
Channel: Scott Hanselman's Blog
Viewing all articles
Browse latest Browse all 1148

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

$
0
0

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



© 2011 Scott Hanselman. All rights reserved.



Viewing all articles
Browse latest Browse all 1148

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>