在某些情况下,我们需要为一些类添加方法,对于我们自己编写的类或接口的,我们很容易实现这一点,但是对于一些第三方的类库,我们却很难做到,虽然可以采用继承等方式实现,但这些方法都需要编写更多的代码,而且不够灵活。所以无论是第三方类库,还是我们自己编写的代码,如果我们想要在不破坏原有类型的基础上,添加一些其他的方法,这时候就可以使用C#
的扩展方法实现。
1. 基本使用
// Extension method allow us to extend new method for existing class or interface and we do not need to rewrite them.
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
public static class PersonHelper
{
public static string GetFullName(this Person person)
{
return person.FirstName + person.LastName;
}
}
...
var person = new Person("Rob", "Li");
var extensionFullName = person.GetFullName();
var staticFullName = PersonHelper.GetFullName(person);
Console.WriteLine(extensionFullName);
Console.WriteLine(staticFullName);
比如我们要实现一个获取全名的方法,只需要编写一个静态类,以及编写相应的方法就可以了。
这里需要注意的是person.GetFullName()
在实际使用的时候,会被转化成普通的静态方法调用,即PersonHelper.GetFullName(person)
2. 链式调用与命名空间引入
namespace DefineMain
{
using ExtensionMethod;
public class MainClass
{
static void Main()
{
var name = "Name";
var doubleName = name.GetDoubleString();
Console.WriteLine(doubleName); // NameName
var result = name.GetDoubleString().GetDoubleString();
Console.WriteLine(result); //NameNameNameName
}
}
}
namespace ExtensionMethod
{
public static class StringHelper
{
public static string GetDoubleString(this string s)
{
return s + s;
}
}
}
使用扩展方法时,必须导入扩展方法所在的命名空间,这时候才能使用。这和使用相应的类时,导入对应的命名空间是一样的。
此外扩展方法支持链式调用,比如name.GetDoubleString()
的返回类型依旧是 string
,所以依旧可以继续使用扩展方法。
3. 优先级
如果扩展方法方法名与实例的方法名一样,在参数数量一致的情况下,总是会优先调用实例的方法,即使扩展方法参数的类型比实例方法的参数类型更加具体
public class Person
{
public string GetName() => "MyName";
public string GetLastName(object lastName) => $"{lastName}";
}
public static class PersonHelper
{
public static string GetName(this Person person) => "No Param";
public static string GetName(this Person person,string param) => $"No Param {param}";
public static string GetLastName(this Person person,string param) => $"LastName {param}";
}
...
var person = new Person();
Console.WriteLine(person.GetName()); // MyName
Console.WriteLine(person.GetName("Lucy")); // No Param Lucy
Console.WriteLine(person.GetLastName("Jack")); // Jack
但是,如果多个扩展方法方法签名一样,参数类型更加具体的方法优先级更高
public static class StringHelper
{
public static string GetName(this string s) => $"{s} string";
}
public static class StringHelperCopy
{
public static string GetName(this object s) => $"{s} object";
}
...
Console.WriteLine("Rob".GetName()); // Rob string
4. 总结
1. 扩展方法形式
-
必须被定义在非嵌套、非泛型静态类的内部;
-
方法的第一个参数为this,第一个参数的类型为要扩展的类型;
2. 注意事项
- 使用时,必须使用
using
引入扩展方法所在的命名空间; - 实例方法与扩展方法,方法签名,参数一致时,即使扩展方法的参数类型更加具体,也会优先调用实例的方法;
- 扩展方法与扩展方法,方法签名,参数一致时,优先调用参数更加具体的扩展方法。
3. 理解
对于实例方法,即使方法没有参数,实际上也会有一个隐藏的参数,也就是this,即实例本身;扩展方法之所以用this加对象类型作为方法的第一次参数,实际上也是为了引入实例对象,只不过相当于是显示指定的this这个默认的参数。
5. 参考资料
- 《C# 8.0核心技术指南》
