一、概述
从概念上讲,扩展方法就是装饰模式(Decorator,一种结构型模式)的一种实现,装饰模式实际上就是利用集成和复合动态添加行为。
当不能给现有类添加行为时,就可以考虑扩展方法,其作用就相当于这个方法属于这个类一样。例如,当我们不能继承一个密封类来为其添加行为时,同样,一些基础类型也是无法继承的。显然,扩展方法提供了一种无需集成的添加行为的方式。
扩展方法允许定义一个静态方法,并用实例方法的语法来调用。
我们在使用.net或第三方类库时,根据业务需要需增加一个函数类处理,但又不想在其他类中调用只想在原有类中调用,又不能直接修改源码,该如何实现呢?这时我们可以用扩展方法实现。
二、扩展方法及其使用规则
2.1 扩展方法的用法
扩展方法可以扩展密封类型,无需继承
扩展方法可以避免出现失控的深度继承层次体系
扩展方法是静态类中的继承方法,即必须在非泛型的静态类中声明
扩展方法第一个参数的类型之前必须使用this修饰符;this指代将要被扩展的类型,且扩展方法至少要有一个参数
不支持扩展属性、事件和操作符,只支持扩展方法
相对于实例方法而言,扩展方法的可视性要差一些,而且限制也会多一些
扩展方法是通过实例语法来调用的
扩展方法的优先级比常规方法要低;因此,如果某个类拥有一个同名方法的话,那么被调用的将是实例方法
扩展方法能在字面量上调用
扩展方法可以用在密封类和内部类型上,因为扩展方法自动支持装箱和拆箱;也就是说,当值类型被当作对象使用时,.NET Framework将会使用一个类将它包装起来
扩展方法并不是真正的成员,因此只能在其中访问被扩展对象的公共成员
扩展方法隐式使用ExtensionAttribute;在VB .NET中,ExtensionAttribute是显示使用的。
编译器在静态类中查找扩展方法时,要求静态类本身必须具有文件作用域;即如果静态类嵌套在另一个类中,编译会报错:扩展方法必须在顶级静态类中定义。
编译器允许创建委托来引用一个对象上的扩展方法。
【示例】
namespace test1
{
public static class Test
{
关键字this+要扩展对象(可以是.net类库或者第三方类库中的类,自定义类可行)
public static int ToInt32(this string str)
{
return Convert.ToInt32(str);
}
}
}
namespace test1
{
class Program
{
static void Main(string[] args)
{
string str = "200";
int i = str.ToInt32();
Console.WriteLine(i);
Console.ReadKey();
}
}
}
【示例:扩展方法是静态类中的继承方法】
#region 定义一个不带有返回类型的扩展方法
var song = new { songer = "huangjiaju", geyu = "haikuotiankong" };
song.Dump();
#endregion
#region 001
public static class Dumper
{
//this:指代将要被扩展的类型
public static void Dump(this Object o)
{
//初始化 System.Reflection.PropertyInfo 类的新实例。关于对象的属性信息
//获取当前实例的 System.Type。
// 返回当前 System.Type 的所有公共属性。
PropertyInfo[] properties = o.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
try
{
//将指定字符串中的格式项替换为两个指定对象的字符串表示形式。
Console.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, p.GetValue(o, null)));
}
catch
{
Console.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, "unk."));
}
}
}
}
#endregion
分析:
示例中定义了一个匿名类型,它含有两个属性。由于Dump作用于object类型,而所有类型又都继承自object,因此,该匿名类型也可以访问到Dump。
【示例】
#region 定义一个带有返回类型的扩展方法
var songs = new { songer = "weixiaoqi", gequ = "like you" };
Console.WriteLine(songs.Dump());
#endregion
#region 002
public static class Dumper
{
public static string Dump(this Object o)
{
PropertyInfo[] properties = o.GetType().GetProperties();
//表示可变字符字符串
StringBuilder builder = new StringBuilder();
foreach (PropertyInfo p in properties)
{
try
{
builder.AppendFormat(string.Format("Name:{0},Value:{1}", p.Name, p.GetValue(o, null)));
}
catch
{
builder.AppendFormat(string.Format("Name:{0},Value:{1}", p.Name, "unk."));
}
//将默认的行终止符追加到当前对象的末尾
builder.AppendLine();
}
return builder.ToString();
}
}
#endregion
扩展方法遵循一个固定的模式。定义一个公共静态类,然后在这个类里面定义一个公共静态方法。该方法的第一个参数必须使用this修饰符。第一个参数(用this修饰的)代指将要被扩展的类。在第一个参数之后,加上其他参数。这些静态方法既可以通过参数来重载,也可以是泛型方法。
【示例:重载扩展方法】
#region 重载扩展方法:第一个所接受的扩展类型是object,并转储单个对象的属性;第二个Dump扩展的是IList,它迭代这个集合中的每一项并对其调用Test
var songs = new[]
{
new {songer="weixiaoqi",song="like you"},
new {songer="xiaoxu",song="haikuotiankong"},
new {songer="qiqi",song="chengdu"},
new {songer="xiaoxiao",song="hebei"}
};
songs.Test();
#endregion
#region 004
public static class Tester
{
public static void Test(this Object o)
{
PropertyInfo[] properties = o.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
try
{
Console.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, p.GetValue(o, null)));
}
catch
{
Console.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, "unk."));
}
}
}
//object类型是所有类型的基类型,而很多集合类型又都实现了IList,这样通过重载,可以编写
//不同形式的Test方法,编译器会判断具体该使用哪个
public static void Test(this IList list)
{
foreach (object o in list)
{
o.Test();
}
}
}
#endregion
2.2 定义泛型扩展方法
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
namespace 定义泛型扩展方法
{
class Program
{
static void Main(string[] args)
{
//连接字符串
string connectionString = "Data Source=.;Initial Catalog=db_Test;Integrated Security=True";
//创建一个User类型的集合,用来存储每一个User类型的对象
List<User> users = new List<User>();
//打开数据库并及时释放资源
using(SqlConnection con=new SqlConnection(connectionString))
{
//编写查询语句:从表中查询所有数据,并作为参数传给命令语句
var selectSQL = "select * from tb_User";
SqlCommand com = new SqlCommand(selectSQL, con);
con.Open();
//返回结果:System.Data.SqlClient.SqlDataReader 对象。
SqlDataReader reader = com.ExecuteReader();
//一条一条地读取数据
while (reader.Read())
{
User user = new User();
user.Read(reader);
users.Add(user);
}
}
//use the dumper to send everything to the console
users.Dump(Console.Out);
Console.ReadLine();
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace 定义泛型扩展方法
{
/// <summary>
/// 输出查询所得结果
/// </summary>
public static class Dumper
{
/// <summary>
/// 对Object定义扩展方法
/// </summary>
/// <param name="o">定义扩展的对象</param>
/// <param name="writer">有序字符编写器对象</param>
public static void Dump(this Object o,TextWriter writer)
{
PropertyInfo[] properties = o.GetType().GetProperties();
foreach (PropertyInfo p in properties)
{
try
{
writer.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, p.GetValue(o, null)));
}
catch
{
writer.WriteLine(string.Format("Name:{0},Value:{1}", p.Name, "unk."));
}
}
}
/// <summary>
/// 将集合循环输出
/// </summary>
/// <typeparam name="T">集合中对象的类型</typeparam>
/// <param name="list"></param>
/// <param name="writer"></param>
public static void Dump<T>(this IList<T> list,TextWriter writer)
{
foreach(object o in list)
{
o.Dump(writer);
}
}
}
}
using System;
using System.Data;
namespace 定义泛型扩展方法
{
public static class ReaderHelper
{
/// <summary>
/// 对每条数据进行处理,如果字段为null,设置默认值
/// </summary>
/// <param name="user">对User定义泛型扩展方法</param>
/// <param name="reader">数据读取器对象</param>
public static void Read(this User user, IDataReader reader)
{
user.UserName = user.SafeRead(reader, "UserName", "未注册");
user.UserPass = user.SafeRead(reader, "UserPass", "000");
user.UserQPass = user.SafeRead(reader, "UserQPasd", "000");
user.Sex = user.SafeRead(reader, "Sex", '中');
user.Age = user.SafeRead(reader, "Age", 0);
}
/// <summary>
/// 判断字段是否为空,如若为空,返回默认值;反之,返回当前值
/// </summary>
/// <typeparam name="T">类型,方法的返回值类型</typeparam>
/// <param name="entity">对EntityClass定义泛型扩展方法</param>
/// <param name="reader">数据读取器对象</param>
/// <param name="filedName">字段名</param>
/// <param name="defaultValue">默认值</param>
/// <returns></returns>
public static T SafeRead<T>(this EntityClass entity, IDataReader reader, string filedName, T defaultValue)
{
try
{
object o = reader[filedName];
if (o == null || o == DBNull.Value)
{
return defaultValue;
}
return (T)Convert.ChangeType(o, defaultValue.GetType());
}
catch
{
return defaultValue;
}
}
}
}
namespace 定义泛型扩展方法
{
/// <summary>
/// 实体类
/// </summary>
public class User:EntityClass
{
/// <summary>
/// 初始化
/// </summary>
public User()
{
}
/// <summary>
/// 用户名
/// </summary>
private string _username;
public string UserName
{
get
{
return _username;
}
set
{
_username = value;
}
}
/// <summary>
/// 用户密码
/// </summary>
private string _userpass;
public string UserPass
{
get
{
return _userpass;
}
set
{
_userpass = value;
}
}
/// <summary>
/// 确认密码
/// </summary>
private string _userqpass;
public string UserQPass
{
get
{
return _userqpass;
}
set
{
_userqpass = value;
}
}
/// <summary>
/// 性别
/// </summary>
private char _sex;
public char Sex
{
get
{
return _sex;
}
set
{
_sex = value;
}
}
/// <summary>
/// 年龄
/// </summary>
private int _age;
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
}
}
namespace 定义泛型扩展方法
{
/// <summary>
/// 实体类
/// </summary>
public class EntityClass
{
}
}
分析:
该示例的核心在于如何对空值即值类型进行处理,而后以何种格式进行输出。
三、分部方法
Bill说:“分部方法就是为自动生成的代码而准备的占位符”。
自动代码生成所存在的一个典型问题是,当消费者修改了已生成的代码之后,又重新自动生成了这段代码,那么用户对这段代码所做的修改就会被覆盖掉。该问题的解决方案之一是从自动生成的代码上继承一个子类,然后在这个子类中编写自定义代码。而分部方法能能够让生产者只修改标记为partial的方法。如果消费者想要在某处插入一些行为,则需要提供该分部方法的一个实现。如果没有给出实现,则该分部方法将会被忽略。
分部方法的一些基本规则:
分部方法要声明在分部类(Partial Class)中 或结构中
分部方法要使用Partial
分部方法在声明时不能有方法体
分部方法只能返回void
分部方法可以是静态的,而且可以有参数和参数修饰符(包括ref和params)
分部方法是私有的,不过不能在字面上使用访问修饰符
编译器将忽略未使用的分部方法
分部方法的声明和实现必须具有一致的签名
如果没有对应的实现部分,便不能在代码中创建一个委托来引用这个分部方法。
【示例】
using System;
namespace 定义分部方法
{
class Program
{
static void Main(string[] args)
{
CustomerList customers = new CustomerList
{
new Customer {CustomerID=1,Name="xushuai" },
new Customer { CustomerID=2,Name="wangfeng"},
new Customer { CustomerID=3,Name="xianggou"}
};
customers.Dump();
Console.ReadKey();
}
}
}
using System;
using System.Collections.Generic;
using System.Web.UI.WebControls;
using System.Reflection;
using System.Diagnostics;
namespace 定义分部方法
{
public partial class CustomerList
{
private string propertyName;
private SortDirection direction;
/// <summary>
/// 分部方法实现代码,分部方法是私有的,不过不能在字面上使用访问修饰符,即不能显示地使用private
/// </summary>
/// <param name="propertyName"></param>
/// <param name="direction"></param>
partial void SpecialSort(string propertyName,SortDirection direction)
{
this.propertyName = propertyName;
this.direction = direction;
Sort(Comparer);
}
private int Comparer(Customer x,Customer y)
{
try
{
PropertyInfo lhs = x.GetType().GetProperty(propertyName);
PropertyInfo rhs = y.GetType().GetProperty(propertyName);
int directionChanger = direction == SortDirection.Ascending ? 1 : -1;
object o1 = lhs.GetValue(x, null);
object o2 = rhs.GetValue(y, null);
//定义一种特定于类型的通用比较方法,值类型或类通过实现此方法对其实例进行排序。
if (o1 is IComparable&&o2 is IComparable)
{
return ((IComparable)o1).CompareTo(o2) * directionChanger;
}
//no sort
return 0;
}
catch(Exception ex)
{
Debug.WriteLine(ex.Message);
return 0;
}
}
}
/// <summary>
/// 可枚举类
/// </summary>
public partial class CustomerList : List<Customer>
{
/// <summary>
/// 分部方法在声明时不能有方法体
/// </summary>
/// <param name="propertyName"></param>
/// <param name="direction"></param>
partial void SpecialSort(string propertyName, SortDirection direction);
public void Dump()
{
SpecialSort("CustomerID", SortDirection.Descending);
foreach(var customer in this)
{
Console.WriteLine(customer.ToString());
}
}
}
/// <summary>
/// 实体类
/// </summary>
public class Customer
{
private int customerID;
public int CustomerID
{
get
{
return customerID;
}
set
{
customerID = value;
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public override string ToString()
{
return string.Format("CustomerID={0}, Name={1}", customerID, Name);
}
}
}