C#的扩展方法详解

扩展方法被定义为静态方法,但它们是通过实例方法语法进行调用的。 它们的第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀。 扩展方法当然不能破坏面向对象封装的概念,所以只能是访问所扩展类的public成员。
  扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,但可以像扩展类型上的实例方法一样进行调用。
C#扩展方法第一个参数指定该方法作用于哪个类型,并且该参数以 this 修饰符为前缀。
扩展方法的目的就是为一个现有类型添加一个方法,现有类型既可以是int,string等数据类型,也可以是自定义的数据类型。
为数据类型的添加一个方法的理解:一般来说,int数据类型有个Tostring的方法,就是把int 数据转换为字符串的类型,比如现在我们想在转换成字符串的时候还添加一点东西,比如增加一个字符 a .那么之前的Tostring就不好使了,因为它只是它我们的int数据转换为string类型的,却并不能添加一个字母 a.所以这就要用到所谓的扩展方法了。
首先我们看一个给现有的类型增加一个扩展方法。
我们想给string 类型增加一个Add方法,该方法的作用是给字符串增加一个字母a.


实例1:

 
 
  1. //必须是静态类才可以添加扩展方法
  2. Static class Program
  3. {
  4. static void Main(string[] args)
  5. {
  6. string str = "quzijing";
  7. //注意调用扩展方法,必须用对象来调用
  8. string Newstr = str.Add();
  9. Console.WriteLine(Newstr);
  10. Console.ReadKey();
  11. }
  12. //声明扩展方法
  13. //扩展方法必须是静态的,Add有三个参数
  14. //this 必须有,string表示我要扩展的类型,stringName表示对象名
  15. //三个参数this和扩展的类型必不可少,对象名可以自己随意取如果需要传递参数,//再增加一个变量即可
  16. public static string Add(this string stringName)
  17. {
  18. return stringName+ "a";
  19. }
  20. }</span>

我们再来尝试给我们自定义的类型增加一个扩展方法,并增加一个传递的参数。

实例2:

首先我们声明一个Student类,它包含了两个方法StuInfo,getStuInfo.实例代码如下:

  1. public class Student
  2. {
  3. public string StuInfo()
  4. {
  5. return "学生基本信息";
  6. }
  7. public string getStuInfo(string stuName, string stuNum)
  8. {
  9. return string.Format( "学生信息:\n" + "姓名:{0} \n" + "学号:{1}", stuName, stuNum);
  10. }
  11. }</span>

之后我们再声明一个名为ExtensionStudentInfo的静态类,注意必须为静态

这个类的作用就是包含一些我们想要扩展的方法,在此我们声明两个Student类型的扩展方法,Student类型为我们自定义的类型。示例代码如下:

  1. public static class ExtensionStudentInfo
  2. {
  3. //声明扩展方法
  4. //要扩展的方法必须是静态的方法,Add有三个参数
  5. //this 必须有,string表示我要扩展的类型,stringName表示对象名
  6. //三个参数this和扩展的类型必不可少,对象名可以自己随意取如果需要传递参数,再增加一个变量即可
  7. public static string ExtensionStuInfo(this Student stuName)
  8. {
  9. return stuName.StuInfo();
  10. }
  11. //声明扩展方法
  12. //要扩展的方法必须是静态的方法,Add有三个参数
  13. //this 必须有,string表示我要扩展的类型,stringName表示对象名
  14. //三个参数this和扩展的类型必不可少,对象名可以自己随意取如果需要传递参数,在此我们增加了两个string类型的参数
  15. public static string ExtensionGetStuInfo(this Student student, string stuname, string stunum)
  16. {
  17. return student.getStuInfo(stuname, stunum)+ "\n读取完毕";
  18. }
  19. }</span>

以上的工作做完之后便可以使用我们的扩展方法了,注意必须要用对象来调用扩展方法。
  1. static void Main(string[] args)
  2. {
  3. Student newstudent = new Student();
  4. //要使用对象调用我们的扩展方法
  5. string stuinfo = newstudent.ExtensionStuInfo();
  6. Console.WriteLine(stuinfo);
  7. //要使用对象调用我们的扩展方法
  8. string stuinformation = newstudent.ExtensionGetStuInfo( "quzijing", "20081766");
  9. Console.WriteLine(stuinformation);
  10. Console.ReadKey();
  11. }</span>

实例3、使用TagBuilder类创建扩展方法

上面自定义的Span()方法十分简单, 但是有时候我们要构造具有复杂结构的Html元素, 如果用字符串拼接的方法就有些笨拙.

ASP.NET MVC框架提供了一个帮助我们构造Html元素的类:TagBuilder

TagBuilder类有如下方法帮助我们构建Html控件字符串:

方法名称 用途
AddCssClass() 添加class=””属性
GenerateId() 添加Id,  会将Id名称中的"."替换为IdAttributeDotReplacement 属性值的字符.默认替换成"_"
MergeAttribute() 添加一个属性,有多种重载方法.
SetInnerText() 设置标签内容, 如果标签中没有再嵌套标签,则与设置InnerHTML 属性获得的效果相同.
ToString() 输出Html标签的字符串, 带有一个参数的重载可以设置标签的输出形式为以下枚举值:
  • TagRenderMode.Normal -- 有开始和结束标签
  • TagRenderMode.StartTag -- 只有开始标签
  • TagRenderMode.EndTag -- 只有结尾标签
  • TagRenderMode.SelfClosing -- 单标签形式,如<br/>

同时一个TagBuilder还有下列关键属性:

属性名称 用途
Attributes Tag的所有属性
IdAttributeDotReplacement 添加Id时替换"."的目标字符
InnerHTML Tag的内部HTML内容
TagName Html标签名, TagBuilder只有带一个参数-TagName的构造函数.所以TagName是必填属性
下面在添加一个自定义的HtmlHelper类扩展方法,同样是输出一个<Span>标签:

  1. public static string Span(this HtmlHelper helper, string id, string text, string css, object htmlAttributes)
  2. {
  3. //创意某一个Tag的TagBuilder
  4. var builder = new TagBuilder( "span");
  5. //创建Id,注意要先设置IdAttributeDotReplacement属性后再执行GenerateId方法.
  6. builder.IdAttributeDotReplacement = "-";
  7. builder.GenerateId(id);
  8. //添加属性
  9. builder.MergeAttributes( new RouteValueDictionary(htmlAttributes));
  10. //添加样式
  11. builder.AddCssClass(css);
  12. //或者用下面这句的形式也可以: builder.MergeAttribute("class", css);
  13. //添加内容,以下两种方式均可
  14. //builder.InnerHtml = text;
  15. builder.SetInnerText(text);
  16. //输出控件
  17. return builder.ToString(TagRenderMode.Normal);
  18. }</span>

在页面上,调用这个方法:

<% =Html.Span("span.test", "使用TagBuilder帮助构建扩展方法", "ColorRed", new { style="font-size:15px;" })%>

生成的Html代码为:

<span id="span-test" class="ColorRed" style="font-size: 15px;">使用TagBuilder帮助构建扩展方法</span>

实例4

(1)、扩展方法


  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. //声明扩展方法的步骤:类必须是static,方法是static,
  7. //第一个参数是被扩展的对象,前面标注this。
  8. //使用扩展方法的时候必须保证扩展方法类已经在当前代码中using
  9. namespace 扩展方法
  10. {
  11. //扩展方法必须是静态的
  12. public static class StringHelper
  13. {
  14. //扩展方法必须是静态的,第一个参数必须加上this
  15. public static bool IsEmail(this string _input)
  16. {
  17. return Regex.IsMatch(_input, @"^\\w+@\\w+\\.\\w+$");
  18. }
  19. //带多个参数的扩展方法
  20. //在原始字符串前后加上指定的字符
  21. public static string Quot(this string _input, string _quot)
  22. {
  23. return _quot + _input + _quot;
  24. }
  25. }
  26. }
  27. </span>

(2)、使用方法


  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace 扩展方法
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. string _myEmail = "[email protected]";
  12. //这里就可以直接使用string类的扩展方法IsEmail了
  13. Console.WriteLine(_myEmail.IsEmail());
  14. //调用接收参数的扩展方法
  15. Console.WriteLine(_myEmail.Quot( "!"));
  16. Console.ReadLine();
  17. }
  18. }
  19. }
  20. </span>

总结:

在我们的编程生涯中我们要使用很多很多类库,这些类库有的是我们自己开发的,我们有她的代码,有的是第三方发布的,我们不仅没有他们的代码,连看的机会都没有。

作为.net程序员,我们每天都要和BCL(Base Class Linbrary)打交道。无疑,BCL做为一个年轻的框架类库,她是成功的,但是还有一些时候我们还是得写一些”Helper”方法来扩展类库,由于我们不能修改类库的源代码,我们只有写一个个的静态类。虽然在使用上也算方便,但作为追求完美的程序员来说总有些不雅。 现在我就碰到这样的事情,前两天奉命写一个从XML文件加载Chart图的设置的方法,从XML加载数据绑定到对象上,这肯定是反射的用武之地了。我经常需要写一些根据对象属性名字来判断这个对象是否有这个属性或者根据属性名获取该属性的值。还是按照平常一样,我很快写了一个PropertyHelper,里面有两个静态方法:HasProperty,GetValueByName。

PropertyHelper.HasProperty(point, "X"),如此的调用也还过得去,不过在C# 3.0微软为我们提供了扩展方法。现在我们可以直接这样调用了point.HasProperty(“X”);看看我是如何实现这个扩展方法的?

  1. public static class PropertyExtension
  2. {
  3. public static object GetValueByName(this object self, string propertyName)
  4. {
  5. if (self == null)
  6. {
  7. return self ;
  8. }
  9. Type t = self.GetType();
  10. PropertyInfo p = t.GetProperty(propertyName);
  11. return p.GetValue(self, null);
  12. }
  13. }</span>

我给object类型添加了一个扩展方法,在.net里所有的类都继承自object,那所有的类都默认的拥有这个方法了,真方便,呵呵。

注意到和普通的静态方法有何差别?在这个方法的第一个参数前面多了一个this关键字。

扩展方法:

1 方法所在的类必须是静态的

2 方法也必须是静态的

3 方法的第一个参数必须是你要扩展的那个类型,比如你要给int扩展一个方法,那么第一个参数就必须是int。

4 在第一个参数前面还需要有一个this关键字。

按照上面的步骤写你就得到了一个“扩展方法”,你可以像调用这个类的原生方法那样去调用它:

string str = "abc";
object len = str.GetValueByName("Length");

好像string类型现在有了GetValueByName这个方法一样,但实际上string并没有这样一个方法。那这又是为什么呢?是我们可爱的编译器在其中做了手脚。为了避开编译器的干扰,我们来直接欣赏MSIL代码:

L_0008: ldstr "Length"
L_000d: call object TestLambda.PropertyExtension::GetValueByName(objectstring)

从MSIL中我们可以看出,这段代码编译后和调用静态方法没有任何的差别(从call指令来看,这是在调用一个静态方法)。

从这里可以知道扩展方法即可以使用实例调用的方式也可以直接使用静态类调用的方式:

str.GetValueByName("Length");

PropertyExtension.GetValueByName(str,"Length");

下面将对扩展方法做一些细节的介绍:

Visual Studio 2008对扩展方法有智能感知的支持,如下图:

在方法的图标上有一个与其他的都不相同,他的突变下面还带有一个蓝色的向下的箭头,这就表明这个方法是一个扩展方法。

下面是对编写扩展方法要注意的几个原则(当然,仁者见仁、智者见智,这也是一家之言):

扩展方法有就近原则,也就是如果在你的程序里有两个一模一样的扩展方法,一个和你的使用类是处于同一命名空间里,另外一个处于别的命名空间里,这个时候会优先使用同一命名空间里的扩展方法,也就是说“血缘关系”越近,越被青睐。

很多人看到扩展方法也许眼里冒出金光,以后在设计的时候不管三七二十一,反正可以扩展。还有一些人会对类任意扩展,将以前一些作为”Helper”的方法统统使用扩展方法代替,注意的是扩展方法有“污染性”,所以我觉得在扩展的时候还是想想,是不是值得这样扩展。

在扩展的时候也不要对比较高层的类进行扩展,像我上面对object的扩展我觉得就是不可取的,object是所有类的基类,一经扩展,所有的类都被“污染”了。


猜你喜欢

转载自blog.csdn.net/u011966339/article/details/80936675