扩展方法详解

一、基础

扩展方法使您能够向现有类型“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。扩展方法是可以通过使用实例方法语法调用的静态方法。效果上,使得附加的方法扩展已存在类型和构造类型成为可能。他可以对现有类功能进行扩充,从而使该类型的实例具有更多的方法。扩展方法有助于把今天动态语言中流行的对快速录入支持的灵活性,与强类型语言之性能和编译时验证融合起来。这里先举一个msdn中的例子:
下面的示例演示为 System.String 类定义的一个扩展方法。假设我要分析一个字符串,希望得到字符串中单词的个数,一般情况下我们可能使用一个统计的函数来解决这个问题

 

代码
static void Main(string[] args)
{
string s = "Hello Extension Methods";
Console.WriteLine(WordCount(s));
//Console.WriteLine(s.WordCount());
}
static int WordCount(string str)
{
return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}

 

 

 

但这样用起来感觉上可能会很别扭,这个WordCount方法如果就是字符串类中的一个方法多好,像所有字符串的实例方法一样用。这里我们引出扩展方法来解决这个问题。

 

代码
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}

 

 

注意,上面的静态方法在第一个类型是string的参数变量前有个“this”关键词,这告诉编译器,这个特定的扩展方法应该添加到类型为“string”的对象中去。
使用 using 指令将 WordCount 扩展方法放入范围中:
using ExtensionMethods;

string s = "Hello Extension Methods";
int i = s.WordCount();

 

二、应用场景

1、扩展类库中已经存在的类

比方说我们是无法改变系统已经存在的类的,就像上例中我们无法在string类中添加WordCount方法,但我又希望在使用时直接使用

这篇文章c#扩展方法奇思妙用中也有很多例子,大家可以参考一下

2、多个类实现接口时有些方法实现方式相同,所以不希望每个实现类中重新实现一遍

假如系统中用户接口需要两个类实现,希望IUser接口声明的实体都有检测Email的方法IsUniqueEmail,而实际上这个方法在所有实现类中的实现逻辑都是一样的,而我们如果把这个方法添加到IUser中,那么两个类就要都实现一遍IsUniqueEmail的逻辑。中间加一层继承关系也可以解决问题,这种方式会增加维护成本,这时我们可以使用扩展方法

如下图:

代码模拟如下:

 

代码
interface IUser
{
string Email { get; set; }
string Name { get; set; }
}
class UserLinqToOracle : IUser
{
#region IUser 成员

private string email;
public string Email
{
get
{
return email;
}
set
{
email
=value;
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name
= value;
}
}

#endregion
}
class UserLinqToSql : IUser
{
#region IUser 成员

private string email;
public string Email
{
get
{
return email;
}
set
{
email
=value;
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name
= value;
}
}

#endregion
}

 

代码
static class UserExtension
{
public static bool IsUniqueEmail(this IUser forUser, string email)
{
return CheckEmail(email);
}

private static bool CheckEmail(string email)
{
return true;
}
}

 

static void Main(string[] args)
{
IUser user
= new UserLinqToOracle();
Console.WriteLine(user.IsUniqueEmail(
""));
}

 

三、扩展方法要点
1、扩展方法的本质为将实例方法调用在编译期改变为静态类中的静态方法调用。事实上,它确实拥有静态方法所具有的所有功能。
2、通常,您更多时候是调用扩展方法而不是实现您自己的扩展方法。由于扩展方法是使用实例方法语法调用的,因此不需要任何特殊知识即可从客户端代码中使用它们。若要为特定类型启用扩展方法,只需为在其中定义这些方法的命名空间添加 using 指令。
3、扩展方法的优先级:现有实例方法优先级最高,其次为最近的namespace下的静态类的静态方法,最后为较远的namespace下的静态类的静态方法。(与接口或类方法具有相同名称和签名的扩展方法永远不会被调用 )

代码
class Program
{
static void Main(string[] args)
{
User user
= new User() { FirstName = "l", LastName = "fm" };
Console.WriteLine(user.FullName());
}


}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName()
{
return "来源于实例方法:" + FirstName + LastName;
}
}
public static class MyExtensions
{
public static string FullName(this User user)
{
return "来源于扩展方法:"+user.FirstName+user.LastName;
}
}

 

 

结果为“来源于实例方法:lfm”

 

代码
namespace ConsoleApplication7
{
using MyNamespace;
class Program
{
static void Main(string[] args)
{
User user
= new User() { FirstName = "l", LastName = "fm" };
Console.WriteLine(user.FullName());
}


}
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public static class MyExtensions
{
public static string FullName(this User user)
{
return "来源于较近的扩展方法:"+user.FirstName+user.LastName;
}
}
}
namespace MyNamespace
{
using ConsoleApplication7;
public static class MyExtensions
{
public static string FullName(this User user)
{
return "来源于较远的扩展方法:" + user.FirstName + user.LastName;
}
}
}

 

 

结果为:“来源于较近的扩展方法:lfm”

4、在接口上扩展的方法,如果用接口声明时会优先使用基于接口的扩展方法

 

代码
class Program
{
static void Main(string[] args)
{
IUser user
= new User() { FirstName = "l", LastName = "fm", Email = "lfm@126.com" };
Console.WriteLine(user.GetEmail());
}
}
public interface IUser
{
string FirstName { get; set; }
string LastName { get; set; }
string Email { get; set; }
}
public class User : IUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string GetEmail()
{
return "来源实例方法:" + Email;
}
}
public static class MyExtensions
{
public static string GetEmail(this IUser forUser)
{
return "来源扩展方法:" + forUser.Email;
}
}

 

 

结果为:来源于扩展方法:lfm@126.com

但如果接口中又被添加了相应的方法,将优先使用实例方法

 

代码
class Program
{
static void Main(string[] args)
{
IUser user
= new User() { FirstName = "l", LastName = "fm", Email = "lfm@126.com" };
Console.WriteLine(user.GetEmail());
}
}
public interface IUser
{
string FirstName { get; set; }
string LastName { get; set; }
string Email { get; set; }
string GetEmail();
}
public class User : IUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string GetEmail()
{
return "来源实例方法:" + Email;
}
}
public static class MyExtensions
{
public static string GetEmail(this IUser forUser)
{
return "来源扩展方法:" + forUser.Email;
}
}

 

 

结果为:来源于实例方法:lfm@126.com
5、在代码中,可以使用实例方法语法调用该扩展方法。但是,编译器生成的中间语言 (IL) 会将代码转换为对静态方法的调用。因此,并未真正违反封装原则。实际上,扩展方法无法访问它们所扩展的类型中的私有变量。

 

四、扩展方法通用准则
1、通常,建议您只在不得已的情况下才实现扩展方法,并谨慎地实现。只要有可能,必须扩展现有类型的客户端代码都应该通过创建从现有类型派生的新类型来达到这一目的。
2、在使用扩展方法来扩展您无法更改其源代码的类型时,您需要承受该类型实现中的更改会导致扩展方法失效的风险。
3、如果您确实为给定类型实现了扩展方法,请记住以下两点:
     如果扩展方法与该类型中定义的方法具有相同的签名,则扩展方法永远不会被调用。
     扩展方法在命名空间级别被放入范围中。例如,如果您在同一个名为 Extensions 的命名空间中具有多个包含扩展方法的静态类,则这些扩展方法将全部由 using Extensions; 指令放入范围中。

posted @ 2010-05-24 18:23  你听海是不是在笑  阅读(2360)  评论(4编辑  收藏  举报