代码改变世界

Effective C# 学习笔记(三十八)理解Dynamic的得与失

2011-07-28 22:35  小郝(Kaibo Hao)  阅读(467)  评论(0编辑  收藏  举报

DynamicC#4.0新增的特性,其可以使我们完成一些在运行时的动态类型逻辑定义。但是其是一把双刃剑,在带来动态特性以应付多变处理需求的同时,也带来了更多需要校验处理的地方,不能在编译时发现程序的漏洞。而且在运行时由于对类型转换的相关处理,要比静态类型处理逻辑更费资源和时间。

 

如下代码展示了如何使一个加法逻辑应用于不同的类型对象。

public static dynamic Add(dynamic left, dynamic right)

{

return left + right;

}

该方法使用dynamic类型作为参数和返回值,程序会在运行时根据传入参数的类型不同,运行不同处理逻辑。如下代码所示:

dynamic answer = Add(5, 5);

answer = Add(5.5, 7.3);

answer = Add(5, 12.3);

dynamic label = Add("Here is ", "a label");
dynamic tomorrow = Add(DateTime.Now, TimeSpan.FromDays(1));

 

注意:这里的类型判断是在运行时进行的,所以在编译时你不能校验你的代码。并且在你需要将这些返回值被你的大多数C#代码调用时,你需要使用CastConversion操作进行转换,以保证其成为一个可被调用静态类型对象。若转换失败的话,就会产生新的问题。

 

你可以在运行时方法的参数或返回值为未知的情况下,使用dynamic类型。而若是在编译时就可以确定方法的参数和返回值类型的话,请使用lambdas表达式。如下代码所示:

 

public static TResult Add<T1, T2, TResult>(T1 left, T2 right,Func<T1, T2, TResult> AddMethod)

{

return AddMethod(left, right);

}

 

var lambdaAnswer = Add(5, 5, (a, b) => a + b);

var lambdaAnswer2 = Add(5.5, 7.3, (a, b) => a + b);

var lambdaAnswer3 = Add(5, 12.3, (a, b) => a + b);

var lambdaLabel = Add("Here is ", "a label",(a, b) => a + b);

dynamic tomorrow = Add(DateTime.Now, TimeSpan.FromDays(1));

var finalLabel = Add("something", 3,(a,b) => a + b.ToString());

 

上面的实现方式有些笨拙,由于处理对象参数的类型不同,每次使用Add方法都需要传入不同加法的处理逻辑。而上面的逻辑其实借鉴了Enumerable.Aggregate()方法的实现方式。

 

var accumulatedTotal = Enumerable.Aggregate(sequence, (a, b) => a + b);

 

若想进一步抽象该方法,你需要构建一个Expression TreeSytem.Linq.Expression类及其子类提供了构建Expression TreeAPIs。下面的代码展示了如何抽象上面的逻辑,以处理同类型的参数和返回值的Expression Tree

// Naive Implementation.

public static T AddExpression<T>(T left, T right)

{

//左操作数

ParameterExpression leftOperand = Expression.Parameter( typeof(T), "left");

//右操作数

ParameterExpression rightOperand = Expression.Parameter( typeof(T), "right");

//构建表达式体

BinaryExpression body = Expression.Add( leftOperand, rightOperand);

//创建表达式树

Expression<Func<T, T, T>> adder =Expression.Lambda<Func<T, T, T>>(body, leftOperand, rightOperand);

//将表达式树编译为代理

Func<T, T, T> theDelegate = adder.Compile();

//返回代理的执行结果

return theDelegate(left, right);

}

 

//使用该方法

int sum = AddExpression(5, 7);

 

上面的方法处理了处理参数和返回值都相同的情况,但是并没有解决不同的情况。下面的代码解决了这个问题。

// A little better.

public static TResult AddExpression<T1, T2, TResult>(T1 left, T2 right)

{

var leftOperand = Expression.Parameter(typeof(T1), "left");

var rightOperand = Expression.Parameter(typeof(T2), "right");

var body = Expression.Add(leftOperand, rightOperand);

var adder = Expression.Lambda<Func<T1, T2, TResult>>( body, leftOperand, rightOperand);

return adder.Compile()(left, right);

}

 

上面的代码好像是解决了不同参数,返回值的处理逻辑,但是其需要在调用时,明确指定T1T2TResult的类型。如下代码所示:

int sum2 = AddExpression<int, int, int>(5, 7);

DateTime nextWeek= AddExpression<DateTime, TimeSpan,DateTime>(DateTime.Now, TimeSpan.FromDays(7));

 

上面的方法好像解决了问题,其实却没有。对于不同类型对象的加法转换的类型转换处理方式并没有得到你代码控制,你可以这样来做:

// A fix for one problem causes another

public static TResult AddExpressionWithConversion

<T1, T2, TResult>(T1 left, T2 right)

{

//转换类型

var leftOperand = Expression.Parameter(typeof(T1),"left");

Expression convertedLeft = leftOperand;

if (typeof(T1) != typeof(TResult))

{

convertedLeft = Expression.Convert(leftOperand, typeof(TResult));

}

//转换类型

var rightOperand = Expression.Parameter(typeof(T2),"right");

Expression convertedRight = rightOperand;

if (typeof(T2) != typeof(TResult))

{

convertedRight = Expression.Convert(rightOperand, typeof(TResult));

}

var body = Expression.Add(convertedLeft, convertedRight);

var adder = Expression.Lambda<Func<T1, T2, TResult>>(body, leftOperand, rightOperand);

return adder.Compile()(left, right);

}

 

但上面的方法,又不能解决DateTime类型和TimeSpan类型对象相加的问题了。

 

如上各例,说明了要想用编译时的技术来解决运行时的动态的问题并不是一件易事,这也证明了Dynamic的好处。但也不是说Expression Tree就没有用武之地,在参数类型和返回值类型相同的情况下,其还是能体现编译时好处的。如下代码所示:

public static class BinaryOperators<T>

{

static Func<T, T, T> compiledExpression;

public static T Add(T left, T right)

{

//由于编译表达式很浪费资源,所以只在第一次使用时进行构建编译,以后直接调用代理

if (compiledExpression == null)

createFunc();

return compiledExpression(left, right);

}

private static void createFunc()

{

var leftOperand = Expression.Parameter(typeof(T), "left");

var rightOperand = Expression.Parameter(typeof(T), "right");

var body = Expression.Add(leftOperand, rightOperand);

var adder = Expression.Lambda<Func<T, T, T>>(body, leftOperand, rightOperand);

compiledExpression = adder.Compile();

}

}