C#的“非信奉者”在问我扩展方法的目的是什么。我解释说,然后您可以向已定义的对象添加新方法,尤其是当您不拥有/控制原始对象的源时。
他提出了“为什么不只是向自己的班级添加方法呢?” 我们一直在(很好的方式)走来走去。我的一般回答是它是工具带中的另一个工具,而他的回答是这是对工具的无用浪费……但是我认为我会得到一个更“开明”的答案。
在哪些情况下使用了扩展方法而又没有(或不应该)使用扩展方法添加到自己的类中?
C#的“非信奉者”在问我扩展方法的目的是什么。我解释说,然后您可以向已定义的对象添加新方法,尤其是当您不拥有/控制原始对象的源时。
他提出了“为什么不只是向自己的班级添加方法呢?” 我们一直在(很好的方式)走来走去。我的一般回答是它是工具带中的另一个工具,而他的回答是这是对工具的无用浪费……但是我认为我会得到一个更“开明”的答案。
在哪些情况下使用了扩展方法而又没有(或不应该)使用扩展方法添加到自己的类中?
Extensions.To(1, 10)
是没有意义的,1.To(10)
是描述性的。当然,我只是说说而已。实际上,在某些情况下,一个“不可能”使用扩展方法代替了静态方法(例如,反射),另一种情况是dynamic
。
Answers:
我认为扩展方法在编写代码时有很大帮助,如果将扩展方法添加到基本类型中,则可以在智能感知中快速获得它们。
我有一个格式提供程序来格式化文件大小。要使用它,我需要写:
Console.WriteLine(String.Format(new FileSizeFormatProvider(), "{0:fs}", fileSize));
创建一个扩展方法,我可以编写:
Console.WriteLine(fileSize.ToFileSize());
更干净,更简单。
LongToFileSize(fileSize)
,它既简洁又可以说同样清晰。
扩展方法的唯一优点是代码可读性。而已。
扩展方法使您可以执行以下操作:
foo.bar();
代替这个:
Util.bar(foo);
现在,C#中有很多类似的东西。换句话说,C#中的许多功能看似微不足道,它们本身并没有很大的好处。但是,一旦开始将这些功能组合在一起,就会开始看到比其各个部分的总和稍大的东西。LINQ从扩展方法中受益匪浅,因为没有它们,LINQ查询将几乎无法读取。如果没有扩展方法,LINQ是可能的,但是不切实际。
扩展方法很像C#的部分类。就它们自己而言,它们不是很有帮助,而且看起来微不足道。但是,当您开始使用需要生成代码的类时,部分类就会变得更加有意义。
不要忘记工具!当在类型Foo上添加扩展方法M时,您将在Foo的智能感知列表中获得“ M”(假设扩展类在作用域内)。这使得'M'比MyClass.M(Foo,...)更容易找到。
归根结底,这只是别处的静态方法的语法糖,但就像买房一样:“位置,位置,位置!” 如果它挂在类型上,人们会发现它!
String
)附带了相当长的实例方法列表,因此此问题并非特定于扩展方法。
我对扩展方法的某些最佳用途是能够:
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 ();
我想在这里支持其他提到提高代码可读性的答案,这是扩展方法背后的重要原因。我将通过两个方面对此进行演示:方法链接与嵌套方法调用,以及杂乱无章的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.
我同意扩展方法可以提高代码的可读性,但是除了静态帮助器方法外,其他什么都没有。
IMO使用扩展方法向您的类添加行为的方法可以是:
令人困惑:程序员可能会认为方法是扩展类型的一部分,因此无法理解为什么在不导入扩展名空间的情况下方法消失了。
反模式:您决定使用扩展方法将行为添加到框架中的类型,然后将其交付给进行单元测试的人员。现在,他陷入了一个框架,其中包含一堆他无法伪造的方法。
扩展方法实际上是Martin Fowler的书中的“引入外部方法”重构的.NET (向下到方法签名)。它们具有基本相同的好处和陷阱。他在此重构的部分中说,当您无法修改应该真正拥有该方法的类时,这是一种变通方法。
我使用它们来重用我的对象模型类。我有一堆类,它们代表数据库中的对象。这些类仅在客户端使用以显示对象,因此基本用法是访问属性。
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的文章。
还请记住,添加扩展方法是为了帮助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的行中……)
我只有一个字要讲:可维护性这是扩展方法使用的关键
我认为扩展方法有助于编写更清晰的代码。
不用像朋友建议的那样在类中放入新方法,而是将其放在ExtensionMethods命名空间中。这样,您可以保持班级的逻辑秩序。并非真正直接与您的类打交道的方法不会使它混乱。
我觉得扩展方法可以使您的代码更清晰,更合理地组织。
我爱他们建立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输出准备自定义逻辑,扩展方法使您可以添加此功能,而无需在类中混合表示和逻辑。
我发现扩展方法对于匹配嵌套的通用参数很有用。
这听起来有点奇怪-但是说我们有一个泛型类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);
就是说,我不确定分界线应该在哪里-方法何时应为扩展方法,何时应为静态辅助方法?有什么想法吗?
我的屏幕上有输入区,无论它们的确切类型是什么(文本框,复选框等),所有输入区都必须实现标准行为。它们不能继承公共基类,因为每种类型的输入区域都已经从特定类(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()
证明给你的同事!:)