您发现扩展方法的哪些优势?[关闭]


84

C#的“非信奉者”在问我扩展方法的目的是什么。我解释说,然后您可以向已定义的对象添加新方法,尤其是当您不拥有/控制原始对象的源时。

他提出了“为什么不只是向自己的班级添加方法呢?” 我们一直在(很好的方式)走来走去。我的一般回答是它是工具带中的另一个工具,而他的回答是这是对工具的无用浪费……但是我认为我会得到一个更“开明”的答案。

在哪些情况下使用了扩展方法而又没有(或不应该)使用扩展方法添加到自己的类中?


2
我认为人们可以提出(并且已经提出)支持扩展方法的观点肯定是正确的……但是,绝对没有一种情况可以用静态方法代替扩展方法。扩展方法真的只是静态方法,访问不同的方式。只是要记住一点。
丹涛

@DanTao,一个简单的事情,就是使用扩展名无法替代的一件事就是它们的命名。Extensions.To(1, 10)是没有意义的,1.To(10)是描述性的。当然,我只是说说而已。实际上,在某些情况下,一个“不可能”使用扩展方法代替了静态方法(例如,反射),另一种情况是dynamic
nawfal

Answers:


35

我认为扩展方法在编写代码时有很大帮助,如果将扩展方法添加到基本类型中,则可以在智能感知中快速获得它们。

我有一个格式提供程序来格式化文件大小。要使用它,我需要写:

Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));

创建一个扩展方法,我可以编写:

Console.WriteLine(fileSize.ToFileSize());

更干净,更简单。


4
为您的扩展方法使用更具描述性的名称将使其更整洁。例如fileSize.ToFormattedString();
David Alpert,2009年

1
嗯,ToFormattedFizeSize()可能是Long类型的扩展方法,ToFormattedString()不是描述性名称
EduardoCampañó09年

3
指出了这一点。当我读取fileSize.ToFileSize()时,我问为什么重复?它实际上在做什么?我知道这只是一个例子,但描述性名称有助于使其简洁明了。
David Alpert,2009年

1
您说得对,就像测试一样,选择正确的名称非常重要
EduardoCampañó09年

5
但是,由于您提供的示例不一定是扩展方法,因此,这实际上并不能直接回答问题。您可能曾经有过LongToFileSize(fileSize),它既简洁又可以说同样清晰。
丹涛

83

扩展方法的唯一优点是代码可读性。而已。

扩展方法使您可以执行以下操作:

foo.bar();

代替这个:

Util.bar(foo);

现在,C#中有很多类似的东西。换句话说,C#中的许多功能看似微不足道,它们本身并没有很大的好处。但是,一旦开始将这些功能组合在一起,就会开始看到比其各个部分的总和稍大的东西。LINQ从扩展方法中受益匪浅,因为没有它们,LINQ查询将几乎无法读取。如果没有扩展方法,LINQ是可能的,但是不切实际。

扩展方法很像C#的部分类。就它们自己而言,它们不是很有帮助,而且看起来微不足道。但是,当您开始使用需要生成代码的类时,部分类就会变得更加有意义。


1
如果您使用x.Foo(something).Bar(somethingelse).Baz(yetmoresomething)之类的链式方法,则这一点变得尤为重要。这在IEnumerable <>扩展方法中
最为盛行,

2
我没有得到的是foo.bar()向我暗示bar()是foo的一种方法。因此,当它不起作用时,我最终会在错误的位置寻找。我不确定这实际上对我来说是可读性的改进。
马丁·布朗

3
在VS IDE中右键单击bar()并选择“转到定义”将把您带到正确的地方。
杰夫·马丁

9
98%的语言构造的唯一优势是代码可读性。分支运算符,标签,跳转和增量之后的所有内容都可以使您的生活更轻松。
Giovanni Galbo,2009年

2
并非完全如此,在某些情况下,扩展方法是扩展类的唯一方法。除了密封类,我还在非常独特的情况下使用它们。请参阅下面的我的帖子。
Edwin Jarvis,2009年

34

不要忘记工具!当在类型Foo上添加扩展方法M时,您将在Foo的智能感知列表中获得“ M”(假设扩展类在作用域内)。这使得'M'比MyClass.M(Foo,...)更容易找到。

归根结底,这只是别处的静态方法的语法糖,但就像买房一样:“位置,位置,位置!” 如果它挂在类型上,人们会发现它!


2
这也有一点负面影响:扩展方法(例如System.Linq的方法用较长的列表填充了IntelliSense自动完成功能,因此导航起来有些困难。)
Jared Updike 2010年

@Jared:...但仅当您已导入扩展方法所在的名称空间时,您实际上才对这种“ IntelliSense污染”有少量控制。其次,别忘了BCL中有一些类(例如String)附带了相当长的实例方法列表,因此此问题并非特定于扩展方法。
stakx-不再贡献2012年

29

我遇到的扩展方法的另外两个好处是:

  • 流利的接口可以封装在静态的扩展方法类中,从而实现核心类与其流利的扩展之间的关注点分离。我已经看到可以实现更大的可维护性。
  • 扩展方法可以与接口无关,从而允许您指定合同(通过接口)和相关的一系列基于接口的行为(通过扩展方法),再次提供了关注点分离。一个例子是LINQ的扩展方法喜欢Select(...)Where(...)等红关闭IEnumerable<T>接口。

1
您能否为这两点引用一些代码示例,以帮助他们更好地理解?这似乎是一个有趣的观点。
RBT

是的,第二点的例子很好。
2013年

26

我对扩展方法的某些最佳用途是能够:

  1. 扩展第三方对象的功能(无论是商业对象还是公司内部的对象,但由单独的小组管理),在许多情况下,这些对象将标记为sealed
  2. 为接口创建默认功能,而无需实现抽象类

例如,IEnumerable<T>。尽管它具有丰富的扩展方法,但令我感到烦恼的是它没有实现通用的ForEach方法。所以,我做了我自己的:

public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action)
{
    foreach ( var o in enumerable )
    {
        action(o);
    }
}

瞧,IEnumerable<T>无论实现类型如何,我所有的对象,无论我是否编写它或其他人,现在都可以ForEach通过在代码中添加适当的“使用”语句来拥有一种方法。


3
+1我喜欢这个扩展程序,我也写了一个扩展程序。太有用了
马斯洛(Maslow)2009年

10
请注意,该方法必须是静态的。建议您在决定使用此扩展程序之前先阅读blogs.msdn.com/ericlippert/archive/2009/05/18/…
TrueWill 2010年

不理解在IEnumerable上实现ForEach扩展方法的原因-blogs.msdn.microsoft.com/ericlippert/2009/05/18/…–
RBT

12

使用扩展方法的主要原因之一是LINQ。如果没有扩展方法,在LINQ中可以做的很多事情将非常困难。Where(),Contains(),Select扩展方法意味着将更多功能添加到现有类型中,而无需更改其结构。


4
它不是“之一”,它是扩展方法的原因。
gbjbaanb 2010年

3
为何如此?这是拥有扩展方法的众多原因之一,而不是唯一的要求。我可以不使用LINQ而使用扩展方法。
雷·布伊森

我认为他的意思是这是创建它们的原因,但是,是的,他听起来像它们是它们的唯一正确用法。
布伦特·里滕豪斯

9

关于扩展方法的优点有很多答案。如何解决不利条件

最大的缺点是,如果在相同的上下文中具有常规方法和具有相同签名的扩展方法,则不会出现编译器错误或警告。

假设您创建一个应用于特定类的扩展方法。然后,有人在该类本身上创建具有相同签名的方法。

您的代码将被编译,甚至可能不会出现运行时错误。 但是,您不再运行与以前相同的代码。


1
4.在扩展您不拥有的类型之前,请三思。如果只为自己拥有的类型编写扩展方法,则不必担心扩展被更改的扩展类型所破坏。另一方面,如果您要扩展其他人的类型,则本质上是他们的摆布。
DavidRR 2014年

1
...或者,您可以通过选择不太可能被其他人使用的名称或通过最小化您定义的扩展方法的总数(即减小表面积)来最小化这种风险。一种执行此操作的技术可能是编写一个扩展方法,该方法将基础对象转换为您控制的其他类型。
DavidRR 2014年

扩展方法(《 C#编程指南》):通常,与实现自己的方法相比,调用扩展方法的频率可能会更多。
DavidRR 2014年


4

我个人对扩展方法的观点是,它们非常适合OOP设计:考虑简单的方法

bool empty = String.IsNullOrEmpty (myString)

相较于

bool empty = myString.IsNullOrEmpty ();

我看不到您的示例与OOP有什么关系。你什么意思?
Andrew Hare

1
它“似乎”认为该方法属于对象
Bluenuance

3
这是一个不好的例子(可以抛出NullReferenceException),但是他处在正确的轨道上。一个更好的例子可能是将Math.abs(-4)替换为-4.abs();。
Outlaw程序员,

15
就我所知(和我的经验,如果有内存的话),myString.IsNullOrEmpty()不需要引发NullReferenceException,因为扩展方法不需要对象实例即可触发。
David Alpert,2009年

1
我个人不喜欢旨在与null实例一起使用的扩展方法-对读者来说,这看起来像一件事,但随后又做了其他事情。
马克·辛普森

4

上面有大量关于可以使用哪些扩展方法的好答案。

我的简短回答是-他们几乎消除了工厂的需要。

我只是指出它们不是一个新概念,它们最大的验证之一是它们是Objective-C(类别)。它们为基于框架的开发增加了很大的灵活性,以至于NeXT拥有NSA和华尔街金融建模者作为主要用户。

REALbasic还将它们作为扩展方法来实现,并且它们在简化开发方面具有相似的用途。


4

我想在这里支持其他提到提高代码可读性的答案,这是扩展方法背后的重要原因。我将通过两个方面对此进行演示:方法链接与嵌套方法调用,以及杂乱无章的LINQ查询和静态类名称。


让我们以这个LINQ查询为例:

numbers.Where(x => x > 0).Select(x => -x)

这两个WhereSelect是扩展方法,在静态类中定义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.

3

确实可以将您的(扩展)方法直接添加到类中。但是,并非所有类都是您编写的。核心库或第三方库中的类通常是关闭的,没有扩展方法就不可能获得语法糖。但是请记住,扩展方法就像(例如)中的(静态)独立方法一样。C ++


3

扩展方法还可以帮助您清洁类和类依赖项。例如,在使用Foo的任何地方,您都可能需要Foo类的Bar()方法。但是,您可能希望在另一个程序集中使用.ToXml()方法,并且仅用于该程序集。在这种情况下,可以在该程序集中而不是原始程序集中添加必需的System.Xml和/或System.Xml.Linq依赖项。

好处:定义类程序集中的依赖项减少到仅需使用,并且将阻止其他消耗程序集使用ToXml()方法。请参阅此PDC演示以获取更多参考。


3

我同意扩展方法可以提高代码的可读性,但是除了静态帮助器方法外,其他什么都没有。

IMO使用扩展方法向您的类添加行为的方法可以是:

令人困惑:程序员可能会认为方法是扩展类型的一部分,因此无法理解为什么在不导入扩展名空间的情况下方法消失了。

反模式:您决定使用扩展方法将行为添加到框架中的类型,然后将其交付给进行单元测试的人员。现在,他陷入了一个框架,其中包含一堆他无法伪造的方法。


开发人员会陷入一堆他无法伪造的方法,这不是真的。这些扩展方法最终会在它们正在扩展的类/接口上调用某个方法,并且该方法是虚拟的,因此可以将其拦截。
PatrikHägne2010年

1
好吧,这取决于扩展方法中执行的操作。假设您在接口上使用提供的扩展方法,甚至都不知道它们是扩展方法。然后,您将不会伪造对接口的方法调用,但是您意识到实际上是在调用静态方法。基于代理的伪造API无法拦截静态方法调用,因此您唯一的选择是对扩展方法进行反思,以找出实际伪造的方法。当与IMO进行单元测试结合时仍然是反模式。
HaraldV 2011年


2

我主要把扩展方法看作是承认它们不应该禁止自由功能。

在C ++社区中,通常优先选择免费的非成员函数而不是成员,这是一种良好的OOP做法,因为这些函数不会通过访问不需要的私有成员来破坏封装。扩展方法似乎是实现同一目标的一种回旋方式。也就是说,对于无法访问私有成员的静态函数,使用了更简洁的语法。

扩展方法不过是语法糖,但我认为使用它们没有任何危害。


2
  • 对象本身的智能感知,而不必调用某些难看的实用程序函数
  • 对于转换函数,可以将“ XToY(X x)”更改为“ ToY(this X x)”,从而得到漂亮的x.ToY()而不是难看的XToY(x)。
  • 扩展您无法控制的课程
  • 当不希望向类本身添加方法时,扩展类的功能。例如,您可以保持业务对象简单且无逻辑,并在扩展方法中添加具有难看依赖性的特定业务逻辑

2

我使用它们来重用我的对象模型类。我有一堆类,它们代表数据库中的对象。这些类仅在客户端使用以显示对象,因此基本用法是访问属性。

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定义文件并生成这些存根类的编译器,这些存根类表示我在数据库中拥有的某些对象。在这种情况下,唯一的方法是扩展它们。


1
您可以为此使用部分类...
JJoos 2010年


1

在上一个项目中,我使用扩展方法将Validate()方法附加到业务对象。我之所以这样说是合理的,因为业务对象是可序列化数据传输的对象,并且将在不同的域中使用,因为它们将在一般的电子商务实体(例如产品,客户,商人等)中使用。在不同的域中,业务规则也可能是不同的,所以我封装了附加到我的数据传输对象基类的Validate方法中的最新绑定验证逻辑。希望这是有道理的:)


1

扩展方法非常有用的一种情况是在使用ASMX Web服务的客户端应用程序中。由于进行了序列化,Web方法的返回类型不包含任何方法(客户端上仅这些类型的公共属性可用)。

扩展方法允许将功能(在客户端)添加到Web方法返回的类型中,而不必在客户端上创建另一个对象模型或大量包装类。



1

还请记住,添加扩展方法是为了帮助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并使其成为扩展方法出它们。

这一点显然是扩展方法的一个优点(在其他方法中),并且在涉及方法链接的每种类似情况下,您都可以从中受益。

尤其是,这是我发现任何子类都可以调用的基类方法,并返回对该子类(具有子类类型)的强类型引用的一种更为优雅和令人信服的方法。

例子(好吧,这种情况很俗气):晚安之后,动物睁开眼睛然后大哭;每只动物都以相同的方式睁开眼睛,而狗则吠叫而鸭子叫wa。

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或之类的特定子类方法Duck。注释为* 1和* 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();

...如果涉及更多的链接,则可能会给代码带来很大的负担(尤其是要知道type参数将始终<Dog>在Goofy的行和<Duck>Donald的行中……)


1
我第一次见过“ kwak”。这是另一个国家常见的拼写吗?我见过的唯一拼写是“嘎嘎”。
布伦特·里滕豪斯

0

我只有一个字要讲:可维护性这是扩展方法使用的关键


4
我认为您可能需要多个单词,因为我不明白这意味着什么。
Outlaw程序员,

2
我实际上会说这是反对扩展方法的论点。扩展方法的风险之一是它们可以无处不在,每个人都可以创建。如果不小心使用,可能会发生野生生长。
鲍里斯·卡伦斯

使用EM时,您可以轻松找到所有参考。另外,为了全局更改一种方法,您只能从一个地方进行更改
Tamir 2009年

0

我认为扩展方法有助于编写更清晰的代码。

不用像朋友建议的那样在类中放入新方法,而是将其放在ExtensionMethods命名空间中。这样,您可以保持班级的逻辑秩序。并非真正直接与您的类打交道的方法不会使它混乱。

我觉得扩展方法可以使您的代码更清晰,更合理地组织。



0

我爱他们建立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输出准备自定义逻辑,扩展方法使您可以添加此功能,而无需在类中混合表示和逻辑。


0

我发现扩展方法对于匹配嵌套的通用参数很有用。

这听起来有点奇怪-但是说我们有一个泛型类MyGenericClass<TList>,并且我们知道TList本身是泛型的(例如List<T>),我不认为有一种方法可以从列表中挖掘出嵌套的“ 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);

就是说,我不确定分界线应该在哪里-方法何时应为扩展方法,何时应为静态辅助方法?有什么想法吗?


0

我的屏幕上有输入区,无论它们的确切类型是什么(文本框,复选框等),所有输入区都必须实现标准行为。它们不能继承公共基类,因为每种类型的输入区域都已经从特定类(TextInputBox等)派生。

也许通过进入继承层次结构,我可以找到一个像WebControl这样的共同祖先,但是我没有开发框架类WebControl,也没有暴露我所需要的。

使用扩展方法,我可以:

1)扩展WebControl类,然后在所有输入类上获得我的统一标准行为

2)或者使我所有的类都从一个接口(例如IInputZone)派生,并使用方法扩展此接口。现在,我将能够在所有输入区域上调用与接口相关的扩展方法。由于我的输入区域已经从多个基类派生,因此我实现了一种多重继承。


0

扩展方法的例子很多,尤其是上面发布的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()

证明给你的同事!:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.