你发现了什么扩展方法的优点?
C#的“不相信者”问我扩展方法的目的是什么。 我解释说,你可以添加新的方法到已经定义的对象,特别是当你不拥有/控制源的原始对象。
他提出了“为什么不给自己的class级添加一个方法?” 我们一直在围绕(好方法)。 我的一般反应是,它是工具栏中的另一个工具,他的回答是对工具的无用浪费……但我想我会得到一个更“开明”的答案。
什么是你已经使用的扩展方法,你不能有(或不应该)使用方法添加到你自己的类的一些情况?
我认为扩展方法在编写代码时有很大的帮助,如果将扩展方法添加到基本types中,您将在智能感知中快速获取它们。
我有格式提供程序来格式化文件大小 。 要使用它,我需要写:
Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));
创build一个扩展方法我可以写:
Console.WriteLine(fileSize.ToFileSize());
更清洁,更简单。
扩展方法的唯一优点是代码可读性。 而已。
扩展方法允许你这样做:
foo.bar();
而不是这个:
Util.bar(foo);
现在在C#中有很多这样的东西。 换句话说,C#中有许多function看起来微不足道,本身并没有太大的好处。 但是,一旦你开始把这些特征结合在一起,你就会看到比它的部分总和还要大的东西。 LINQ对于扩展方法有很大的好处,因为没有它们,LINQ查询几乎是不可读的。 LINQ将是可能的,没有扩展方法,但不实际。
扩展方法很像C#的部分类。 他们本身并不是很有帮助,看起来微不足道。 但是当你开始使用需要生成代码的类时,部分类开始变得更有意义。
不要忘记工具! 当在Footypes上添加扩展方法M时,在Foo的智能感知列表中(假设扩展类在范围内)会得到“M”。 这使得'M'比MyClass.M(Foo,…)更容易find。
在一天结束的时候,这只是其他地方的静态方法的语法糖,但是像买房子一样:“位置,位置,位置! 如果挂在types上,人们会发现它!
我遇到过的扩展方法还有两个好处:
- 一个stream畅的接口可以被封装在一个静态类的扩展方法中,从而实现核心类与stream畅扩展之间的关注分离; 我已经看到,实现更大的可维护性
- 扩展方法可以挂起接口,从而允许您指定一个合约(通过接口)和一系列基于接口的行为(通过扩展方法),再次提供一个关注点分离。
我用于扩展方法的一些最佳用途是能够:
- 将function扩展到第三方对象(不pipe是商业还是内部的,但由一个单独的组pipe理),在许多情况下,这些function将被标记为
sealed
。 - 为接口创build默认function,而无需实现抽象类
以IEnumerable<T>
为例。 虽然它有丰富的扩展方法,但我发现它并没有实现一个通用的ForEach方法。 所以,我做了我自己的:
public void ForEach<T>(this IEnumerable<T> enumerable, Action<T> action) { foreach ( var o in enumerable ) { action(o); } }
瞧,所有我IEnumerable<T>
对象,无论实现types,是否我写了它或其他人现在有一个ForEach
方法,通过在我的代码中添加一个适当的“使用”语句。
使用扩展方法的一个重要原因是LINQ。 如果没有扩展方法,你可以在LINQ中做的很多事情是非常困难的。 Where(),Contains(),Select扩展方法意味着更多的function被添加到现有的types而不改变它们的结构。
stream畅的接口和上下文敏感性由Greg Young在CodeBetter上演示
关于扩展方法的优点有很多答案。 如何处理缺点 ?
最大的缺点是没有编译器错误或警告,如果你有一个常规的方法和相同的上下文中签名相同的扩展方法。
假设你创build了一个适用于特定类的扩展方法。 然后稍后有人在该类本身上创build一个具有相同签名的方法。
你的代码将被编译,甚至可能不会得到一个运行时错误。 但是你不再像以前那样运行相同的代码。
我个人对扩展方法的观点是,它们非常适合OOPdevise:考虑简单的方法
bool empty = String.IsNullOrEmpty (myString)
与…相比
bool empty = myString.IsNullOrEmpty ();
上面有很多关于什么扩展方法可以让你做的很好的答案。
我简短的回答是 – 他们几乎消除了对工厂的需求。
我只想指出,它们并不是一个新的概念,其中最大的validation之一就是它们是Objective-C( 类别 )中的一个杀手锏。 它们为基于框架的开发增加了很大的灵活性,NeXT将NSA和华尔街金融build模者作为主要用户。
REALbasic也将它们作为扩展方法来实现,并且它们也有相似的用处,简化了开发。
我想支持这里提到的其他答案,提高代码可读性作为扩展方法背后的重要原因。 我将通过以下两个方面来演示这一点:方法链接与嵌套方法调用,以及用无意义的静态类名称混淆LINQ查询。
我们以这个LINQ查询为例:
numbers.Where(x => x > 0).Select(x => -x)
Where
和Select
都是扩展方法,在静态类Enumerable
定义。 因此,如果扩展方法不存在,并且这些是普通的静态方法,那么最后一行代码基本上应该是这样的:
Enumerable.Select(Enumerable.Where(numbers, x => x > 0), x => -x)
看看这个查询刚刚得到了多less。
其次,如果你现在想引入你自己的查询操作符,你自然就没有办法在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演示文稿以作进一步参考
扩展方法实际上是由Martin Fowler书中的“引入外部方法”重构(直到方法签名)的.NET合并。 他们有基本相同的好处和陷阱。 在这个重构的章节中,他说,当你不能修改应该真正拥有这个方法的类的时候,他们是一个解决方法。
我主要看到扩展方法是承认也许他们不应该禁止自由function。
在C ++社区中,通常认为OOP实践中优先考虑免费的非成员函数,因为这些函数不会因为获得对不需要的私有成员的访问而破坏封装。 扩展方法似乎是一个迂回的方式来实现同样的事情。 也就是说,对于无法访问私有成员的静态函数,语法更清晰。
扩展方法只不过是语法糖,但我没有看到使用它们的任何伤害。
- 智能感知对象本身,而不必调用一些丑陋的实用function
- 对于转换函数,可以将“XToY(X x)”更改为“ToY(this X x)”,这会导致x.ToY()而不是丑陋的XToY(x)。
- 扩展你无法控制的课程
- 当不希望将类添加到类本身时,扩展类的function。 例如,您可以保持业务对象简单且无逻辑,并在扩展方法中添加具有丑陋依赖性的特定业务逻辑
我用它们来重用我的对象模型类。 我有一堆代表我在数据库中的对象的类。 这些类仅用于客户端显示对象,因此基本用法是访问属性。
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) {...} }
这样,我可以使用相同的类进行业务逻辑处理和用户界面显示,而不必在客户端重载不必要的代码。
关于这个解决scheme的一个有趣的事情是,我的对象模型类是使用Mono.Cecildynamic生成的,所以即使我想要添加业务逻辑方法也是非常困难的。 我有一个编译器,读取XML定义文件,并生成这些存根类,代表我在数据库中的一些对象。 在这种情况下唯一的办法是扩大他们。
我同意扩展方法增加了代码的可读性,但它实际上只是静态帮助方法。
国际海事组织使用扩展方法为您的类添加行为可以是:
令人困惑的是:程序员可能认为方法是扩展types的一部分,因此不理解为什么当扩展名称空间没有被导入时方法消失了。
反模式:您决定使用扩展方法将行为添加到框架中的types,然后将其传递给某个进入unit testing的人员。 现在他被困在一个包含一堆他不能伪造的方法的框架中。
我只有一个词来说明: 可维护性这是扩展方法使用的关键
它允许C#更好地支持dynamic语言,LINQ和其他一些东西。 看看Scott Guthrie的文章 。
在我上一个项目中,我使用扩展方法将Validate()方法附加到业务对象。 我certificate了这一点,因为业务对象的可序列化的数据传输对象,并将用于不同的领域,因为他们在一般的电子商务实体,如产品,客户,商家等在不同领域的业务规则可能是不同的,所以我封装在我的数据传输对象的基类中引入了Validate方法的后期validation逻辑。 希望这是有道理的:)
扩展方法非常有用的一种情况是使用ASMX Web服务的客户端应用程序。 由于序列化,Web方法的返回types不包含任何方法(只有这些types的公共属性在客户端上可用)。
可以使用扩展方法在客户端添加function(在客户端)返回到Web方法返回的types,而无需在客户端创build另一个对象模型或多个包装类。
另外请记住,扩展方法被添加作为一种方式来帮助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
的types参数不需要被明确地提及,因为它们可以被传递,这是由于这两个方法的第一个参数(通过关键字this
引入的参数并使它们成为扩展方法)。
这一点显然是扩展方法的一个优点,而且在涉及方法链接的每个类似的情况下,您都可以从中受益。
特别是,我发现有一个更优雅和令人信服的方式,有一个可由任何子类调用的基类方法,并返回一个强types的引用到这个子类(与子类的types)。
例子(好吧,这个场景完全是俗气):晚安之后,一只动物睁开眼睛,然后哭了起来; 每只动物都以同样的方式打开眼睛,而一只狗叫,一只鸭叫。
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会引发编译错误。
但是由于扩展方法的原因,我们可以有一种“知道被调用的子types的基本方法”。
请注意,一个简单的通用方法可能已经完成了这项工作,但以一种更为尴尬的方式:
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 } }
这一次,没有参数,因此没有可能的返回types推断。 电话可以是除了:
Goofy.OpenTheEyes<Dog>().Bark(); Donald.OpenTheEyes<Duck>().Kwak();
…如果涉及更多的链接,可以对代码进行很多的权衡(尤其是知道types参数总是在Goofy的行上是<Dog>
,而在Donald的行上是<Duck>
)。
我认为扩展方法有助于编写更清晰的代码。
不要像你的朋友build议的那样在你的类中放置一个新的方法,而是把它放在ExtensionMethods命名空间中。 通过这种方式,您可以保持对您class级的逻辑顺序。 那些不直接与你的class级打交道的方法不会让你感到困惑。
我觉得扩展方法使你的代码更加清晰,组织得当。
它允许你的编辑器/ IDE智能地自动完成build议。
我爱他们build立HTML。 经常有部分被重复使用,或者在函数有用的地方recursion地生成,否则将会破坏程序的stream程。
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输出准备自定义逻辑 – 扩展方法可让您添加此function,而无需在类中混合表示和逻辑。
我发现扩展方法可以用来匹配嵌套的generics参数。
这听起来MyGenericClass<TList>
– 但是说我们有一个generics类MyGenericClass<TList>
,我们知道TList本身是通用的(例如List<T>
),我不认为有办法挖掘出嵌套的“ T'从List没有扩展方法或静态辅助方法。 如果我们只有静态的帮助者方法,那就是(a)丑陋的,(b)会迫使我们把属于课堂的function移到外部的位置。
例如,检索元组中的types并将其转换为方法签名,我们可以使用扩展方法:
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);
这就是说,我不确定分界线应该在哪里 – 何时应该是一个扩展方法,什么时候应该是一个静态帮助方法? 有什么想法吗?
扩展方法可以用来在C#中创build一种mixin 。
这反过来又提供了更好的正交概念的关注分离。 以这个答案为例。
这也可以用来启用C#中的angular色 , CCI是DCI体系结构的核心概念。
我在屏幕上input区域,无论它们的确切types是什么(文本框,checkbox等),都必须实现标准行为。 由于每种types的input区域已经从特定的类(TextInputBox等)中派生出来,因此它们不能inheritance公共基类。
也许通过inheritance层次上去,我可以find像WebControl这样的共同的祖先,但是我没有开发框架类WebControl,也没有公开我需要的东西。
用扩展方法,我可以:
1)扩展WebControl类,然后获得我所有input类的统一标准行为
2)或者让我所有的类都从一个接口派生出来,比如说IInputZone,并用方法扩展这个接口。 现在我可以在所有input区域上调用与接口相关的扩展方法。 因此,我实现了一种多重inheritance,因为我的input区域已经从多个基类派生。
有很多很好的扩展方法的例子,特别是在IEnumerables上面。
例如,如果我有一个IEnumerable<myObject>
我可以为IEnumerable<myObject>
创build和扩展方法
mylist List<myObject>;
…创build列表
mylist.DisplayInMyWay();
没有扩展方法将不得不打电话:
myDisplayMethod(myOldArray); // can create more nexted brackets.
另一个很好的例子就是在闪存中创build一个循环链表
我可以为此承担责任!
循环链表使用扩展方法
现在结合这些和使用扩展方法的代码读取如下。
myNode.NextOrFirst().DisplayInMyWay();
而不是
DisplayInMyWay(NextOrFirst(myNode)).
使用扩展方法它更整洁,更容易阅读和更多的面向对象。 也非常接近:
myNode.Next.DoSomething()
显示给你的同事! 🙂