【整理】C#中的扩展方法(Method Extension)

[原创] 作者:项目部 王辰龙

最近一直在微软中国做Windows 8 Store App的技术支持,总会遇到各种各样关于开发方面的问题,本文产生的契机来源于客户开发过程中想要在现有的一些类型中扩展一些原本没有的方法。

1. 问题描述

用户正在开发自己的Store App, 使用的开发方式为XAML+C#的模式,在开发的过程中使用Gridview控件承载内容,一部分内容是固定布局写好,另一部分内容的产生方式由DateTemplate决定。大致的代码如下

<GridView>

<ScrollViewer 

X:name = “scroll”

Loaded = “scrollLoaded”>

<StackPanel>

<TextBlock x:Name="restaurantDescription" 

Text="{Binding SkyDriveData.Groups.Description}"/>

</StackPanel>

</ScrollViewer>

<my:VaribleSizedGridView x:Name="itemGridView"

Loaded="itemGridViewLoaded">

<GridView.GroupStyle>

<GroupStyle>

<GroupStyle.HeaderTemplate>

<DataTemplate>

<Viewbox>

<Grid Margin="50,137,0,6">

<TextBlock x:Name="textBlock" 

Text="{Binding Title}"/>

.

.

    上面的代码主要定义了两个控件,一个是固定布局的,名为“restaurantDescription”的TextBlock控件,以及另一个由DataTemplate产生的,名为”textblock”的TextBlock控件。现在在开发过程中,由于某种需求,需要去获取这两个控件对象本身。

    private void ScrollVieweLoaded(object sender, RoutedEventArgs e)

{

            var textBlock =   scroll.FindName("restaurantDescription") as TextBlock;

            if(textBlock != null)

            {

 

            } 

}

实际操作了一下,运行结果如下:

 

关于第一个控件,通过FindName方法成功获得了对象。

对于第二个控件。并没有在容器的Loaded事件中获取到对象。

2. 问题分析

仔细分析一下,额,好吧,当时根本分析不出个子曰来,只好上MSDN去查了。还果真有相关的信息。关于XAML命名范围方面的。

http://msdn.microsoft.com/zh-cn/library/ms746659.aspx

其中的一段话非常引人注意:

“Styles and templates in WPF provide the ability to reuse and reapply content in a straightforward way. However, styles and templates might also include elements with XAML names defined at the template level. That same template might be used multiple times in a page. For this reason, styles and templates both define their own XAML namescopes, independent of whatever location in an object tree where the style or template is applied.”

必应的机器翻译太难懂所以直接上原文,大意是为了避免由于使用模板或样式而造成XAML控件在名称空间中出现名称冲突。所以每个用模板产生的控件都保留一个自己的名称空间。这也就解释了为什么通过FindName方法无法获得第二个控件。

原文给出了一个解决方案,原文如下

“Because of the separate XAML namescopes, finding named elements in a template is more challenging than finding a non-templated named element in a page. You first need to determine the applied template, by getting the Template property value of the control where the template is applied. Then, you call the template version of FindName, passing the control where the template was applied as the second parameter.

If you are a control author and you are generating a convention where a particular named element in an applied template is the target for a behavior that is defined by the control itself, you can use the GetTemplateChild method from your control implementation code. The GetTemplateChild method is protected, so only the control author has access to it.”

上文提到,如果要获取由模板产生的控件方法有两种,一种是获取到产生控件的模板类型,并使用DataTemplate类型上的FindeName方法。另一种是使用一个叫做GetTemplateChild的方法,但是这种方法只有在定义控件的时候可用。

好的,问题来了,刚才那篇文章是针对WPF的(资料搜集的时候确实没有留意)。对于全新的.Net for Windows Store, DataTemplate类型中没有这个方法。

思路断了,看来只能找其他的解决方法。这次要查对方式,不能再错查成WPF的了。最后在Windows Store App的应用体系中查到一篇,介绍一个叫做VisualTreeHelper的类,提供可用于在可视化树中遍历对象关系(以及子对象或父对象轴)的实用工具方法。同样的,我也找到了一篇关于使用这个VisualTreeHelper类的实用文章。

http://www.wiredprairie.us/blog/index.php/archives/date/2012/09

原文提到的不能找到控件的原因是FindName方法不是递归的,但根据最先读过的文章,这点我不太同意。

文章用了一种我没见过的方法来实现通过VisualTreeHelper找到控件的方式。先给出代码。

public static class FrameworkElementExtensions

{

    public static FrameworkElement FindDescendantByName(this FrameworkElement element, string name)

    {

        if (element == null || string.IsNullOrWhiteSpace(name)) { return null; }

        if (name.Equals(element.Name, StringComparison.OrdinalIgnoreCase))

        {

            return element;

        }

        var childCount = VisualTreeHelper.GetChildrenCount(element);

        for (int i = 0; i < childCount; i++)

        {

            var result = (VisualTreeHelper.GetChild(element, i) as FrameworkElement).FindDescendantByName(name);

            if (result != null) { return result; }

        }

        return null;

    }

}

这个方法的逻辑还是很简单的,就是一个递归查找的过程,但是参数列表的第一个参数自带的那个this方法非常让我迷惑。只能继续去查。

还是在MSDN,查到了这种写法的目的,是一种叫做扩展方法的编程方法。

http://msdn.microsoft.com/zh-cn/library/vstudio/bb383977(v=vs.110).aspx

关于扩展方法,原文是这样描述的:

“扩展方法使您能够向现有类型添加方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。 扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。 对于用 C# 和 Visual Basic 编写的客户端代码,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。

关于实现扩展方法,只需要遵从以下的标准:

“扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀。 仅当您使用 using 指令将命名空间显式导入到源代码中之后,扩展方法才位于范围中。“

这样看的话,刚才找到的那段代码就明了了,现在在项目中实现一下试试。

 

创建静态方法。

 

引入相应的命名空间。

 

这次成功的找到了第二个控件对象。

虽然这次能够成功的获取了对象,但是MSDN也同时说明,扩展方法可能会对别的程序员在阅读代码的过程中带来疑惑,所以对现有类型进行方法扩展的最佳实践依然是通过继承的方式来做。

3.总结

在本次技术文章的撰写过程中我犯了很多错误。在调查Windows Store App中的FindName方法的过程中,我错误的去调查了WPFMSDN,最后调查到最后一步才发现自己的错误,浪费了很多时间。

其次是第一次听说扩展方法技术,说明自身对技术更新的敏感性不足。

最后,要感谢一起在微软中国外驻的各位同事,感谢周宇大哥在技术方面对我的指导,谢谢你们帮助我进步。

posted on 2013-09-10 21:12  令奇  阅读(946)  评论(0编辑  收藏  举报

导航