分类: C/C++
2008-08-05 14:01:06
环境:C# (C# 2.0) Windows (Win2K, WinXP, Win2003), .NET
(.NET 2.0)
Win32, VS (VS2005)
简介:理解C#2.0的匿名方法的内部工作机制
目录:
1. 匿名方法基础
2. 匿名方法的静态数据成员的用法
3. 匿名方法的实例数据成员用法
4. 匿名方法的局部变量用法
5. 匿名方法的作用域和局部变量用法
6. 在循环控制结构内使用匿名方法的局部变量的用法
7. 总结
匿名方法允许我们定义委托对象可以接受的代码块。这个功能省去我们创建委托时想要传递给一个委托的小型代码块的一个额外的步骤。它也消除了类代码中小型方法的混乱。让我们看看:比方说,我们有一个字符串集合命名为MyCollection。这个类有一个方法:获得集合中满足用户提供的过滤准则的所有项,调用者决定在集合中的一个特殊项是否符合条件而被检索到,作为从此方法返回数组的一部分。
public class MyCollection { public delegate bool SelectItem(string sItem); public string[] GetFilteredItemArray(SelectItem itemFilter) { List我们可以用上面定义的类写如下所示的代码:sList = new List (); foreach(string sItem in m_sList) { if (itemFilter(sItem) == true) sList.Add(sItem); } return sList.ToArray(); } public List ItemList { get { return m_sList; } } private List m_sList = new List (); }
public class Program { public static void Main(string[] args) { MyCollection objMyCol = new MyCollection(); objMyCol.ItemList.Add("Aditya"); objMyCol.ItemList.Add("Tanu"); objMyCol.ItemList.Add("Manoj"); objMyCol.ItemList.Add("Ahan"); objMyCol.ItemList.Add("Hasi"); //获得集合中以字母’A‘开头的字符项数组 string[] AStrings = objMyCol.GetFilteredItemArray(FilterStringWithA); Console.WriteLine("----- Strings starting with letter ''A'' -----"); foreach(string s in AStrings) { Console.WriteLine(s); } //获得集合中以字母’T‘开头的字符项数组 string[] TStrings = objMyCol.GetFilteredItemArray(FilterStringWithT); Console.WriteLine("----- Strings starting with letter ''T'' -----"); foreach(string s in TStrings) { Console.WriteLine(s); } } public static bool FilterStringWithA(string sItem) { if (sItem[0] == ''A'') return true; else return false; } public static bool FilterStringWithT(string sItem) { if (sItem[0] == ''T'') return true; else return false; } }可以看出对于每个我们想要提供的简单过滤准则,我们应该定义一个方法(静态或实例的)。这很快就搞乱了类的代码。而用匿名方法,代码变得相当自然。下面是这个Program类用匿名方法重写后的:
public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { MyCollection objMyCol = new MyCollection(); objMyCol.ItemList.Add("Aditya"); objMyCol.ItemList.Add("Tanu"); objMyCol.ItemList.Add("Manoj"); objMyCol.ItemList.Add("Ahan"); objMyCol.ItemList.Add("Hasi"); //获得集合中以字母’A‘开头的字符项数组 string[] AStrings = objMyCol.GetFilteredItemArray(delegate(string sItem) { if (sItem[0] == ''A'') return true; else return false; }); Console.WriteLine("----- Strings starting with letter ''A'' -----"); foreach (string s in AStrings) { Console.WriteLine(s); } //获得集合中以字母’ T ‘开头的字符项数组 string[] TStrings = objMyCol.GetFilteredItemArray(delegate(string sItem) { if (sItem[0] == ''T'') return true; else return false; }); Console.WriteLine("----- Strings starting with letter ''T'' -----"); foreach (string s in TStrings) { Console.WriteLine(s); } } }正如上面示例中的所示,我们已能用内联代码块定义的过滤准则替代定义一个新的方法来代表每个过滤准则。老实说,用这种内联代码可能看起来自然并且避免了定义新方法,但是如果这个技术被用于更大的内联代码块,这时代码很快变得难于管理并可能导致代码重复。因此,使用方法与内联匿名方法都是委托/事件处理器的可选方案。
如果我们已经使用了用匿名方法的''Program'' 类的任何静态数据,C#编译器将仍然在''Program'' 类里创建一个静态方法来包装匿名方法。
public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { //实例数据成员测试 Program p = new Program(); for(int i=1;i<=5;i ) p.TestInstanceDataMembers(); } public void TestInstanceDataMembers() { MyDelegate d = delegate { Console.WriteLine("Count: {0}", m_iCount); }; d(); } public int m_iCount = 0; }我们定义了一个新的实例方法:TestInstanceDataMembers,在''Program''类中这个方法定义了一个匿名方法,匿名方法使用了实例数据成员:隶属''Program''类的m_iCount。当这个示例编译时,C#编译器将创建一个private实例方法来包装这个在TestInstanceDataMembers中定义的匿名方法。C#编译器必须创建一个实例方法因为该方法需要访问''Program''类的实例数据成员。下面是这个示例程序集''Program''类的ILDASM视图。在图的下部选中部分显示了由C#编译器默默添加到''Program''类的新的private实例方法。
到现在为止,我们对匿名方法如何工作以及内部如何实现有了一点基本的理解。从根本上说,C#创建了private方法来包装匿名方法。同时这些方法的签名与它们被分配到的委托相匹配。现在,让我们看看下面的代码:
public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { int iTemp = 100; MyDelegate dlg = delegate { Console.WriteLine(iTemp); }; dlg(); } }对于我们到现在为止对匿名方法已了解的内容来说,这段代码不应该编译。因为我们没有使用如何实例数据成员,C#编译器应该在''Program''类中创建一个private静态方法来包装这个匿名方法。但是新的方法如何访问局部变量呢?这让我们相信该代码将不能被编译。但是令人惊讶的是,C#编译器成功编译了这个代码而没有任何错误或报警。而且,当你执行这个示例时,在控制台屏幕上输出打印出iTemp变量的正确的值。现在让我们进入匿名方法的高级话题。一个匿名方法有封装在其方法体中使用了的环境变量的值的能力。这个封装应用于匿名方法被定义的方法中的所有局部变量。当C#编译器在一个匿名方法的方法体中识别出用到一个局部变量,它就会做如下事情: 1. 创建一个新的private类作为匿名方法被定义的类的一个内部类。
public class Program { private class InnerClass { private void InstanceMethod() { Console.WriteLine(iTemp); } public int iTemp; } public delegate void MyDelegate(); public static void Main(string[] args) { InnerClass localObject = new InnerClass(); localObject.iTemp = 100; MyDelegate dlg = new MyDelegate(localObject.InstanceMethod); dlg(); } }正如上面的伪代码所示,C#编译器为''Program''类生成了一个private内部类。在匿名方法中使用的局部变量作为新的已创建的内部类的一个实例数据成员而捕获。并且匿名方法本身被包装在内部类的实例方法中。最后,该实例方法在Main方法中作为一个委托处理器而使用。这样,当委托被调用时,对于在被封装入匿名方法中的局部变量将会有一个正确的值。下面图中选定的部分显示了由C#编译器默默添加到''Program'' 类的新的private内部类。 被用在匿名方法中的局部变量有着超出用到它们的外部常规方法的生命周期。这个技术,在其它语言中,就是大家都知道的closures。除去匿名方法提供的简单语法,closures是匿名方法提供给开发者的一个功能强大的技术。该技术允许委托处理器代码(匿名方法)访问在常规方法内部被定义的局部变量。这就允许out-of-band数据,除了委托参数之外还有数据将被传递到委托,以供在其方法执行时使用。没有这个技术,每个委托和其相应的处理器方法就不得不声明表示局部上下文数据的参数,随着时间的过去这(译注:指不断声明表示局部上下文数据的参数)将变得难于管理。
public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { MyDelegate dlg = null; int iTemp = 100; if (iTemp > 50) { int jTemp = 200; dlg = delegate { Console.WriteLine("iTemp: {0}, jTemp: {1}",iTemp,jTemp); }; } dlg(); } }当上面的代码被编译时,C#编译器在''Program''类中创建两个内部类。一个内部类包装局部变量iTemp作为一个public数据成员。第二个内部类包装在嵌套作用域中的局部变量,jTemp,作为一个public数据成员,同时在相同的嵌套作用域中包装匿名方法作为public实例方法。C#编译器为上面的代码生成下面的伪代码:
public class Program { //包装来自外部作用域的局部变量''iTemp''的类 private class InnerClassScope1 { public int iTemp; } //包装来自内部作用域和匿名方法的局部变量的类 private class InnerClassScope2 { public void InstanceMethod() { Console.WriteLine("iTemp: {0}, jTemp: {1}", localObjectScope1.iTemp, jTemp); } public InnerClassScope1 localObjectScope1; public int jTemp; } public delegate void MyDelegate(); public static void Main(string[] args) { MyDelegate dlg = null; InnerClassScope1 localObject1 = new InnerClassScope1(); localObject1.iTemp = 100; if (localObject1.iTemp > 50) { InnerClassScope2 localObject2 = new InnerClassScope2(); localObject2.localObjectScope1 = localObject1; localObject2.jTemp = 200; dlg = new MyDelegate(localObject2.InstanceMethod); } dlg(); } }正如上面的代码所示,包装匿名方法的内部类将拥有所有代表外部作用域局部变量的对象,这些变量被用在匿名方法中,像public数据成员。下图显示了C#默默创建的内部类的ILDASM视图:
public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { MyDelegate d = null; for (int i = 1; i <= 5; i ) { MyDelegate tempD = delegate { Console.WriteLine(i); }; d = tempD; } d(); } }上面的代码运行时将会有什么输出呢?我们的意图是捕获在我们的匿名方法中的循环计数变量''i''并显示之。我们预期的输出应该如下所示: 1
但是如果你运行上面的代码,输出将是如下所示:
6public class Program { public delegate void MyDelegate(); public static void Main(string[] args) { MyDelegate d = null; for (int i = 1; i <= 5; i ) { int k = i; MyDelegate tempD = delegate { Console.WriteLine(k); }; d = tempD; } d(); } }在你运行上面的代码示例时,将会获得预期的输出,也就是: 1