孜孜不倦的程序员

使用 Gemini 库实现动态化

Ted Neward

Ted Neward我的文章或博客的读者将会知道这已经,但对于那些闯进了这篇文章 (是否由事故或因为他们觉得这是星座),我往往要花很多时间去看其他语言和平台。 这通常是为了帮助模型软件,更有效地、 高效率地或准确的想法或概念。

最近有一个趋势 — — 尽管它并不是所有最近 — — 内 Web 社区一直追求的"动态"或"脚本"的语言,特别是其中两个:红宝石 (Ruby on Rails 框架,有时也称为 RoR,编写所针对的) 和 JavaScript (我们具有的 Node.js 执行服务器端的应用程序,有数以百计的框架)。 这两种语言都缺乏什么我们习惯了在 C# 和 Visual Basic 世界的特征:严格遵守哪些类定义作为唯一定义哪些对象组成。

在 JavaScript (有时自作聪明主持人像我自己作为"Lisp 用大括号"的特点是语言),例如,对象是一个完全可变的实体,意味着您可以在需要时 (或想要) 添加属性或方法:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;
myCar.makeSounds = function () {
  console.log("Vroom!
Vroom!")
}

对象 myCar,首先构造时,对它有没有属性或方法 — — 隐式添加这些时的数据值 ("福特"、"野马,"1969年和函数) 都设置为那些名字 (厂商、 型号、 年和 makeSounds)。 本质上,每个在 JavaScript 中的对象是只是一本字典的名称/值对,凡对的值可以是要调用的函数或数据元素。 除语言设计者,玩这样的类型信息的能力,通常称为元对象协议 (澳门币),和约束的子集,这通常称为面向方面编程 (AOP)。 它是灵活、 强大的对象,但这是非常不同的 C# 方法。 而是比尝试创建复杂的类层次结构,在其中您尝试捕获每个可能的变化,通过继承,作为传统的 C# 对象设计建议,议定书 》 的做法说真实世界中的事物不所有完全相同的 (除了其数据的课程),并在其中你示范他们的方式不应该。

开发者社区 Microsoft.NET 框架的一部分已经许多年现在会记得较早版本的 C# 引入动态关键字/类型,允许您对对象的引用在运行时发现其成员的声明,但这是一个不同的问题。 (动态功能集使容易写反思样式代码,不创建对象的拖把种类)。幸运的是,C# 开发人员有两个选项:传统的静态类型定义,通过标准的 C# 类设计机制 ; 或灵活的类型定义,通过开放源代码库调用生成动态的功能,以使你附近 JavaScriptian 功能上的双子座。

双子座的基本知识

好像很多,我在本专栏中讨论过的软件包,双子是通过 NuGet 可用:在软件包管理器控制台中的"安装软件包双子星"到您的项目带来了善良。 不像其他软件包你已经见过,不过,双子座入项目安装时它不带大会或两个 (或三个以上)。 相反,它带来了几个源代码文件并将它们放入一个叫做"橡木"文件夹中并直接向项目中添加它们。 (写这篇文章,双子座 1.2.7 包含四个文件:Gemini.cs、 GeminiInfo.cs、 ObjectExtensions.cs 和一个文本文件,其中包含的发行说明)该文件夹命名为橡木的原因是实际上非常合理:双子座是实际上的子集 (不令人惊讶的是,称为橡木) 的较大项目,带来了很多这个动态编程善良,对 ASP.NET MVC 世界 — — 大橡木包在以后的专栏中,我将探讨。

自行确定事实那双子星座交付作为源真的没什么大不了 — — 生活在自己的命名空间 (橡木) 的代码,和将简单地被编译成项目的源代码文件的其余部分是。 实际一点,然而,有源代码文件便于荒谬地逐句通过双子座的源代码时出了差错,或甚至只是为了看看什么是可用的因为智能感知有时完全被击败的动态关键字类型使用的代码通过拇指。

开始使用

再次,正如我的习惯,我开始通过创建单元测试项目,用来写一些勘探测试 ; 到该项目安装双子并测试它外面通过创建一个简单的"你好的世界"-像测试:

[TestMethod]
public void CanISetAndGetProperties()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
}

发生了什么在这里微妙的但功能强大:双子星座,在另一边的"人"的引用,该对象是一个类型,直到分配到 (如前面的代码的情况下) 或显式添加到对象中的方法,SetMember 和 GetMember,通过这些成员是基本上是空的所有的属性或方法,就像这样:

[TestMethod]
public void CanISetAndGetPropertiesDifferentWays()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = "Neward" });
  Assert.AreEqual(person.FirstName, "Ted");
  Assert.AreEqual(person.LastName, "Neward");
  person = new Gemini();
  person.SetMember("FirstName", "Ted");
  person.SetMember("LastName", "Neward");
  Assert.AreEqual(person.GetMember("FirstName"), "Ted");
  Assert.AreEqual(person.GetMember("LastName"), "Neward");
}

虽然我这样做,这里的数据成员,也是同样容易做这行为成员 (即,方法),通过将它们设置为 DynamicMethod (它返回 void) 或动态的实例­函数 (该公司预计返回值),其中每个不带参数。 或者您可以设置它们向其伙伴"WithParam",如果该方法或函数可以带参数,就像这样:

[TestMethod]
public void MakeNoise()
{
  dynamic person =
    new Gemini(new { FirstName = "Ted", LastName = "Neward" });
  person.MakeNoise =
    new DynamicFunction(() => "Uh, is this thing on?");
  person.Greet =
    new DynamicFunctionWithParam(name => "Howdy, " + name);
    Assert.IsTrue(person.MakeNoise().Contains("this thing"));
}

一个有趣的趣闻升起出双子图书馆,顺便说:双子座 (没有任何的替代执行) 的对象使用"结构性键入"以确定他们是否相等,或它们是否满足特定的实现。 相反 OOP 类型系统,使用继承/IS-A 测试来确定给定的对象是否可以满足对象参数类型上的限制,结构上类型化的系统反而只问中传递的对象是否具有所有要求 (在这种情况下,成员) 使代码正常运行所需。 结构性打字、 之间的功能的语言,是众所周知还要"鸭子类型化"一词在动态语言 (但这不会是个很酷的声音)。

一会儿,考虑采用对象并打印出友好的消息,有关该对象,如图中所示的方法, 图 1

图 1 使用方法的对象,并且打印出一条消息

string SayHello(dynamic thing)
{
  return String.Format("Hello, {0}, you are {1} years old!",
    thing.FirstName, thing.Age);
}
[TestMethod]
public void DemonstrateStructuralTyping()
{
  dynamic person = new Gemini(
    new { FirstName = "Ted", LastName = 
      "Neward", Age = 42 });
    string message = SayHello(person);
    Assert.AreEqual("Hello, Ted, you are 42 years old!", 
      message);
    dynamic pet = new Gemini(
      new { FirstName = "Scooter", Age = 3, Hunter = true });
  string otherMessage = SayHello(pet);
  Assert.AreEqual("Hello, Scooter, you are 3 years old!", 
      otherMessage);
}

通常情况下,在传统面向对象层次结构中,人和宠物将很可能来自非常不同的继承树的分支 — — 人和宠物不一般共享大量的共同属性 (尽管猫所认为的) 软件系统中。 在结构上或鸭键入系统,然而,较少的工作需要去做深和无所不包的继承链 — — 如果有人类那也狩猎,然后嘿,人权上有一个"猎人"成员和任何想要检查的对象的猎人状态的例程传递中可以使用该成员,无论是一个人、 猫或捕食者无人机。

审讯

权衡鸭打字的办法,许多人会注意到,是编译器不能执行,只可以通过某些种类的对象中,并且也是如此的双子座类型 — — 尤其是因为大多数双子座代码惯用法上存储的对象背后的动态引用。 您需要带一点额外的时间和努力,以确保被移交的对象满足要求,否则,面对一些运行时异常。 这意味着质问的对象来看看它有没有必要的成员,双子座在完成使用 RespondsTo 方法 ; 也有一些方法返回的双子座承认作为一个给定对象的一部分的各个成员。

例如,请考虑需要知道如何猎取的对象的方法:

int Hunt(dynamic thing)
{
  return thing.Hunt();
}

当踏板车中传递时,东西做工精细,如图所示,在图 2

图 2 当它工作时的动态编程

[TestMethod]
public void AHuntingWeWillGo()
{
  dynamic pet = new Gemini(
    new
    {
      FirstName = "Scooter",
      Age = 3,
      Hunter = true,
      Hunt = new DynamicFunction(() => new Random().Next(4))
    });
  int hunted = Hunt(pet);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
  // ...
}

但不知道如何狩猎的东西传递时,异常将导致,如图所示,在图 3

图 3 动态编程失败时

[TestMethod]
public void AHuntingWeWillGo()
{
  // ...
dynamic person = new Gemini(
    new
    {
      FirstName = "Ted",
      LastName = "Neward",
      Age = 42
    });
  hunted = Hunt(person);
  Assert.IsTrue(hunted >= 0 && hunted < 4);
}

为防止这种情况,狩猎方法应该测试通过使用 RespondsTo 方法存在问题的成员。 这是简单的包装,周围的 TryGetMember 方法,用于简单的布尔值 yes/no 的反应:

int Hunt(dynamic thing)
{
  if (thing.RespondsTo("Hunt"))
    return thing.Hunt();
  else
    // If you don't know how to hunt, you probably can't catch anything.
return 0;
}

顺便说一句如果这一切都看起来相当简单的样板或字典 < 对象,字符串 > 不是不正确的评估的包装 — — 双子座类的基础是,确切的字典接口。 但包装类型有助于缓解某些类型系统波动,否则是必要的如不会使用 dynamic 关键字。

但当几个对象共享类似的行为时,会发生什么呢? 例如,四只猫都知道如何打猎,和它将是比较低效的要写一个新的匿名方法定义为所有四,尤其是作为所有四个份额猫科动物狩猎本能。 在传统的 OOP 中这不是一个问题,因为他们会全部被猫类的成员,因此共享相同的实现。 在澳门币系统,如 JavaScript,通常是一种机制允许对象将推迟或"链"属性或请求调用另一个对象中,称为"原型"。双子座在您使用有趣的组合静态打字和拖把称为"扩展"。

Prototype

首先,您需要标识猫的基本类型:

public class Cat : Gemini
{
  public Cat() : base() { }
  public Cat(string name) : base(new { FirstName = name }) { }
}

请注意,猫类继承双子星座,这是什么会使猫类也到目前为止已讨论的所有动态的灵活性 — — 事实上,猫第二个构造函数使用同一双子座构造函数被用来创建动态的所有实例,到目前为止。 这意味着所有的前面的散文仍持有任何猫的实例。

但双子还允许我们的猫可以如何申报"扩展",这样,每一只猫收益相同的功能,而无需显式地将它添加到每个实例。

扩展类

为此功能的实际使用,认为这就是正在开发的 Web 应用程序。 通常情况下,你需要对 HTML-­转义名称值被存储和返回,为了避免无意中允许 HTML 注射 (或者更糟,SQL 注入):

string Htmlize(string incoming)
{
  string temp = incoming;
  temp = temp.Replace("&", "&amp;");
  temp = temp.Replace("<", "&lt;");
  temp = temp.Replace(">", "&gt;");
  return temp;
}

这是一种痛苦 ; 系统中定义的每个模型对象上都要记住 幸运的是,拖把允许您系统地"中达到"和定义行为的新成员上的模型对象,如图所示,在图 4

图 4 写作方法,而无需编写方法

[TestMethod]
public void HtmlizeKittyNames()
{
  Gemini.Extend<Cat>(cat =>
  {
    cat.MakeNoise = new DynamicFunction(() => "Meow");
    cat.Hunt = new DynamicFunction(() => new Random().Next(4));
    var members =
      (cat.HashOfProperties() as IDictionary<string, object>).ToList();
    members.ForEach(keyValuePair =>
    {
      cat.SetMember(keyValuePair.Key + "Html",
        new DynamicFunction( () =>
          Htmlize(cat.GetMember(keyValuePair.Key))));
    });
  });
  dynamic scooter = new Cat("Sco<tag>oter");
  Assert.AreEqual("Sco<tag>oter", scooter.FirstName);
  Assert.AreEqual("Sco&lt;tag&gt;oter", scooter.FirstNameHtml());
}

基本上,扩展调用添加到每个猫类型,后缀"Html",所以 FirstName 属性可以访问 HTML 安全版本中,通过调用 FirstNameHtml 方法相反的新方法。

这是可以做到完全在运行时为任何双子星座-­继承类型在系统中。

持久性和更多

双子座并不打算和一群的动态解析对象代替 C# 环境的整体 — — 远非如此。 双子座在其首页的用法,在橡树 MVC 框架,用于添加到模型 (尤其) 类的持久性和其他有用的行为,并无拥塞,用户代码或分部类中添加验证。 然而,即使在橡木,双子座表示一些功能强大的设计力学,其中有些读者可能还记得 8 系列的一部分我多模式化的.NET 从一段时间了 (msdn.microsoft.com/magazine/hh205754)。

所以说的橡木,那下一次是在水龙头上,看看如何所有这个动态的东西在真实世界的情况下发挥作用。

祝您工作愉快!

Ted Neward  是 Neward & Associates LLC 的负责人。他已写了 100 多个文章和创作和合著十多本书,包括"专业 F # 2.0"(Wrox 2010)。他是 F# 领域最优秀的专家之一和著名的 Java 专家,在全球 Java 和 .NET 会议上演讲。他定期担任顾问和导师,如果您有兴趣请他参与您的团队工作,请通过 ted@tedneward.comTed.Neward@neudesic.com 与他联系。他的博客网址是 blogs.tedneward.com,您也可以通过 Twitter 地址 twitter.com/tedneward 关注他。

衷心感谢以下技术专家对本文的审阅:埃米尔 Rajan (改善企业)
埃米尔 Rajan 是改善企业的主要顾问。 他是社会发展的一个积极成员,具有专业知识,在 ASP.NET MVC 中,HTML5,其余结构、 红宝石、 JavaScript/CoffeeScript、 NodeJS,iOS/ObjectiveC 和 F #。 Rajan 是真正通晓多种语言软件坚定不移的激情。 他是在 Twitter 上 @amirrajan 和在网页上 github.com/amirrajanamirrajan.netimprovingenterprises.com