我刚刚在 msdn/books 中读到,如果现有类源代码不可用,扩展方法对于向现有类添加方法很有用,但是我注意到在一些非常好的书面开源代码中,扩展方法仍然与继承一起使用(抽象,接口)在具有作者自己编写的源代码的类上。
这只是一般问题,这里没有源代码。
我刚刚在 msdn/books 中读到,如果现有类源代码不可用,扩展方法对于向现有类添加方法很有用,但是我注意到在一些非常好的书面开源代码中,扩展方法仍然与继承一起使用(抽象,接口)在具有作者自己编写的源代码的类上。
这只是一般问题,这里没有源代码。
这里已经有很多答案都很棒:我喜欢在可选行为和接口上使用扩展。还有其他几个很好的理由。
避免抽象方法重载。考虑以下接口:
public interface IUserService
{
User GetUser(int userId);
User GetUser(int userId, bool includeProfilePic);
}
我们可以看到在从IUserService
. 但是,使用接口上的这两种方法,它们可以以完全不同的方式实现(这种简单的方法可能不会,但我经常遇到这个问题)。使用扩展方法,重载不能有不同的行为:
public interface IUserService
{
User GetUser(int userId, bool includeProfilePic);
}
public static class UserServiceExtensions
{
public static User GetUser(this IUserService userService, int userId)
{
return userService.GetUser(userId, false);
}
}
尊重封装。 如果您想在一个类上添加一些附加功能,但它不需要对类的内部成员进行任何访问即可运行,并且不保存任何状态,那么使用扩展方法是可取的。对类内部成员和状态的了解越少,您的耦合就越少,从长远来看,维护代码就越容易。
缺点:你不能 Moq Extension Methods 很多时候这并不重要。这意味着,为了模拟扩展方法背后的行为,您通常需要知道扩展方法是如何工作的,并模拟它调用的虚拟方法。这会将您的测试与扩展方法的实现结合起来。如果您的扩展方法简单且不太可能改变,这只是烦人的。如果您的扩展方法封装了一些复杂的调用,这将非常糟糕。出于这个原因,我通常只对相对简单的行为使用扩展方法。
一个常见的原因是依赖管理:假设您有一个相当通用的类User
和一个不那么通用的类GravatarImage
。现在,能够调用SomeUser.GravatarImage()
而不是GravatarImage.ImageForUser(SomeUser)
. 这不仅是方便;在一个大型项目中,其他程序员可能很难找到做某事的“正确”方法。IntelliSense 将在这里提供很大帮助。
但是,User 类是一个“后端”类,不需要了解任何有关图像、视图、头像或 URL 的信息,因此您希望保持依赖关系干净。
类似的论点适用于 LINQ,它基本上由扩展方法组成。这些扩展方法,扩展了集合接口,所以你可以用非常小的接口创建很多功能。实现一种新类型IEnumerable
非常容易,但您可以获得 LINQ 提供的所有功能。
IEnumerable
接口,坚持这个例子,除了获得一个枚举器之外,没有更多的功能。Count
您可以调用扩展方法来完成相同的任务,而不是要求每个实现者提供一个方法。
然而值得注意的是,类似的方法IEnumerable.Count()
可能非常慢(它必须触及每个元素),而底层类的直接实现可能像返回一个简单的 int 一样简单。
A good way to think of extension methods is like a plugin-type architecture - they give you the ability to include/exclude functionality for a particular type instance. To answer your question specifically:
Why to use extension methods if source code is available instead of inheritance
The simplest answer to that would be for optional functionality. The most common, and probably the most important, reason for using extension methods is being able to extend a particular type without changing any core functionality. Deriving new types for the sake of adding a couple of methods is overkill, even if you had control over the source it would make more sense to make the type partial
instead. Extension methods tend to be used to solve problems for particular scenarios and don't really merit going into the core code base. However, should you find yourself using them all over the place then that's a good indicator that your probably not using them correctly.
For example, consider the following extension:
var epochTime = DateTime.UtcNow.ToEpochTime();
ToEpochTime
would return me the date time as Unix Time. This would be useful as an alternative way of generating a timestamp or serializing the date. However, it's quite a specific function so it wouldn't make sense being part of DateTime
but by making it an extension method it allows me to simply include this type of functionality if & when required.
我可以想到将子类化作为扩展方法的替代方案有几个缺点:
我经常使用扩展方法来桥接命名空间之间的边界,以提高可读性并保持关注点分离。例如myObject.GetDatabaseEntity()
读起来很好,但代码GetDatabaseEntity()
应该在代码的数据库部分,而不是在我的业务逻辑中。通过将此代码放在扩展方法中,我可以将所有内容保留在它所属的位置,而不会增加子类化的复杂性。
此外,如果myObject
在传递给数据库代码之前在我的业务逻辑中实例化,那么业务逻辑将需要包含数据库命名空间。我更喜欢明确划分每个模块的职责,并且希望我的业务逻辑尽可能少地了解数据库。
扩展方法还有一些有用的技巧(其中一些已经在其他答案中提到过):
在 C# 中,为接口提供扩展方法通常是一种近似* mixins的尝试。
一个 mixin 也可以被看作是一个实现了方法的接口。
尽管 C# 不支持 mixins,但为接口类提供其他人可以实现的扩展方法是一种很好的“连接”功能的方式。
这是一个简单接口的真实示例,该接口具有作为 mixin 提供的固定功能:
public interface IRandomNumberGenerator
{
Int32 NextInt();
}
public static class RandomNumberGeneratorExtensions
{
public static Double NextDouble(this IRandomNumberGenerator instance)
{
return (Double)Int32.MaxValue / (Double)instance.NextInt();
}
}
// Now any class which implements IRandomNumberGenerator will get the NextDouble() method for free...
* C# 对 mixins 的近似与真实的最大区别在于,在支持的语言中,mixins 可以包含私有状态,而 C# 中接口上的扩展方法显然只能访问公共状态。
这可能是一种特殊的情况,但我发现扩展方法在向现有类提供各种类型的“规则”时很有用。
假设您有一个SomeDataContainer
包含大量成员和数据的类,并且您希望在某些情况下导出该数据的某些部分,而在其他情况下导出其他部分。
你可以做这样的事情SomeDataContainer
:
if(ShouldIncludeDataXyz()){
exportXyz();
}
(...)
private bool ShouldIncludeDataXyz(){
// rules here
}
private void ExportXyz(){ (...) }
...但是我发现这有时会变得混乱,尤其是如果您有很多课程和许多规则等。
在某些情况下,我所做的是将规则放在单独的类中,每个“数据类”有一个“规则类”,并将规则创建为扩展类。
这只是在一个地方为我提供了一个与核心数据分开的规则层次结构——无论如何我觉得这种分离很有用。
生成的代码仍然与上面类似:
// This now calls an extention method, which can be found
// in eg. "SomeDataContainerRules.cs", along with other similar
// "rules"-classes:
if(this.ShouldIncludeDataXyz()){
exportXyz();
}