一个 C# 的“非信徒”问我扩展方法的目的是什么。我解释说,然后您可以向已定义的对象添加新方法,尤其是当您不拥有/控制原始对象的源时。
他提出“为什么不给你自己的类添加一个方法?” 我们一直在兜兜转转(以一种好的方式)。我的一般反应是它是工具带中的另一个工具,他的回答是这是对工具的无用浪费......但我认为我会得到一个更“开明”的答案。
在哪些情况下您使用了无法(或不应该)使用添加到您自己的类中的方法的扩展方法?
一个 C# 的“非信徒”问我扩展方法的目的是什么。我解释说,然后您可以向已定义的对象添加新方法,尤其是当您不拥有/控制原始对象的源时。
他提出“为什么不给你自己的类添加一个方法?” 我们一直在兜兜转转(以一种好的方式)。我的一般反应是它是工具带中的另一个工具,他的回答是这是对工具的无用浪费......但我认为我会得到一个更“开明”的答案。
在哪些情况下您使用了无法(或不应该)使用添加到您自己的类中的方法的扩展方法?
扩展方法的唯一优点是代码可读性。就是这样。
扩展方法允许您这样做:
foo.bar();
而不是这个:
Util.bar(foo);
现在C#中有很多东西都是这样的。换句话说,C# 中有许多看似微不足道的特性,它们本身并没有太大的好处。但是,一旦您开始将这些功能组合在一起,您就会开始看到比其部分总和大一点的东西。LINQ 从扩展方法中受益匪浅,因为没有它们,LINQ 查询几乎无法读取。如果没有扩展方法, LINQ 是可能的,但不实用。
扩展方法很像 C# 的部分类。它们本身并不是很有帮助,而且看起来微不足道。但是当您开始使用需要生成代码的类时,部分类开始变得更有意义。
我认为扩展方法在编写代码时很有帮助,如果您将扩展方法添加到基本类型,您将在智能感知中快速获得它们。
我有一个格式提供程序来格式化文件大小。要使用它,我需要编写:
Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));
创建一个我可以编写的扩展方法:
Console.WriteLine(fileSize.ToFileSize());
更干净更简单。
不要忘记工具!当您在 Foo 类型上添加扩展方法 M 时,您会在 Foo 的智能感知列表中获得“M”(假设扩展类在范围内)。这使得 'M' 比 MyClass.M(Foo,...) 更容易找到。
归根结底,它只是其他静态方法的语法糖,但就像买房子一样:“位置,位置,位置!” 如果它挂在类型上,人们会找到它!
我遇到的扩展方法的另外两个好处:
Select(...)
,Where(...)
等。挂在IEnumerable<T>
接口之外。我对扩展方法的一些最佳用途是能够:
sealed
.举个例子,IEnumerable<T>
。虽然它有丰富的扩展方法,但我发现它没有实现通用的 ForEach 方法很烦人。所以,我自己做了:
public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
foreach ( var o in enumerable )
{
action(o);
}
}
瞧,我的所有IEnumerable<T>
对象,无论实现类型如何,无论是我编写还是其他人现在都ForEach
通过在我的代码中添加适当的“使用”语句来获得方法。
使用扩展方法的重要原因之一是 LINQ。如果没有扩展方法,您在 LINQ 中可以做的很多事情都会非常困难。Where()、Contains()、Select 扩展方法意味着更多的功能被添加到现有类型而不改变它们的结构。
关于扩展方法的优点有很多答案;如何解决缺点?
最大的缺点是,如果您在同一上下文中拥有具有相同签名的常规方法和扩展方法,则不会出现编译器错误或警告。
假设您创建了一个应用于特定类的扩展方法。然后后来有人在该类本身上创建了一个具有相同签名的方法。
您的代码将编译,您甚至可能不会收到运行时错误。 但是您不再运行与以前相同的代码。
Greg Young 在CodeBetter上演示的流畅接口和上下文敏感性
我对扩展方法的个人观点是,它们非常适合 OOP 设计:考虑简单的方法
bool empty = String.IsNullOrEmpty (myString)
相比
bool empty = myString.IsNullOrEmpty ();
关于扩展方法可以让你做什么,上面有很多很好的答案。
我的简短回答是——它们几乎消除了对工厂的需求。
我只想指出它们不是一个新概念,对它们的最大验证之一是它们是 Objective-C 中的杀手级特性(类别)。它们为基于框架的开发增加了极大的灵活性,以至于 NeXT 将 NSA 和华尔街金融建模师作为主要用户。
REALbasic 也将它们作为扩展方法来实现,它们在简化开发方面也有类似的用途。
我想在这里支持其他答案,这些答案提到提高代码可读性是扩展方法背后的一个重要原因。我将通过两个方面来证明这一点:方法链接与嵌套方法调用,以及使用无意义的静态类名使 LINQ 查询混乱。
让我们以这个 LINQ 查询为例:
numbers.Where(x => x > 0).Select(x => -x)
Where
和都是Select
扩展方法,在静态类中定义Enumerable
。因此,如果扩展方法不存在,并且这些是普通的静态方法,那么最后一行代码基本上必须如下所示:
Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)
看看这个查询刚刚得到了多少。
其次,如果您现在想引入自己的查询运算符,您自然无法在Enumerable
静态类中定义它,就像所有其他标准查询运算符一样,因为Enumerable
它在框架中,您无法控制该类。因此,您必须定义自己的包含扩展方法的静态类。然后,您可能会收到如下查询:
Enumerable.Select(MyEnumerableExtensions.RemoveNegativeNumbers(numbers), x => -x)
// ^^^^^^^^^^^^^^^^^^^^^^
// different class name that has zero informational value
// and, as with 'Enumerable.xxxxxx', only obstructs the
// query's actual meaning.
确实,您可以将(扩展)方法直接添加到您的类中。但并非所有课程都是由您编写的。来自核心库或第三方库的类通常是封闭的,没有扩展方法就不可能获得语法糖。但请记住,扩展方法就像例如(静态)独立方法。C++
扩展方法还可以帮助保持您的类和类依赖项干净。例如,在使用 Foo 的任何地方,您可能都需要 Foo 类的 Bar() 方法。但是,您可能希望在另一个程序集中使用 .ToXml() 方法,并且仅用于该程序集。在这种情况下,您可以在该程序集中而不是在原始程序集中添加必要的 System.Xml 和/或 System.Xml.Linq 依赖项。
好处:定义类程序集中的依赖关系减少到仅是必需品,并且将阻止其他消费程序集使用 ToXml() 方法。请参阅此 PDC 演示文稿以获取更多参考。
我同意扩展方法增加了代码的可读性,但它实际上只是静态辅助方法。
IMO 使用扩展方法向您的类添加行为可以是:
困惑:程序员可能认为方法是扩展类型的一部分,因此不理解为什么不导入扩展命名空间时方法会消失。
反模式:您决定使用扩展方法向框架中的类型添加行为,然后将它们发送给某个人进行单元测试。现在他被困在一个包含一堆他无法伪造的方法的框架中。
扩展方法实际上是Martin Fowler 书中“引入外部方法”重构的 .NET 合并(直至方法签名)。它们具有基本相同的好处和陷阱。在有关此重构的部分中,他说当您无法修改应该真正拥有该方法的类时,它们是一种解决方法。
我主要将扩展方法视为一种承认,也许它们不应该禁止免费功能。
在 C++ 社区中,通常认为良好的 OOP 实践是更喜欢免费的非成员函数而不是成员,因为这些函数不会通过访问它们不需要的私有成员来破坏封装。扩展方法似乎是实现相同目标的一种迂回方式。也就是说,对于无法访问私有成员的静态函数来说,这是一种更简洁的语法。
扩展方法只不过是语法糖,但我认为使用它们没有任何害处。
我使用它们来重用我的对象模型类。我有一堆代表我在数据库中拥有的对象的类。这些类在客户端仅用于显示对象,因此基本用法是访问属性。
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 定义文件并生成这些存根类,这些存根类代表我在数据库中拥有的一些对象。在这种情况下,唯一的方法是扩展它们。
它允许 C# 更好地支持动态语言、LINQ 和其他十几种东西。查看Scott Guthrie 的文章。
在我的上一个项目中,我使用扩展方法将 Validate() 方法附加到业务对象。我证明了这一点是因为可序列化数据传输对象的业务对象将在不同的域中使用,因为它们在一般电子商务实体(如产品、客户、商家等)中使用。在不同的域中,业务规则也可能不同,所以我封装了我的附加到我的数据传输对象的基类的 Validate 方法中的后期绑定验证逻辑。希望这是有道理的:)
扩展方法非常有用的一种情况是在使用 ASMX Web 服务的客户端应用程序中。由于序列化,Web 方法的返回类型不包含任何方法(只有这些类型的公共属性在客户端可用)。
扩展方法允许用于向 Web 方法返回的类型添加功能(在客户端),而无需在客户端创建另一个对象模型或大量包装类。
还请记住,添加扩展方法是为了帮助 Linq 查询在其 C# 样式中使用时更具可读性。
这两种做作是绝对等价的,但第一种更具可读性(并且随着更多方法链接,可读性的差距当然会增加)。
int n1 = new List<int> {1,2,3}.Where(i => i % 2 != 0).Last();
int n2 = Enumerable.Last(Enumerable.Where(new List<int> {1,2,3}, i => i % 2 != 0));
请注意,完全限定的语法甚至应该是:
int n1 = new List<int> {1,2,3}.Where<int>(i => i % 2 != 0).Last<int>();
int n2 = Enumerable.Last<int>(Enumerable.Where<int>(new List<int> {1,2,3}, i => i % 2 != 0));
偶然地,不需要明确提及和的类型参数,因为这两个方法的第一个参数的存在可以推断它们(由关键字引入Where
并使其成为扩展方法的参数)。Last
this
这一点显然是扩展方法的一个优势(除其他外),您可以在涉及方法链接的每个类似场景中从中受益。
特别是,我发现它是一种更优雅和令人信服的方式,它有一个可由任何子类调用的基类方法,并返回对该子类的强类型引用(使用子类类型)。
例子(好吧,这个场景很俗气):一个晚安之后,一只动物睁开眼睛然后哭了;每只动物都以同样的方式睁开眼睛,而狗叫,鸭子叫。
public abstract class Animal
{
//some code common to all animals
}
public static class AnimalExtension
{
public static TAnimal OpenTheEyes<TAnimal>(this TAnimal animal) where TAnimal : Animal
{
//Some code to flutter one's eyelashes and then open wide
return animal; //returning a self reference to allow method chaining
}
}
public class Dog : Animal
{
public void Bark() { /* ... */ }
}
public class Duck : Animal
{
public void Kwak() { /* ... */ }
}
class Program
{
static void Main(string[] args)
{
Dog Goofy = new Dog();
Duck Donald = new Duck();
Goofy.OpenTheEyes().Bark(); //*1
Donald.OpenTheEyes().Kwak(); //*2
}
}
从概念上讲OpenTheEyes
应该是一个Animal
方法,但它会返回一个抽象类的实例Animal
,它不知道特定的子类方法,如Bark
orDuck
或其他。注释为 *1 和 *2 的 2 行将引发编译错误。
但是由于扩展方法,我们可以拥有一种“知道调用它的子类类型的基本方法”。
请注意,一个简单的通用方法可以完成这项工作,但以一种更尴尬的方式:
public abstract class Animal
{
//some code common to all animals
public TAnimal OpenTheEyes<TAnimal>() where TAnimal : Animal
{
//Some code to flutter one's eyelashes and then open wide
return (TAnimal)this; //returning a self reference to allow method chaining
}
}
这一次,没有参数,因此没有可能的返回类型推断。调用可以是:
Goofy.OpenTheEyes<Dog>().Bark();
Donald.OpenTheEyes<Duck>().Kwak();
...如果涉及更多链接,这可能会给代码带来很大的负担(尤其是知道类型参数将始终<Dog>
在 Goofy 的线路和<Duck>
Donald 的线路上......)
我只有一个词要说:可维护性这是扩展方法使用的关键
我认为扩展方法有助于编写更清晰的代码。
正如您的朋友所建议的那样,您没有将新方法放入您的类中,而是将其放入 ExtensionMethods 命名空间中。通过这种方式,您可以保持班级的逻辑秩序感。不真正直接处理你的类的方法不会把它弄得乱七八糟。
我觉得扩展方法使您的代码更清晰,组织更恰当。
它允许您的编辑器/IDE 智能地自动完成建议。
我喜欢他们构建 html。经常有一些部分被重复使用,或者在一个函数有用但会破坏程序流程的情况下递归生成。
HTML_Out.Append("<ul>");
foreach (var i in items)
if (i.Description != "")
{
HTML_Out.Append("<li>")
.AppendAnchor(new string[]{ urlRoot, i.Description_Norm }, i.Description)
.Append("<div>")
.AppendImage(iconDir, i.Icon, i.Description)
.Append(i.Categories.ToHTML(i.Description_Norm, urlRoot)).Append("</div></li>");
}
return HTML_Out.Append("</ul>").ToString();
在某些情况下,对象需要为 HTML 输出准备自定义逻辑 - 扩展方法允许您添加此功能,而无需在类中混合表示和逻辑。
我发现扩展方法对于匹配嵌套的泛型参数很有用。
这听起来有点奇怪 - 但假设我们有一个泛型 class MyGenericClass<TList>
,并且我们知道 TList 本身是泛型的(例如 a List<T>
),我认为没有任何扩展名的情况下没有办法从 List 中挖掘出嵌套的“T”方法或静态辅助方法。如果我们只有静态辅助方法可供我们使用,它 (a) 丑陋,并且 (b) 将迫使我们将属于该类的功能移动到外部位置。
例如,要检索元组中的类型并将它们转换为方法签名,我们可以使用扩展方法:
public class Tuple { }
public class Tuple<T0> : Tuple { }
public class Tuple<T0, T1> : Tuple<T0> { }
public class Caller<TTuple> where TTuple : Tuple { /* ... */ }
public static class CallerExtensions
{
public static void Call<T0>(this Caller<Tuple<T0>> caller, T0 p0) { /* ... */ }
public static void Call<T0, T1>(this Caller<Tuple<T0, T1>> caller, T0 p0, T1 p1) { /* ... */ }
}
new Caller<Tuple<int>>().Call(10);
new Caller<Tuple<string, int>>().Call("Hello", 10);
也就是说,我不确定分界线应该在哪里——方法什么时候应该是扩展方法,什么时候应该是静态辅助方法?有什么想法吗?
我的屏幕上有输入区域,无论它们的确切类型是什么(文本框、复选框等),都必须实现标准行为。它们不能继承公共基类,因为每种类型的输入区域已经派生自特定类(TextInputBox 等)
也许通过继承层次结构我可以找到一个共同的祖先,比如 WebControl,但我没有开发框架类 WebControl,它也没有暴露我需要的东西。
使用扩展方法,我可以:
1)扩展WebControl类,然后在我所有的输入类上获取我的统一标准行为
2)或者让我所有的类都从一个接口派生,比如IInputZone,并用方法扩展这个接口。我现在可以在所有输入区域上调用与接口相关的扩展方法。由于我的输入区域已经从多个基类派生,因此我实现了一种多重继承。
扩展方法有很多很好的例子……尤其是在上面发布的 IEnumerables 上。
例如,如果我有一个IEnumerable<myObject>
我可以创建和扩展的方法IEnumerable<myObject>
mylist List<myObject>;
...创建列表
mylist.DisplayInMyWay();
没有扩展方法将不得不调用:
myDisplayMethod(myOldArray); // can create more nexted brackets.
另一个很好的例子是快速创建一个循环链表!
我不能把它归功于它!
现在结合这些并使用扩展方法代码如下。
myNode.NextOrFirst().DisplayInMyWay();
而不是
DisplayInMyWay(NextOrFirst(myNode)).
使用扩展方法它更整洁、更容易阅读并且更面向对象。也非常接近:
myNode.Next.DoSomething()
把它展示给你的同事!:)