Clay:易塑的c#动态对象——第二部分
在第一部分中,我解释了在 Orchard 页面视图模型中我们的需求以及为什么我们认为 dynamic 是这种对象模型的一种非常好的解决方案。
现在,我们准备来看一下 Louis’Clay 库,看看怎样利用它创建并使用对象图。
在我们开始之前,我想先说明两个问题。
1.如果使用动态技术,我们不就失去了智能感知和编译时检查以及所有静态类型语言拥有的一切好处吗?C#是不是变得概念太多了,而想在每一方面都做好,但结果却没有一方面做得好呢?
安静,安静,一切都会很好的,放松。
现在想想你在.NET里所认识的所有 XML/DOM 方式的AIPs(或者Java里面类似的东西)。除非是在处理代码生成,否则你已经失去了智能感知和编译时检查能力。好吧,你是通过元模型(这是你不关心的)获得这些能力的,而不是通过实际数据(这才是你最关心的)。所以一切都很好,我们能够承担本来就没有的失去。而且还有很多测试,不是吗?
继续,至于C#的演进,你应该为它现在还充满创新和活力而感到高兴。比如,深入研究一下表达式树(expression trees)意味着什么,它是一个非常好的东西。
2. ExpandoObject 有什么问题?
没问题,但我们能够做得更好。ExpandoObject 实际上是通过一种令人惊讶方式实现的,这使得它非常高效。提示:不是通过字典。又提示:它是一个非常好的东西。
但是,依照API 可用性原则,它不够大胆,尤其是在构建深层次动态对象图方面它并没有给我们多少帮助。它的行为也比较固定且不能被扩展。
另一方面,Clay 是高度可扩展的,且专注于深层次对象图的创建和使用。
让我们开始吧。通过 Clay 你可以做的第一件事情就是创建一个简单的对象并在它上面设置属性。在此之前,我们将首先实例化一个给我们提供 语法 语义糖衣的工厂。我希望我们能够跳过这一步而使用一些类似静态API的方式(译注:静态工厂方法),但是我们不能。好了,正如你将看到的只需很小的代价:
dynamic New = new ClayFactory();
现在这个“New”对象将帮助我们创建新的 Clay 对象,正如它的名字所暗示一样(虽然这个名字只是一个惯例而已)。
以下是一些简单且没有多少新奇的东西:
var person = New.Person(); person.FirstName = "Louis"; person.LastName = "Dejardin";
这些你都能通过 ExpandoObject 来完成的,但是这里比较有趣的地方是它有多种实现形式,且开启了发掘潜能之窗。
例如,在 Clay 中,索引语法与属性访问器是相等的,就像 JavaScript 一样。当你在写代码通过名字去访问一个属性,而这个属性的名字在编译时刻又是未知的时候,这就非常有用了:
var person = New.Person(); person["FirstName"] = "Louis"; person["LastName"] = "Dejardin";
但还不止于此,你还可以将属性作为链式设置器来使用,像 jQuery那样:
var person = New.Person() .FirstName("Louis") .LastName("Dejardin");
或者,如果你喜欢,你还可以传入一个匿名对象:
var person = New.Person(new { FirstName = "Louis", LastName = "Dejardin" });
更加好的是,Clay 还能理解命名参数,我们可以这样写:
var person = New.Person( FirstName: "Louis", LastName: "Dejardin" );
总之,你可以使用很多种方式来设置属性和初始化 Clay 对象。
正如你所料,获取属性值也有多种方式且它们都是相等效果的:
person.FirstName person["FirstName"] person.FirstName()
你也可以创建 JavaScript-style 数组:
var people = New.Array( New.Person().FirstName("Louis").LastName("Dejardin"), New.Person().FirstName("Bertrand").LastName("Le Roy") );
这种方式创建的数组也是一个完整的 Clay 对象,这意味着你可以在运行时对它添加属性。
然后,如果你想知道数组里的总项数,或者获取数组第一项的 FirstName 属性值,你可以这样:
people.Count people[0].FirstName
当你想在一个已经存在的 Clay 对象上创建一个数组属性,这也非常容易:
person.Aliases("bleroy", "BoudinFatal");
如果有多于一个参数被传入,Clay 就会认为你正在初始化的这个属性是数组。但是如果只有0或1个参数,你只虽显式地传入一个数组 (CLR or Clay):
person.Aliases(new[] {"Lou"});
相比 CLR 数组,Clay 数组能动态增长:
person.Aliases.Add("loudej");
而且,它们也能够响应一些方法调用,如 AddRange, Insert, Remove, RemoveAt, Contains, IndexOf, or CopyTo 。
综合起来,我们就可以通过一种非常简洁而又富有表现力的语法来创建一个相当复杂的对象图:
var directory = New.Array( New.Person( FirstName: "Louis", LastName: "Dejardin", Aliases: new[] { "Lou" } ), New.Person( FirstName: "Bertrand", LastName: "Le Roy" ).Aliases("bleroy", "boudin"), New.Person( FirstName: "Renaud", LastName: "Paquay" ).Aliases("Your Scruminess", "Chef") ).Name("Some Orchard folks");
最后一点我想说明的是,Louis 第一次展示它给我看的时候,我觉得真的非常优雅和惊讶。
想像一下你有一个CLR接口需要实现,例如:
public interface IPerson { string FirstName { get; set; } string LastName { get; set; } }
但是你想使用一个 Clay 对象,比如在上面定义的数组 persons 中一个元素。是的,你可以这样:
IPerson lou = people[0]; var fullName = lou.FirstName + " " + lou.LastName;
这里最特别的是 lou 是一个非常合法的静态类型 CLR 变量,你将获得全部智能感知和编译时检查。虽然我们从未写过实现这个接口的具体类型,但它就是一个实现了 IPerson 的对象。
能够实现如此不可思议的功能,是因为 Clay 重写了转换操作符,并为这个接口创建了一个动态代理(使用 Castle),这个动态代理再委托成员调用给 Clay 对象。
因此,那是一个真正 CLR 类型,但它是在运行时被生成的。
那就是使你能够写以下代码的:
foreach(var person in directory) { Trace.Write(person.FirstName); }
这里发生了事情是: “directory” Clay 数组被转换成一个 IEnumerable,而所有相应的方法都通过 Clay 动态数组对象实现。
我们准备在将来的帖子中更深入在研究 Clay,因为还很多需要做的,但应该对这个库的基本意图给出一个令人满意的介绍。我真希望你喜欢它,并想到一些非常好的主意来使用它。
You can find Clay here: http://clay.codeplex.com/
本贴的第一部分:http://aprogrammer.net/2012/08/clay-malleable-c-dynamic-objects-part-1-why-we-need-it/
原文地址:http://aprogrammer.net/2012/08/clay-malleable-c-dynamic-objects-part-2/