您找到了扩展方法的哪些优点?

时间:2009-01-28 14:55:55

标签: c# extension-methods

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

他提出“为什么不在你自己的课堂上添加一个方法?”我们一直在四处走动(以一种很好的方式)。我的一般回答是,它是工具带中的另一个工具,他的回答是它是一种无用的工具浪费......但我认为我会得到一个更“开明”的答案。

您使用的扩展方法有哪些情况,您不能(或不应该)使用添加到您自己的类中的方法?

30 个答案:

答案 0 :(得分:82)

扩展方法的唯一优势是代码可读性。就是这样。

扩展方法允许您这样做:

foo.bar();

而不是:

Util.bar(foo);

现在C#中有很多东西都是这样的。换句话说,C#中有许多功能似乎微不足道,并且本身并没有很大的好处。然而,一旦开始将这些功能组合在一起,您就会开始看到比其各部分总和更大的东西。 LINQ从扩展方法中获益很大,因为如果没有它们,LINQ查询几乎是不可读的。 LINQ可能可能没有扩展方法,但不实用。

扩展方法很像C#的部分类。他们自己并不是很有帮助,而且似乎微不足道。但是当你开始使用需要生成代码的类时,部分类开始变得更有意义。

答案 1 :(得分:33)

不要忘记工装!当你在Foo类型上添加一个扩展方法M时,你会在Foo的intellisense列表中得到'M'(假设扩展类在范围内)。这使得'M'比MyClass.M(Foo,...)更容易找到。

在一天结束时,它只是其他静态方法的语法糖,但就像买房子一样:'位置,位置,位置!'如果它挂在类型上,人们就会找到它!

答案 2 :(得分:33)

我认为扩展方法在编写代码时有很大帮助,如果你将扩展方法添加到基本类型中,你会在intellisense中快速获得它们。

我有format a file size的格式提供程序。要使用它我需要写:

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

创建扩展方法我可以写:

Console.WriteLine(fileSize.ToFileSize());

更清洁,更简单。

答案 3 :(得分:29)

我遇到的扩展方法还有两个好处:

  • 一个流畅的接口可以封装在一个静态类的扩展方法中,从而实现核心类与它的流畅扩展之间的关注点分离;我已经看到了实现更高的可维护性
  • 扩展方法可以挂起接口,从而允许您指定合同(通过接口)和相关的一系列基于接口的行为(通过扩展方法),再次提供关注点分离。

答案 4 :(得分:26)

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

  1. 扩展第三方对象的功能(无论是商业用途还是我公司内部对象,但由一个单独的小组管理),在许多情况下会被标记为sealed
  2. 创建接口的默认功能,而无需实现抽象类
  3. IEnumerable<T>为例。虽然它有丰富的扩展方法,但我发现它没有实现通用的ForEach方法很烦人。所以,我自己做了:

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

    Voila,我的所有IEnumerable<T>个对象,不论其实现类型如何,以及我是否通过在我的代码中添加适当的“using”语句来编写它或其他人现在都有ForEach方法。

答案 5 :(得分:12)

使用扩展方法的一个重要原因是LINQ。没有扩展方法,你在LINQ中可以做的很多事情都会非常困难。 Where(),Contains(),Select扩展方法意味着在不改变其结构的情况下将更多功能添加到现有类型。

答案 6 :(得分:9)

关于扩展方法的优点,有很多答案;如何解决缺点

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

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

您的代码将编译,您甚至可能不会收到运行时错误。 但您不再运行与之前相同的代码。

答案 7 :(得分:8)

流畅的界面和上下文敏感度,正如Greg Young在CodeBetter

所证明的那样

答案 8 :(得分:4)

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

bool empty = String.IsNullOrEmpty (myString)

相比
bool empty = myString.IsNullOrEmpty ();

答案 9 :(得分: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.

答案 10 :(得分:4)

上面有大量关于扩展方法的重要答案。

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

我只想指出它们不是一个新概念,它们最大的验证之一是它们是Objective-C( categories )中的杀手级功能。他们为基于框架的开发增加了很大的灵活性,NeXT将NSA和华尔街金融建模师作为主要用户。

REALbasic还将它们实现为扩展方法,它们在类似的用途中简化了开发。

答案 11 :(得分:3)

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

好处:定义类程序集中的依赖项减少到只有必需品,其他消耗程序集将被阻止使用ToXml()方法。有关详细信息,请参阅this PDC presentation

答案 12 :(得分:3)

您可以将您的(扩展名)方法直接添加到您的班级中。但并非所有课程都是由你写的。来自核心库或第三方库的类通常是关闭的,没有扩展方法就不可能获得合成糖。但请记住,扩展方法就像(静态)独立方法一样。 C ++

答案 13 :(得分:2)

我主要将扩展方法视为承认他们不应该禁止使用免费功能。

在C ++社区中,通常认为优秀的OOP实践更喜欢免费的非成员函数而不是成员,因为这些函数不会通过访问他们不需要的私有成员来破坏封装。扩展方法似乎是实现相同目标的迂回方式。也就是说,静态函数的更清晰的语法,无法访问私有成员。

扩展方法只不过是语法糖,但我认为使用它们没什么害处。

答案 14 :(得分:2)

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

答案 15 :(得分: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定义文件并生成这些存根类,表示我在数据库中的一些对象。在这种情况下,唯一的方法是扩展它们。

答案 16 :(得分:2)

扩展方法实际上是来自Martin Fowler's Book的"Introduce Foreign Method"重构的.NET结合(直到方法签名)。他们带来了基本相同的好处和陷阱。在关于这个重构的部分中,他说当你无法修改应该真正拥有该方法的类时,它们是一个解决方法。

答案 17 :(得分:2)

我同意扩展方法提高了代码的可读性,但它实际上只不过是静态辅助方法。

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

混淆: 程序员可能认为方法是扩展类型的一部分,因此无法理解在未导入扩展名称空间时方法消失的原因。

反模式: 您决定使用扩展方法向框架中的类型添加行为,然后将它们发送给进入单元测试的某个人。现在,他坚持使用一个框架,其中包含一些他无法伪造的方法。

答案 18 :(得分:1)

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

答案 19 :(得分: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));

偶然地,WhereLast的类型参数不需要明确提及,因为这两种方法的第一个参数(参数由关键字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这样的特定子类方法或Duck或其他什么。注释为* 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();

...如果涉及更多链接,可以对代码进行大量权衡(特别是知道类型参数在Goofy的行上始终为<Dog>而在唐纳德的行上为<Duck> ...)< / p>

答案 20 :(得分:1)

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

扩展方法允许使用(在客户端)向Web方法返回的类型添加功能,而无需在客户端创建另一个对象模型或众多包装类。

答案 21 :(得分:1)

它允许C#更好地支持动态语言,LINQ和其他一些东西。查看Scott Guthrie's article

答案 22 :(得分:0)

有很多很好的扩展方法示例。特别是在上面发布的IEnumerables上。

e.g。如果我有IEnumerable<myObject>我可以为IEnumerable<myObject>

创建和扩展方法
mylist List<myObject>;

...创建列表

mylist.DisplayInMyWay();

没有扩展方法必须调用:

myDisplayMethod(myOldArray); // can create more nexted brackets.

另一个很好的例子是在flash中创建循环链接列表!

我可以&#39;值得赞扬!

circlular linked list using extension Methods

现在结合这些并使用扩展方法代码如下所示。

myNode.NextOrFirst().DisplayInMyWay();

而不是

DisplayInMyWay(NextOrFirst(myNode)).

使用扩展方法它更整洁,更容易阅读,更面向对象。 也非常接近:

myNode.Next.DoSomething()

向你的同事展示! :)

答案 23 :(得分:0)

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

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

使用扩展方法,我可以:

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

2)或者让我的所有类派生自一个接口,比如说IInputZone,并用方法扩展这个接口。我现在可以在所有输入区域调用与接口相关的扩展方法。因此,我实现了一种多重继承,因为我的输入区域已经从多个基类派生。

答案 24 :(得分:0)

扩展方法可用于在C#中创建kind of mixin

反过来,这为正交概念提供了更好的关注点分离。以这个answer为例。

这也可用于在C#中启用角色,这是DCI architecture的核心概念。

答案 25 :(得分:0)

我发现扩展方法对于匹配嵌套的泛型参数非常有用。

这听起来有点奇怪 - 但是说我们有一个泛型类MyGenericClass<TList>,我们知道TList本身是通用的(例如List<T>),我认为没有办法在没有扩展方法或静态辅助方法的情况下从List中挖出嵌套的'T'。如果我们只有我们可以使用的静态辅助方法,那么(a)很丑,(b)会迫使我们将属于该类的功能移动到外部位置。

e.g。要检索元组中的类型并将它们转换为方法签名,我们可以使用扩展方法:

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);

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

答案 26 :(得分: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输出准备自定义逻辑 - 扩展方法允许您添加此功能,而无需在类中混合表示和逻辑。

答案 27 :(得分:0)

它允许您的编辑器/ IDE自动完成智能建议。

答案 28 :(得分:0)

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

答案 29 :(得分:0)

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

而不是像你朋友建议的那样在你的类中放入一个新方法,而是将它放在ExtensionMethods命名空间中。通过这种方式,您可以为您的班级保持合理的秩序感。没有真正直接处理你的课程的方法不会让它变得混乱。

我认为扩展方法可以使您的代码更清晰,更有条理。