65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 4.0 动态(dynamic)的冒险之旅 - ExpandoObject、ElasticObject 和 10 分钟构建 Twitter 客户端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (61投票s)

2010年3月3日

CPOL

7分钟阅读

viewsIcon

213112

downloadIcon

4464

探讨 C# 4.0 中的动态特性,以及你可以用它们做的一些很酷的事情。

引言

关于 .NET 4.0 中新的(好吧,也不算太新了)动态特性,已经有很多讨论和实践。至少,有些人认为动态是纯粹的邪恶。也许,也许不是。

请暂时将这些争论放在一边。本文的目的是“仅仅”探讨一些你可以用动态特性做到的很酷的事情。是否使用它们,则由你自行决定。结束。相关的源代码是用 VS2010 beta 版本编写的,应该也能在 RC 版本中使用。

并非全然邪恶 - 存在有用的场景

在很多场景下,动态特性可以真正为你简化工作。例如,假设我们有一个基于反射的场景,你需要从外部程序集或其他地方加载一个类型来调用一个成员。这里有一个简单的示例。你将看到如何使用动态作为反射的一种更简单的替代方案。这在处理 COM 互操作等场景时更为明显。稍后我们将看到更多有趣的用法。

幕后花絮 - 动态语言运行时(Dynamic Language Runtime)将在运行时处理方法分派。如果你想详细了解“动态”是如何被编译的,以及它在 IL 中是什么样子,请阅读这篇文章并查看其中的相关源代码。

事物变得更有趣 - DynamicObject 和 ExpandoObject

在 Ruby 等动态语言中,有一个有趣的特性叫做 method_missing。Method missing 是当你调用的方法未找到时,请求结束的地方。

在 .NET 4.0 中,现在你可以通过继承你的对象自 DynamicObject 类来引入相同的特性。这意味着,当你的对象被调用方法/访问属性时,你会得到通知。稍后我们将详细介绍。

此外,.NET Framework 4.0 在 System.Dynamic 命名空间中有一个很酷的新类 ExpandoObject。它的特别之处在于,它允许你在运行时动态地添加和删除成员来创建一个对象。

dynamic obj = new ExpandoObject();
obj.Value = 10;
var action = new Action<string>((line) => Console.WriteLine(line));
obj.WriteNow = action;
obj.WriteNow(obj.Value.ToString());

如果你运行上面的代码,你肯定(:))会在控制台中看到输出 10。

现在,让我们看看如何创建我们自己的 ExpandoObject 的最小实现。我们称之为 MyExpando。是的,我们将继承我们的 MyExpando 类自 DynamicObject 类。DynamicObject 类有一些很酷的方法供你重写。目前,我们只对这些成员感兴趣

  • TrySetMember - 提供设置成员的实现。
  • TryGetMember - 提供获取成员的实现。
  • TryInvokeMember - 提供调用成员的实现。

所以,这是 MyExpando 的实现,它是 .NET 4.0 的 ExpandoObject 类的一个最小实现,用于揭示那个“东西”是如何真正工作的。

public class MyExpando : DynamicObject
{

    private Dictionary < string, object > _members = 
            new Dictionary < string, object > ();

    /// <summary>
    /// When a new property is set, 
    /// add the property name and value to the dictionary
    /// </summary>     
    public override bool TrySetMember
         (SetMemberBinder binder, object value)
    {
        if (!_members.ContainsKey(binder.Name))
            _members.Add(binder.Name, value);
        else
            _members[binder.Name] = value;

        return true;
    }

    /// <summary>
    /// When user accesses something, return the value if we have it
    /// </summary>      
    public override bool TryGetMember
           (GetMemberBinder binder, out object result)
    {
        if (_members.ContainsKey(binder.Name))
        {
            result = _members[binder.Name];
            return true;
        }
        else
        {
            return base.TryGetMember(binder, out result);
        }
    }

    /// <summary>
    /// If a property value is a delegate, invoke it
    /// </summary>     
    public override bool TryInvokeMember
       (InvokeMemberBinder binder, object[] args, out object result)
    {
        if (_members.ContainsKey(binder.Name) 
                  && _members[binder.Name] is Delegate)
        {
            result = (_members[binder.Name] as Delegate).DynamicInvoke(args);
            return true;
        }
        else
        {
            return base.TryInvokeMember(binder, args, out result);
        }
    }


    /// <summary>
    /// Return all dynamic member names
    /// </summary>
    /// <returns>
    public override IEnumerable<string> GetDynamicMemberNames()
    {
        return _members.Keys;
    }
}

每当发生属性设置操作时,运行时都会调用 TrySetMember,在那里,我们将属性名和值推送到一个字典中。同样,每当发生获取操作(TryGetMember)时,我们只需在字典中查找并返回可用的值对象。

另外,当调用方法时(TryInvokeMember),如果值类型是委托,我们只需通过传递我们收到的参数来调用它。很简单,不是吗?好了,就是这样。现在,让我们用我们的 MyExpando 对象来尝试上面的 ExpandoObject 场景。

dynamic obj = new MyExpando();
obj.Value = 10;
var action = new Action<string>((line) => Console.WriteLine(line));
obj.WriteNow = action;
obj.WriteNow(obj.Value.ToString());

事物变得*非常*有趣 - ElasticObject

遵循这些概念,我最近实现了一个“ElasticObject”类,它使用了动态特性。首先,让我们看看 ElasticObject 实现到底有什么能力。之后,我们可能会触及实现细节。首先,这里有一些你可以使用 ElasticObject 的场景

  • 一种更简单、更流畅的方式来处理数据格式 - 例如 XML 和 JSON。目前,我们对 XML 有一些支持。
  • 更简洁的代码,尽管它是鸭子类型的。
  • 一种分层的方式来维护松散类型的数据。

这是什么意思?看这里。

1 - 你可以像这样自动创建多级动态对象

ElasticObject 支持创建分层动态数据结构。例如,考虑这段代码

image

2 - 然后,你可以简单地使用“>”转换运算符将其转换为 XML

ElasticObject 实现中,有几个运算符具有特殊含义。例如,“>”运算符可用于将 ElasticObject 实例转换为另一种数据格式。现在,你需要这样做才能从上面的“store”对象生成 XElement 表示。

image

如果你查看生成的 XML,你会看到这个

image

3 - 你可以在 XML 和 ElasticObject 之间双向转换

在上面的例子中,我们看到了如何从“动态”ElasticObject 转换为 XML。事实上,你可以直接将 XML 转换为“动态”ElasticObject,通过使用 DynamicExtensions 类中的 XElement.ToElastic() 扩展方法。请参阅此单元测试以获得更好的了解

image

如果你想了解更多关于 ElasticObject 的信息,我在我的博客上有一篇更详细的介绍性帖子在这里。另外,你可以从上面的下载链接下载完整的源代码 - 同时,我目前在这篇文章中不讨论 ElasticObject 的实际实现 - 你可以查看代码和相关的单元测试。

一个 10 分钟的 Twitter 搜索应用

现在,让我们看看 ElasticObject 有什么能力。这是一个快速的、用 ASP.NET MVC 构建的 10 分钟 Twitter 搜索客户端。好的,首先,看看下面的应用程序截图。它是一个简单的 ASP.NET MVC Twitter 搜索应用,可以显示你搜索内容的最新推文。

image

控制器

看看我的控制器。我刚刚在 VS2010 中创建了一个 ASP.NET MVC 2 应用程序,并添加了对 Microsoft.CSharp.dll 和 AmazedSaint.Elastic 项目(包含在上面的下载中)的引用。AmazedSaint.Elastic 命名空间包含了我之前提到的ElasticObject 实现。基本上,ElasticObject 可以用作一个流畅的动态包装器,用于处理 XML 等数据格式,并且有望成为 C# 4.0 ExpandoObject 的更智能的表亲。

所以,让我们回到控制器。VS 已经为我创建了模板,我修改了 HomeController 中的 Index 操作,如下所示:

image

你可能注意到我在 Index 操作中添加了一个“query”参数。另外,我省略了事件处理程序,所以如果你的 Twitter 连接中断,应用程序会崩溃 :)。我们正在解析 ATOM 格式(它是 XML)到一个 XElement,然后从中创建一个 ElasticObject。如果你仍然在想 ToElastic() 方法到底是什么,你应该看看这个。否则,如果你能想象,请继续阅读。

View

视图也非常简洁。这里有两个有趣的要点。首先,页面继承自 ViewPage<dynamic>,这意味着从控制器返回的视图数据在此作为动态对象传递。其余的都很简单,不是吗?我们只是遍历模型中名为“entry”的所有元素(请参阅 Twitter API 调用结果:http://search.twitter.com/search.atom?lang=en&q=aspnetmvc),来打印图片、作者姓名、推文文本等。

image

根据 ElasticObject 的约定,“_All”放在元素名称后面会返回所有同名元素,如上所示。另外,在元素前添加波浪号“~”字符会返回该元素的内容。如果访问的项目是属性,则不需要(请参阅我们如何访问 href)。再次,如果还需要清晰,请将我们在视图中使用的元素与 Twitter API 返回的 ATOM 数据进行比较。这只是一个演示应用程序,用于展示 ElasticObject - 我并不提倡鸭子类型 :)。

哦,是的,你可以从上面的下载链接下载此演示应用程序。

历史

  • 星期二,3 月 29 日 - 第一个版本。
© . All rights reserved.