C#动静结合编程中的Duck Typing方法

开发 后端
本文作者Todd具备多年.NET编程经验,今天将讲到的是Duck Typing方法。也就是将委托的思想应用于对象,相信这一方法能给大家在实际开发过程中带来帮助。

引言

C#是静态类型语言,要使用类型必须引用该类型的定义。因此,从软件组织角度会发生组件间的引用依赖关系。常见的引用依赖关系有两种模式:

a. 正向依赖:组件A用到了组件B中定义的类T,组件A直接引用组件B,依赖关系是“组件A -> 组件B”。

b. 反向依赖:组件A通过接口I定义功能规范,针对抽象编程;组件B反过来引用组件A,并定义类T实现接口I;由另一组件C将I与T粘合起来,依赖关系是“组件A <- 组件B”。这就是著名的IoC方式。

简单说来,IoC是“谁制定规范,谁就拥有控制权;谁执行规范,谁就被控制”。如果规范借助于C#的静态类型检查,比如接口或抽象类,那么规范就表现出较强的语法约束性,使得组件A的编写比较独立,而组件B则受制与组件A。

本系列的第一篇举了一个基于接口的IoC例子,我们看到当需要采用第三方组件时,为了适用接口的静态类型约束,不得不增加一个adapter去实现接口并包装对第三方组件的调用。这表现出基于接口的IoC在粘合规范与实现时不太灵活。

但是,规范和类型约束没有必然的联系。在基于委托的IoC例子中,我们不需要任何的adapter,就能轻松的粘合规范与实现,表现出较强的灵活性。这就是通过委托定义规范,不会造成组件B对组件A的依赖,组件A和组件B的实现都显得比较独立。

实际上,我们还可以有比委托更灵活的规范表达方式,比如:通过HTTP + XML来表达规范,这样甚至是语言无关的,完全可能组件A由C#编写,组件B由Java编写。

上面列举的3种规范定义方式:基于接口、基于委托、基于HTTP + XML分别代表了由约束到协议,由严格到灵活的3种风格。当然,还有更多的方式,但这里只列举这三种作为代表。动与静之间需要把握一个分寸,接口过于死板;而HTTP + XML的方式则完全是基于运行时协议的,需要自己做很多检查工作;委托的好处在于既消除了组件A、B的依赖关系,又能享受IDE智能提示和编译器检查(签名检查)等好处。因此,委托是把动与静结合得恰到好处的中庸之道。

Duck Typing

但可惜委托还无法覆盖接口或类的所有功能,有朋友提到“接口是对象功能的抽象,而委托是方法功能的抽象”就是这个意思。那么我们自然会想,有没有一种方式,能将委托的思想应用于对象呢?有!它就是:duck typing。前文已经谈到,duck typing关注“对象能做什么”或者说“如何使用对象”,对象继承自什么类,或者实现什么接口并不重要。duck typing的本意为“如果一只动物,走起来像鸭子,叫起来像鸭子,我就可以把它当作鸭子”。与继承性多态相对应,duck typing可以实现非继承多态。按duck typing的本意,纯正的duck typing看起来应该是这个样子:

static void Main(string[] args) 
{
    object person= new Person();
    IPerson duck= Duck.Create(person);//创建鸭子对象
    Console.WriteLine(duck.Name + " will be " + (duck.Age + 1) + "next year");
    duck.Play("basketball");
    Console.WriteLine(duck.Mother);//为null
    //duck无法调用duck.Sing()
}
interface IPerson
{
    string Name { get; }
    int Age { get; }
    string Mother { get; }
    void Play(string ball);
}
class Person
{
    public string Name { get { return "Todd"; } }
    public int Age { get { return 26; } }
    public void Play(string ball) { Console.WriteLine("Play " + ball); }
    public void Sing(string song) { Console.WriteLine("Sing " + song");}
}

上面的例子中,虽然person对象没有实现IPerson接口,我们一样可以通过Duck.Create(person)创建鸭子对象调用person的属性和方法。这种把接口和对象粘合的方式与委托和方法的粘合方式非常接近,真正达到了我们所谓把委托思想应用于对象的想法。

C#中要实现Duck.Create的功能,可以通过Emit动态创建实现T接口的代理类,在代理类中拦截方法调用,并将方法调用转换成target对象上的反射调用。Castle开源项目的DynamicProxy是一个很好用的工具,在它的帮助下很容易实现代理类的创建和方法调用的拦截。

动态类型

事实上,duck typing是动态类型概念的一种。C#4.0已经通过dynamic关键字来实现动态类型,让我们先来看看下面的示例:

string json = @"{ ""FirstName"": ""John"", ""LastName"": ""Smith"", ""Age"": 21 }"; 
dynamic person = CreateFromJson(json);
Console.WriteLine("{0} will be {1} next year", person.FirstName, person.Age + 1);
Console.WriteLine(person.ToJson());
person.Play("basketball");
string firstName = person.FirstName;
int age = person.Age;
Func toJson = person.ToJson>;
Action play = person.Play>;

通过dynamic关键字,我们不需要在编译时为person对象指定类型,编译器不会进行类型检查,而是将对象的属性访问和方法调用转换为反射调用,所以,只要对象的运行时类型能通过反射找到匹配的属性或方法即可。

上面的例子通过json创建了一个dynamic对象,就像javascript中操作json一样方便。在运行 时,person.FirstName和person.Age能通过反射正确地进行属性访问,person.ToJson()也可以正确地执行,但 person.Play( "basketball")由于运行时类型不存在该方法而抛出异常。

C#4.0的味道如何?很爽吗?不过,说实在的,我觉得有点儿不太舒服了!仔细想想,它像接口,像委托,还是更像HTTP + XML? 对于dynamic对象,编译器不进行对象类型检查,不进行属性类型检查,也不进行方法签名检查。很明显,它像HTTP+XML,完全基于运行时协议,没有一点儿静态的东西。如果类比委托的话,更理想的方式应该是,不进行对象类型检查,但进行属性类型和方法签名检查,就像下面这样:

string json = @"{ ""FirstName"": ""John"", ""LastName"": ""Smith"", ""Age"": 21 }";
dynamic person = CreateFromJson(json);
Console.WriteLine("{0} will be {1} next year", person.FirstName, person.Age + 1);
Console.WriteLine(person.ToJson());
person.Play("basketball");//不存在的方法,可以通过编译,但会抛出运行时异常

这样,除了属性和方法的名称是动态的外,属性的类型和方法的签名都是静态的,把运行时错误的可能降到最低,同时享受静态检查的好处。其实,沿着这个思路,我们大可不必等着C#4.0的dynamic才开始动态类型,在C#2.0时代也可以这样:

object jsonObj = CreateFromJson(@"{ ""FirstName"": ""John"", 
""LastName"": ""Smith"", ""Age"": 21 }");
Dynamic person = new Dynamic(jsonObject);
string firstName = person.Property("FirstName");
int age = person.Age("Age");
Func toJson = person.Method>("ToJson");
Action play = person.Method>("Play");

看到这里,相信您一定明白该如何实现Dynamic类了吧?如果觉得有用,就自己尝试实现一下吧!

博文链接:http://www.cnblogs.com/weidagang2046/archive/2009/03/26/1421943.html

【编辑推荐】

  1. C#实用基础教程
  2. 详解C# 4.0中必选参数与可选参数混合的问题
  3. 浅析C#3.0编码习惯与命名规则
责任编辑:彭凡 来源: 博客园
相关推荐

2009-03-12 09:05:18

接口C#.NET

2009-02-20 09:50:29

C#方法重载编程

2009-04-10 09:55:44

C#反射.NET

2009-01-16 09:58:07

C#编程C#内存管理垃圾收集

2009-08-03 13:23:04

C#编程组件-事件-委托

2024-10-21 16:59:37

C#编程多线程

2009-03-10 13:59:41

C#套接字编程

2009-07-20 09:53:43

Java混合编程

2012-04-28 15:28:21

JNI混合编程Java

2024-11-20 17:28:00

C#CPU代码

2012-03-20 11:37:24

JavaJNI

2011-07-01 14:55:28

Qt QML C++

2009-08-21 16:35:08

使用C#结合ADO.N

2020-02-28 09:00:00

ObjectC#编程语言

2009-08-26 10:34:15

C#类型C#变量

2009-08-24 11:02:52

C#接口映射

2009-09-08 16:22:27

c# listBox

2009-08-24 09:55:26

C#接口转换

2009-08-21 10:17:14

C#异步网络编程

2009-04-09 09:19:25

C#规则表达式.NET
点赞
收藏

51CTO技术栈公众号