这些问题是我之前提出的关于Extension Methods的问题的后续问题。
10 回答
什么时候使用扩展方法有意义?
当您使用 LINQ 并希望将功能输出从一个函数链接或管道传输到另一个函数时,它们是有意义的。它提高了代码的可读性,并允许您更优雅地表达一个概念(无论其价值如何)。
它们还允许您在您喜欢的任何类型上给出实例方法的外观,而无需修改该类型的源代码,这通常有助于合理使用代码的可读性和表现力
向类型添加扩展方法会影响性能吗?
请注意,扩展方法调用如下:
instance.SomeExtensionMethod()
被编译为:
StaticExtensionMethodClass.SomeExtensionMethod(instance);
因此性能将与任何其他静态方法调用相同。
从我在这里的回答:
至于扩展方法的实际用途,您可以在不派生新类的情况下向类添加新方法。
看看下面的例子:
public class extended {
public int sum() {
return 7+3+2;
}
}
public static class extending {
public static float average(this extended extnd) {
return extnd.sum() / 3;
}
}
如您所见,该类Extending
正在向 class 添加一个名为 average 的方法Extended
。要获得平均值,请调用average
方法,因为它属于extended
类:
extended ex = new extended();
Console.WriteLine(ex.average());
至于Performance,我认为您可以看到 Extension 方法的改进,因为它们永远不会动态调度,但这完全取决于动态方法的实现方式。
它用于扩展(添加)现有类的功能,而无需实际更改它们。
您可以在 LINQ(System.Linq 命名空间和其他命名空间)如何为所有集合添加大量功能中看到这一点。
扩展方法的另一个有趣用途是当您希望将某些功能添加到一个命名空间下的类而不是另一个命名空间下。一个具体的例子是添加简化单元测试的方法——您不希望它们使您的生产程序集变得混乱,但是在编写单元测试时拥有它们是很棒的。
除了其他答案之外,扩展方法是将样板实现添加到接口的好方法。例如,如果您希望所有列表都可排序,请为IList<T>
.
您也可以(如前所述)使用扩展方法将方法添加到您无法控制的类中;曾经想要一个Reverse()
方法string
吗?加一个!
唯一的区别是扩展方法不使用虚拟,并且没有空检查。如果你喜欢,你可以利用它来发挥你的优势:
public static void ThrowIfNull<T>(this T obj, string name) where T : class
{
if(obj == null) throw new ArgumentNullException(name);
}
与常规实用方法不同,它们使编写流畅的接口变得非常容易;这是它们存在的原因之一 - 即使用 LINQ:
var foo = source.Where(predicate).OrderBy(selector);
比以下更具可读性:
var foo = Enumerable.OrderBy(Enumerable.Where(source,predicate),selector);
对于常规方法,要使用第一种方法,它必须是常规实例方法,这将需要(例如)更改为IEnumerable<T>
- 不可取的。
扩展方法在使用函数式编程的地方大放异彩。
考虑一下您在应用程序中已经拥有的几乎所有模块级函数,以及当您将它们标记为扩展方法时它们会变成什么。它们成为“主动语态”而非“被动语态”的机会。主动语音意味着代码读起来好像实例提供了自己的方法来执行特定任务,而不是让函数被动地对其执行操作。
ExtendUnlimitedCredit(AddVIP(tblCustomer, "Bill Gates"))
对比
tblCustomer.AddVIP("Bill Gates").ExtendUnlimitedCredit()
扩展方法使这种转换变得简单。使用“主动语音”代码消除嵌套函数调用通常是一种改进。另外,由于我们的商店锁定了它的核心类,我们只能在扩展方法之前使用嵌套函数调用。
关于扩展方法的其他好处:
它们使您的功能(因为 Intellisense)更容易被发现。如果您提供了描述函数用途和用途的内联标记,Intellisense 甚至会向发现它的开发人员提供描述该方法及其用途的有用工具提示(只需按下点)。未标记为扩展方法的函数不容易被发现,可能会被闲置,因此,其他人可能会发明他们自己对所述函数的风格。
尽管您实际上无法为接口实现方法,但扩展方法提供了另一种方法,使您看起来已经这样做了。
我不知道任何性能影响。当您无法访问源代码并因此无法直接将方法添加到类中时,使用扩展方法最有意义,并且该方法可以作为函数实现。这转到我对您之前的问题所做的评论,其中另一个人提供了“字符串”类上的扩展方法的示例,该方法根据字符串是否为有效电子邮件返回布尔值。IMO,这是不使用扩展方法的示例,因为该函数不是字符串类型的基础。但是,将 Left(int) 和 Right(int) 函数添加到 'string' 确实有意义。
我使用它们来重用我的对象模型类。我有一堆代表我在数据库中拥有的对象的类。这些类在客户端仅用于显示对象,因此基本用法是访问属性。
public class Stock {
public Code { get; private set; }
public Name { get; private set; }
}
由于这种使用模式,我不想在这些类中包含业务逻辑方法,因此我将每个业务逻辑都作为扩展方法。
public static class StockExtender {
public static List <Quote> GetQuotesByDate(this Stock s, DateTime date)
{...}
}
通过这种方式,我可以将相同的类用于业务逻辑处理和用户界面显示,而不会用不必要的代码使客户端超载。
关于这个解决方案的一个有趣的事情是我的对象模型类是使用Mono.Cecil动态生成的,所以即使我想要添加业务逻辑方法也会非常困难。我有一个编译器,它读取 XML 定义文件并生成这些存根类,这些存根类代表我在数据库中拥有的一些对象。在这种情况下,唯一的方法是扩展它们。
解决你的第二个问题:我的经验法则是扩展方法应该是类型上的“功能的自然扩展”,或者它应该是流畅接口的可维护和可读部分。
“功能的自然扩展”的一个示例是扩展数据读取器以在遇到 DBNull 时返回默认值。扩展数据读取器以返回由多个字段中的数据表示的实体的实例并不是那么自然。在后一种情况下,您将错误的职责注入到可怜的对象中:)。
/// <summary>
/// External Library Code
/// </summary>
namespace ExternalLibrary
{
public class Calculator
{
public int Number1 { get; set; }
public int Number2 { get; set; }
public int Addition()
{
return Number1 + Number2;
}
public int Subtraction()
{
return Number1 - Number2;
}
}
}
--------------------------------------------------------------------------------------
using ExternalLibrary;
using System;
namespace StackOverFlow
{
class Program
{
static void Main(string[] args)
{
Calculator calc = new Calculator()
{
Number1 = 5,
Number2 = 3
};
Console.WriteLine(calc.Addition());
Console.WriteLine(calc.Subtraction());
// Here we want multiplication also. but we don't have access Calculator
// class code, so we can't modify in that.
// In order to achieve this functionality we can use extension method.
Console.WriteLine(calc.Multiplication());
Console.ReadLine();
}
}
/// <summary>
/// Extension Method for multiplication
/// </summary>
public static class CalculatorExtension
{
public static int Multiplication(this Calculator calc)
{
return calc.Number1 * calc.Number2;
}
}
}