我发现它们是扩展现有类的一种非常自然的方式,特别是当您只需要将某些功能“点焊”到现有类时。
微软表示,“一般来说,我们建议您谨慎实施扩展方法,并且只在必要时才实施。”然而,扩展方法构成了Linq的基础;事实上,Linq是创建扩展方法的原因。
是否存在使用扩展方法而不是继承或组合的特定设计标准?他们劝阻什么标准?
答案 0 :(得分:17)
我们向C#MVP展示了提议的新语言功能(至少是那些有一半看到光明的机会)以获得早期反馈。我们经常在许多功能上获得的一些反馈是“我我会明智地使用此功能,并根据功能的设计原则,但我的goofball同事将会用这个东西搞疯了,写下一大堆不可维护的,不可理解的代码,我将在接下来的十年中遇到调试问题,所以尽我所能,请不要实现这个功能!“< / p>
虽然我对喜剧效果有点夸张,但这是我们非常认真对待的事情;我们希望设计新功能以鼓励他们使用,但不鼓励他们滥用。
我们担心扩展方法会被用来,例如,不加区分地随意扩展“对象”,从而创建一个很难理解,调试和维护的松散类型的泥球。
我个人特别关注你在“流利”编程中看到的“可爱”扩展方法。像
这样的东西6.Times(x=>Print("hello"));
呸。 “for”循环在C#中普遍可以理解和惯用; 不要变得可爱。
答案 1 :(得分:11)
问题在于扩展方法不一定清楚。
当您在代码中看到类似内容时:
myObject.Foo();
天生的本能是Foo()
是在myObject的类或myObject类的父类上定义的方法。
使用Extension方法,这是不正确的。几乎任何类型的方法都可以定义几乎任何类型(你可以在System.Object上定义一个扩展方法,虽然这是一个坏主意......)。这实际上增加了代码的维护成本,因为它降低了可发现性。
任何时候添加非显而易见的代码,甚至降低实施的“显而易见性”,都会产生长期可维护性的成本。
答案 2 :(得分:6)
过度使用扩展方法是不好的,原因与过度使用重载运算符的原因相同。让我解释一下。
让我们先看看运算符重载示例。当你看到代码时:
var result = myFoo + myBar;
您希望myFoo
和myBar
是数字类型,operator +
正在执行添加。这是合乎逻辑的,直观的,易于理解的。但是一旦运算符重载进入图片,您就无法确定myFoo + myBar
实际上在做什么 - +运算符可能会重载到意味着什么。您不仅可以阅读代码并找出正在发生的事情,而无需阅读表达式中涉及的所有类型的代码。现在,operator +
已经为String
或DateTime
等类型重载了 - 但是,在这些情况下,对添加的含义有直观的解释。更重要的是,在这些常见情况下,它为代码增添了许多表达能力。因此值得潜在的混淆。
那么所有这些与扩展方法有什么关系?嗯,扩展方法引入了类似的情况。没有扩展方法,当你看到:
var result = myFoo.DoSomething();
您可以假设DoSomething()
是myFoo
的方法或其中一个基类。这很简单,易于理解,甚至直观。但是使用扩展方法,DoSomething()
可以在任何地方定义 - 更糟糕的是,定义取决于代码文件中的using
语句集,并且可能会将许多类引入到mix,其中任何一个都可以主持DoSomething()
的实现。
现在不要误会我的意思。运算符重载和扩展方法都是有用且强大的语言功能。但请记住 - 以强大的力量来承担很大的责任。您应该在提高实现的清晰度或功能时使用这些功能。如果你不加选择地开始使用它们,它们会给你想要创造的东西增加混乱,复杂性和可能的缺陷。
答案 3 :(得分:3)
我遵循一个非常简单的规则,包括扩展方法和辅助类。
任何时候我都有一个可以降级为静态助手类的方法,我会在一般范围内权衡它的用处,我真的希望这个方法能够被共享吗?意义是否明确且对他人有用?
如果我能对这个问题回答“是”,我会将其创建为扩展方法。我有一个共享库,其中包含1个命名空间中的所有Extension方法,每个类文件定义为TypeNameExtension,因此很清楚在该类中可以期待什么,以便您可以轻松找到代码。
如果我质疑是否需要辅助方法作为全局可访问的用法,我将声明方法private static并将其留在拥有类中。
答案 4 :(得分:2)
当Borland发明它们(Delphi Class Helpers)时,指导原则已经是:“应该仅在特殊情况下使用”。
它们已经变得更加被接受(作为LINQ的一部分),但它们仍然是添加功能的奇怪方式。我认为里德对原因有很好的答案。
答案 5 :(得分:2)
我有一个不断扩展的扩展方法库,可以在BCL类上运行,以简化常见问题,乍一看看起来很混乱。坦率地说,我认为扩展方法使事情更具可读性。例如,进行if语句测试以查看数字是否在两个数字之间:
if (x <= right && x >= left )
{
// Do Something
}
如果right
在事故中小于left
怎么办?这究竟是做什么的?当变量简单地类似于x
和left
时,它很容易理解。但如果他们是长班的财产怎么办? if语句可以变得非常大,这会使你想要做的事情变得模糊不清。
所以我写了这个:
public static bool Between<T>(this T test, T minValue, T maxValue)
where T : IComparable<T>
{
// If minValue is greater than maxValue, simply reverse the comparision.
if (minValue.CompareTo(maxValue) == 1)
return (test.CompareTo(maxValue) >= 0 && test.CompareTo(minValue) <= 0);
else
return (test.CompareTo(minValue) >= 0 && test.CompareTo(maxValue) <= 0);
}
现在我可以改进我的if语句:
if (x.Between(left, right))
{
// Do Something
}
这是“非凡的”吗?我不这么认为......但我确实认为这是对可读性的重大改进,它允许对测试输入进行一些额外的检查以确保安全。
我认为微软希望避免的危险是人们编写了大量的扩展方法,重新定义了Equals
和ToString
。
答案 6 :(得分:1)
扩展方法可以被认为是面向方面的。我相信微软说,如果你有源代码,你应该改变原来的类。它与清晰度和易维护性有关。需要扩展方法的原因是它们扩展了现有对象的功能。对于LINQ,如果你深入研究它是如何工作的,可以使用各种类型的对象创建,这些类型是提前无法知道的。使用扩展方法可以添加一些行为。
例如,扩展.NET框架对象是有意义的,因为您没有源,但可能有一些行为要添加到对象。扩展字符串,DateTime和FrameworkElement类是可以接受的。为跨越应用程序边界的类创建扩展方法也是可以接受的。比如,如果您想共享DTO类并具有某些特定的UI行为,请仅在UI中创建该扩展方法。
使用动态语言,范例是不同的,但我相信你在谈论C#和VB.net。