在我们程序中,经常有这样一些需求: 1. 需要一个临时方法,这个方法只会使用一次,或者使用的很少。 2. 这个方法的方法体很短,以至于比方法声明都短,写起来实在没劲( 我将其称之为“一句话方法”) 。 没办法,这样的方法写起来真是吃力不讨好,比如一些按钮事件处理中,有些按钮点击就是弹出一个对话框,或者调用一下别的什么方法。比如下面的代码:
this.btnRefresh.Click +=
new
System.EventHandler(this.btnRefresh_Click);
这个”Refresh”按钮就是做一下调用一下BindData()数据绑定的方法,为此我们不得不写一个新方法。好了,C# 2.0
为我们提供了匿名方法:private void btnRefresh_Click( object sender, EventArgs e) { BindData(); }
this.btnRefresh.Click += delegate(
object
sender, EventArgs e) { BindData(); };
没劲的代码没了。想知道这种写法的幕后黑手么?其实编译器还是在我们的后面干了一件龌龊的事情:它为我们产生了一个新的方法,它只是表面上为我们节省了代码。
privatevoid<Test>b__0(
object
sender, EventArgs e)
看看这个编译器产生的方法的名称:{ this.BindData(); } <Test>b_0 , Test 是这个匿名方法所放置的地方 ( 因为这个按钮的时间我是放在一个 Test 方法里的 ) 还有一点需要注意的是 , 如果这个匿名方法是在实例方法里使用 , 那么编译器为我们生成的幕后方法也是实例方法 , 否则就是静态方法了 . 是不是觉得匿名方法这东西很不错 , 减少了很多代码阿 , 但是匿名方法的使用还并不人性化 , 什么是人性化呢 ? 比如你可以用自然的语言将程序代码读出来 , 这样才算人性化了 . 在 .net 2.0 中 System.Collections.Generic 命名空间下 List<T> 里有一些新增的方法。比如 Find ,如果使用匿名方法我们如何调用呢:
books.Find(delegate(Book book){return book.Price <
50
;});
代码是很简单,但是却无法朗读出来,来看看
Lambda
表达式的写法:books.Find(book=>book.Price<50); 这个 Lambda 表达式就可以这样阅读出来了:给你一本书,如果它的价格小于 50 则返回 true 。 好了,那我们就走进 Lambda 表达式吧: ![]() 将使用了Lambda表达式的程序集反编译后,我们发现,它实际上和匿名方法没有什么不同。Lambda的输入参数就对应着delegate括号里面的参数,由于Lambda表达式可以推断参数的类型,所以这里的参数无需声明。Lambda操作符读作”Goes to”,它后面紧跟着表达式或者是语句块(这点和匿名方法也不同,匿名方法只能使用语句块而不能使用表达式),下面我就用实例来说明一下有那些类型的Lambda表达式: () => 1 delegate(){return 1;} 对于Lambda表达式来说她的用法就是如此,但是在Lambda背后却有很多的故事和玄机。用Lambda表达式可以构建表达式树,而表达式树对于Linq来说就像树根对于树一样重要。在这里就不讨论表达式树的问题了,这个东西也不是三言两语能够说清楚的,等待时机成熟的时候我们再来进一步讨论。 Lambda表达式更多阅读 Lambda实际上源远流长,我们现在使用的机器都是冯-诺依曼体系的,属于图灵机,在那之前还有一种称作λ演算的理论,但是图灵机由于先被实现出来,所以大行其道,λ演算后来成就了函数式编程语言特别是Lisp,在函数式编程语言里函数是第一等元素,函数的参数,函数的返回值都是函数,程序没有变量,函数嵌套函数。而且函数式编程语言一直存在于象牙塔中,所以在工业界并没有得到通用,不过近年来工业界比较喜欢“复古”风格,所以函数式编程语言也慢慢的走上了历史的舞台。函数式编程能解决一些命令式编程难以解决的问题(或者解决起来非常麻烦)。C#要做到函数风格编程怎么办?靠原来的方法定义的方式肯定是不可行的,2.0的匿名方法从某种程序上来说解决了这个问题,但还是不够,3.0里的Lambda终于很好的解决了,一个Lambda就是一个delegate,一个delegate指向一个方法,现在我们使用Lambda也能简单的将方法作为参数传递了,还可以层层嵌套,都是很简单的事情了。 匿名方法的典型应用。
您是否熟悉上面这段逻辑说明?如果您的应用中大量使用了缓存,则上面这段逻辑很可能会出现许多次。例如:
CacheManager cacheManager = new CacheManager();
public List<User> GetFriends(int userId)
{
string cacheKey = "friends_of_user_" + userId;
object objResult = cacheManager.Get(cacheKey);
if (objResult != null) return (List<User>)objResult;
List<User> result = new UserService().GetFriends(userId);
cacheManager.Set(cacheKey, result);
return result;
}
这段逻辑似乎比较简单,不过在实际应用中,从数据源中获取数据可能不是简单地调用一个方法,而是需要多个类之间的协作,事务控制等等,而缓存的读写可能也会比上面的示例来的复杂。因此,一个可读性高的做法是提供三个独立的方法(读取缓存,读取数据源,写入缓存),使得一个拥有缓存的方法只需要简单地实现上面所提到的读写逻辑即可。 正如文章开头所说,如果您的应用中大量使用了缓存,则上面这段逻辑很可能会出现许多次。在一定程度上这种重复也是多余的,违背了DRY原则。因此我们设法提供一个基类,把这段缓存读写逻辑封装起来:
public abstract class CacheReader<T>
{
/// <summary>从缓存中获取数据</summary>
/// <param name="data">从缓存中取得的数据</param>
/// <returns>从缓存中成功取得数据则返回true,反之则false</returns>
public abstract bool GetFromCache(out T data);
/// <summary>从数据源获取数据</summary>
/// <returns>从数据源取得的对象</returns>
public abstract T ReadFromSource();
/// <summary>将数据写入缓存</summary>
/// <param name="data">将要写入缓存的数据</param>
public abstract void SetToCache(T data);
public T Read()
{
T data;
if (this.GetFromCache(out data)) return data;
data = this.ReadFromSource();
this.SetToCache(data);
return data;
}
}
于是我们将这段缓存读写逻辑集中到了CacheReader类的Read方法中。而对于每个缓存读写操作,我们只要实现一个CacheReader类的子类,提供三个抽象方法的具体实现即可。如下: private class GetFriendCacheReader : CacheReader<List<User>>
{
private int m_userId;
private string m_cacheKey;
private CacheManager m_cacheManager;
public GetFriendCacheReader(int userId, CacheManager cacheManager)
{
this.m_userId = userId;
this.m_cacheKey = "friends_of_user_" + userId;
this.m_cacheManager = cacheManager;
}
public override bool GetFromCache(out List<User> data)
{
object objData = this.m_cacheManager.Get(this.m_cacheKey);
if (objData == null)
{
data = null;
return false;
}
data = (List<User>)objData;
return true;
}
public override List<User> ReadFromSource()
{
return new UserService().GetFriends(this.m_userId);
}
public override void SetToCache(List<User> data)
{
this.m_cacheManager.Set(this.m_cacheKey, data);
}
}
于是我们的GetFriends方法就可以修改成如下模样: public List<User> GetFriends(int userId)
{
return new GetFriendCacheReader(userId, cacheManager).Read();
}
典型的“模板方法(Template Method)”模式的应用,真是……优雅?这是明显的“矫枉过正”!一个GetFriends方法需要开发一个类,而应用中少说也该有几十上百个这样的方法吧?于是乎,我们吭哧吭哧地开发了几十上百个CacheReader的子类,每个子类需要把所有用到的对象和数据封装进去,并且实现三个抽象方法——OMG,真可谓“OOP”到了极致! 还好,我们可以使用匿名方法。为此,我们写一个Helper方法: public static class CacheHelper
{
public delegate bool CacheGetter<TData>(out TData data);
public static TData Get<TData>(
CacheGetter<TData> cacheGetter,
Func<TData> sourceGetter,
Action<TData> cacheSetter)
{
TData data;
if (cacheGetter(out data))
{
return data;
}
data = sourceGetter();
cacheSetter(data);
return data;
}
}
委托是个好东西,可以作为方法的参数使用,而匿名方法的存在让这种方式变得尤其有用。例如,我们现在就可以: public List<User> GetFriends(int userId)
{
string cacheKey = "friends_of_user_" + userId;
return CacheHelper.Get(
delegate(out List<User> data) // cache getter
{
object objData = cacheManager.Get(cacheKey);
data = (objData == null) ? null : (List<User>)objData;
return objData != null;
},
() => // source getter
{
return new UserService().GetFriends(userId);
},
(data) => // cache setter
{
cacheManager.Set(cacheKey, data);
});
}
看上去是不是有点古怪?其实习惯了就好。这种做法有好处还不少:
在出现匿名方法之后,这种将委托作为参数传入方法的做法其实已经非常普遍了。例如在微软推出的并行库中就能使用同样的调用方式: void ParallelCalculate()
{
double result = 0;
object syncObj = new object();
List<int> list = GetIntList();
Parallel.For<double>(
0,
list.Count,
() => 0,
(index, ps) =>
{
int value = list[index];
for (int n = 0; n < 100; n++)
{
ps.ThreadLocalState += Math.Sqrt(value) * Math.Sin(value);
}
},
(threadResult) =>
{
lock (syncObj)
{
result += threadResult;
}
});
Console.WriteLine("result = " + result);
}
您接受这种做法了吗? 您善于使用匿名函数吗? |