One of the things I have seen people applaud about Python is its "namedtuple" class. If you could describe this in terms of .Net’s Tuple<T> (and <T1, T2, etc>) it would be to basically say that it’s the same as Tuple<T>, but if instead of "Item1" on Tuple<T> you got "Name" or "Age" or whatever other meaningful name for the property (instead of ‘Item1’). And, of course, if you weren’t limited to at most 8 items on the Tuple object.
That being said, let’s have a look at how namedtuple works in practice:
C:\Users\Brandon>python
Python 2.7.6 (default, Nov 10 2013, 19:24:18) [MSC v.1500 32 bit (Intel)] on win
32
Type "help", "copyright", "credits" or "license" for more information.
>>> from collections import namedtuple
>>> PersonClass = namedtuple(‘person’, ‘name, age, gender’)
>>> PersonClass
<class ‘__main__.person’>
>>> p = PersonClass(name=’brandon’, age=32, gender=’m')
>>> p
person(name=’brandon’, age=32, gender=’m')
>>> p.name
‘brandon’
>>>
Let’s go over what you’re seeing in detail as it relates to what you may, or may not know, about languages and .Net in particular:
The call to ‘namedtuple’ creates a new class in the type system called ‘person’ with properties ‘name’, ‘age’, and ‘gender’ on it. The return value from this call is assigned to ‘PersonClass’ variable which effectively becomes an alias for the new class within the type system. When you want to use this newly created class, you reference it with ‘PersonClass’ – a usual practice in this area is to assign the return value to the same name as the class you created. In other words ‘person’ instead of ‘PersonClass’. I did this here to illustrate the difference.
After you have an alias to this new type, you get to use it just like you would any other type. I create a new instance of this type assigned to variable ‘p’ by simply using the constructor that is created for this type. The default constructor for types created by ‘namedtuple’ is one that takes a value for each of the properties you gave the new type. So in this example, it’s a constructor that takes values for ‘name’, ‘age’, and ‘gender’. If you try to create an instance without specifying them all, you’ll get an error.
After creating my new instance with its values, you get to reference those values by the names you assigned to the properties when you created the new class in the type system with the call to ‘namedtuple’.
Indeed it is pretty slick. So… let’s see what we can do with .Net shall we?
My first thought here was that Dynamic objects in general somewhat "solve" this issue. What they don’t provide, however, is the "definition" and the ability to add properties on the fly. At least, not at first glance.
I knew about ExpandoObject from a few years back, and decided to see what it could do for me. If you haven’t seen this before, it’s pretty mind blowing when you first see what you can do with it. Here’s a quick example:
1: dynamic coolStuff = new System.Dynamic.ExpandoObject();
2: coolStuff.name = "Brandon";
3: coolStuff.age = 32;
4: coolStuff.gender = 'M';
5:
6: Console.WriteLine("name: " + coolStuff.name);
And the output:
name: Brandon
"Whoa, what?" Yeah – the beauty of .Net’s DLR (Dynamic Language Runtime). It’s pretty awesome. ExpandoObject is a special type that says "if you assign something to a property and I don’t have it already, I’ll just create the property for it on myself and let you get at it later". It’s a property bag on steroids. The biggest key to it, though, is that you have to cast it to a ‘dynamic’ object in order to really use it.
So this is pretty close to namedtuple, but not quite there. We want the properties there for usage at creation time, and we’d also like the other niceties that Python provides like _make, _replace, and _fields to do some special things.
Unfortunately, ExpandoObject can’t be inherited from (sealed type) but there is another type that the DLR provides that lets you do these cool dynamic typing things; DynamicObject.
This type is one that hooks in to what the DLR does at runtime and, more importantly, lets you react to them. In the case of namedtuple, what you’re reacting to are the Get/Set calls used in property fetching and assignment.
But let’s roll back a second. At face value what *really* are we playing with here? It’s a Dictionary. More specifically, Dictionary<string, object>. It’s a collection of values that you can look up by a string. The only difference is you get to do it in good old-fashioned OO ways, by using property calls. That being said, if you were to create a class that used DynamicObject to react to type system calls, what would you back it with? A Dictionary.
Let’s get down to brass tax and see how this can be done:
1: public class NamedTuple : DynamicObject
2: {
3: private readonly IDictionary<string, object> _contained = new Dictionary<string, object>();
4:
5: 6: 7: 8: 9: 10: 11: 12: 13: public override bool TryGetMember(GetMemberBinder binder, out object result)
14: {
15: var contAsDict = (IDictionary<string, object>)_contained;
16: return contAsDict.TryGetValue(binder.Name, out result);
17: }
18:
19: 20: 21: 22: 23: 24: 25: 26: 27: public override bool TrySetMember(SetMemberBinder binder, object value)
28: {
29: var contAsDict = (IDictionary<string, object>)_contained;
30: try
31: {
32: contAsDict[binder.Name] = value;
33: return true;
34: }
35: catch
36: {
37: return false;
38: }
39: }
40: }
And boom. By inheriting from DynamicObject we’re letting .Net know that we intend on intercepting the DLR’s calls while it’s resolving the code at run time. Specifically the Get and Set Member calls.
And how do we use this gem?
dynamic nt = new NamedTuple();
nt.name = "Brandon";
I know, it looks a lot like ExpandoObject. But the important thing to know here is that I can’t change how ExpandoObject works, is constructed, other functionality on it, nothing. But I can change my new NamedTuple. When the ‘name’ property is assigned, the code that’s executed is the code in TrySetMember above. You can see that all that does is store the value in my underlying dictionary. Similarly when it’s retrieved, TryGetMember is executed and the value’s pulled back out.
Interestingly enough, if you look at the documentation of Python’s namedtuple, you’ll see it provides methods to convert a namedtuple in to a dictionary – it’s now obvious to see why.
So let’s look at providing the exact functionality of Python’s variant in to ours. The first thing you see is that there’s a nice constructor to specify the property names. Easy enough:
public NamedTuple(IEnumerable<string> propertyNames)
{
foreach (var property in propertyNames)
{
_contained.Add(property, null);
}
}
And let’s go one step further and give our users to populate the names and the values just for convenience sake:
public NamedTuple(IDictionary<string, object> propertiesAndValues)
{
_contained = propertiesAndValues;
}
Now the equivalent to Python’s _make:
public void Make(IEnumerable<object> contents)
{
for (int i = 0; i < contents.Count(); i++)
{
_contained[_contained.Keys.ElementAt(i)] = contents.ElementAt(i);
}
}
[TestMethod]
public void Make()
{
dynamic t = new NamedTuple(new[] { "one", "two", "three" });
t.Make(new object[] { 3, 2, 1 });
Assert.AreEqual(3, t.one);
Assert.AreEqual(2, t.two);
Assert.AreEqual(1, t.three);
}
_fields:
public ICollection<string> Fields
{
get
{
return _contained.Keys;
}
}
[TestMethod]
public void Fields()
{
var t = new NamedTuple(new[] { "one", "two", "three" });
Assert.IsTrue(t.Fields.Contains("one"));
Assert.IsTrue(t.Fields.Contains("two"));
Assert.IsTrue(t.Fields.Contains("three"));
}
_replace:
public NamedTuple Replace(string field, object value)
{
if (!_contained.ContainsKey(field))
{
throw new ArgumentException("Field not found in tuple", "field");
}
var newDict = new Dictionary<string, object>(_contained);
newDict[field] = value;
return new NamedTuple(newDict);
}
The important note about _replace is that you get a new instance back. Same as Python.
[TestMethod]
public void Replace()
{
dynamic t = new NamedTuple(new[] { "one", "two", "three" });
t.Make(new object[] { 3, 2, 1 });
dynamic newT = t.Replace("one", 4);
Assert.AreEqual(4, newT.one);
Assert.AreNotEqual(t.one, newT.one);
}
Since we can see how similar ExpandoObject and NamedTuple are, let’s give our users an easy way to convert an ExpandoObject to our NamedTuple:
public static implicit operator NamedTuple(ExpandoObject eo)
{
return new NamedTuple((IDictionary<string, object>)eo);
}
And since we know Python provides the ability to take a NamedTuple instance and give the user back a Dictionary:
public static implicit operator Dictionary<string, object>(NamedTuple me)
{
return (Dictionary<string, object>)me._contained;
}
[TestMethod]
public void AsDict()
{
var t = new NamedTuple(new[] { "one", "two", "three" });
t.Make(new object[] { 3, 2, 1 });
var d = (Dictionary<string, object>)t;
Assert.IsInstanceOfType(d, typeof(Dictionary<string, object>));
var dyn = (dynamic)t;
Assert.AreEqual(dyn.one, d["one"]);
Assert.AreEqual(dyn.two, d["two"]);
Assert.AreEqual(dyn.three, d["three"]);
}
And there you have it. The full class is available on NuGet as ‘namedtuple’.
The only couple of things I didn’t implement here are a few of the more funky constructors that Python’s has – I didn’t see them as too valuable past what we have here, but if you find you would like them or make changes to this class to add them don’t hesitate to contact me so I can update the NuGet package with your changes!