使用扩展:权衡利弊与利弊

最近我问了一个关于如何清理我认为丑陋代码的问题。 一个建议是创建一个扩展方法,它将执行所需的function并返回我想要的function。 我的第一个想法是’太棒了! Extensions有多酷……“但经过一番思考后,我开始重新考虑使用Extensions ……

我主要担心的是,Extensions似乎是一种自定义的“快捷方式”,可以让其他开发人员难以遵循。 我理解使用扩展可以帮助使代码语法更容易阅读,但是如何跟随幕后的声音呢?

以我之前的问题代码片段为例:

if (entry.Properties["something"].Value != null) attribs.something = entry.Properties["something"].Value.ToString(); 

现在用扩展名替换它:

 public static class ObjectExtensions { public static string NullSafeToString(this object obj) { return obj != null ? obj.ToString() : String.Empty; } } 

并使用语法调用:

 attribs.something = entry.Properties["something"].Value.NullSafeToString(); 

定义一个方便的方法,但它真的值得另一个类对象的开销吗? 如果有人想重用我的代码片段而不理解扩展,会发生什么? 我可以像使用相同的结果一样轻松地使用语法:

 attribs.something = (entry.Properties["something"].Value ?? string.Empty).ToString() 

所以我做了一些挖掘,发现了一些关于使用Extensions的优点/缺点的文章。 对于那些倾向于看看以下链接的人:

MSDN:扩展方法

扩展方法最佳实践

扩展方法

我无法确定哪种方式更好。 自定义扩展执行我想要他们做的事情或更多显示的代码来完成相同的任务? 我真的很想学习“真正的”开发人员对这个主题的看法……

我个人认为扩展方法可读性的“问题”被夸大了。 如果您专注于使代码易于阅读,那么大多数情况下,这比执行代码更重要。 如果开发人员想要追踪并了解幕后实际发生的事情,他们总是可以点击执行。

扩展方法的主要问题是它们的发现方法 – 即通过指定的命名空间而不是指定的类。 虽然这是另一回事:)

我并不是建议你随意添加扩展方法,但是我会认真考虑你需要知道方法中每个表达式的工作方式,而不是通过它来查看它在广义上的作用。

编辑:您对术语的使用可能会误导您。 没有“扩展对象”这样的东西 – 只有“扩展方法”,它们必须存在于静态类型中。 所以你可能需要引入一个新类型,但你不会再创建任何对象

[OP]定义一个方便的方法,但它真的值得另一个类对象的开销吗?

在此方案中不会创建额外的类对象。 在引擎盖下,扩展方法与静态方法没有区别。 扩展方法容器有一个额外的元数据条目,但这是非常小的。

[OP]如果有人想重用我的代码片段而不理解扩展对象,会发生什么?

那么这将是教育他们的好时机:)。 是的,新开发人员可能不习惯使用扩展方法启动。 但这不是一个孤立的特征。 它在我在内部和网络上看到的所有代码示例中被越来越多地使用。 对于开发人员来说,这是绝对值得学习的东西。 我不认为它符合“期待人们知道的深奥”的范畴

在Extension方法中处理的唯一严重的怪异是:

  1. 如果左侧(显示您正在调用方法的对象)为null,则它们不必导致空引用exception。
    • 有时可能有用,但与预期相反,因此应谨慎使用。
  2. 它们无法通过对它们所适用的类/接口的reflection来访问。
    • 一般不是问题,但值得记住。
  3. 与其他扩展方法的名称冲突涉及冗长的分辨率规则序列
    • 如果你关心顺序是喜欢:
      1. 在当前模块中定义的扩展方法。
      2. 在当前名称空间或其任何一个父项中的数据类型内定义的扩展方法,子名称空间的优先级高于父名称空间。
      3. 在当前文件中的任何类型导入内定义的扩展方法。
      4. 在当前文件中的任何命名空间导入内定义的扩展方法。
      5. 在任何项目级类型导入中定义的扩展方法。
      6. 在任何项目级命名空间导入中定义的扩展方法。

[OP]如果有人想重用我的代码片段而不理解扩展对象,会发生什么?

如果实现它们的程序集不是项目中的引用,则扩展方法将不会在对象的intellisense中显示。 您的代码段也无法编译。 这可能会给其他开发人员带来一些混乱。

如果引用了扩展方法程序集,它将在intellisense中显示,但在对象的文档中将不会提及。 这可能会引起一些混乱。

但是,正如@JaredPar所提到的,作为一种技术的扩展方法越来越多地被使用,我希望大多数C#程序员能够了解它们。 因此,我不担心任何可能的混乱。

C#Extensions是.Net提供的另一个“工具”,以帮助您更好地编写代码。 它们的另一个优点是,它们处理null。 虽然它们似乎非常实用,但我尝试只在某些情况下使用它们才能真正整理我的代码,因为它们不是标准的编码方法,它们与其他类有点分离,因为它们必须在静态类中并且是静态自己。

让我们说它们的实现有点凌乱,但它们的使用更加整洁

同样重要的是,它们只存在于C#和VB.Net中(Java没有扩展)。 另一个重要的事实是Extensions没有优先于标准方法,这意味着如果一个方法是在一个与同一个类上的扩展方法同名的类中实现的,那么第一个方法是将被调用的方法,而不是扩展方法。

下面有三种情况我经常使用它们,为什么我使用它们以及可以解决同一问题的替代解决方案:

1.实现generics类的特定方法:我有一个generics类型,比如说集合List 。 我想做一个仅适用于特定类型列表的方法。 假设使用分隔符( "A", "B", "C", " sep " --> "A sep B sep C" )从字符串列表创建联合的方法:

 public static string union(this List stringList, String seperator) { String unionString = ""; foreach (string stringItem in stringList) { unionString += seperator + stringItem; } if (unionString != "") { unionString = unionString.Substring(seperator.Length); } return unionString; } 

如果我不想使用扩展,我将不得不创建一个新类“ StringCollection : List ”并在那里实现我的方法。 这主要不是问题,在大多数情况下实际上更好,但并非在所有情况下都是如此。 例如,如果您在许多情况下接收字符串列表中的所有数据,则每次要使用union时都不必在StringCollections转换这些列表,而是使用扩展名。

2.实现需要处理null的方法我需要一个方法将对象转换为字符串而不抛出exception,以防对象为null

 public static String toStringNullAllowed(this Object inputObject) { if (inputObject == null) { return null; } return inputObject.ToString(); } 

如果我不想使用扩展,我将不得不创建一个类(可能是静态的),例如StringConverter ,它将完成相同的工作,比简单的myObject.toStringNullAllowed();更多的单词myObject.toStringNullAllowed();

3.扩展值类型或密封类:值类型(如int,float,string等)以及密封类(无法inheritance的类)不能通过inheritance进行扩展。 下面你可以看到一个扩展整数的例子,它可以转换成x位数的字符串(例如integer 34, digits 5 --> "00034" ):

 public static String toXDigit(this int inputInteger, int x) { String xDigitNumber = inputInteger.ToString(); while (xDigitNumber.Length < x) { xDigitNumber = "0" + xDigitNumber; } return xDigitNumber; } 

另一种解决方案是静态类(如工具箱),让我们说“数学”。

  • 在这种情况下,你会写: Math.toXDigit(a, x);
  • 使用扩展方法: a.toXDigit(x);

扩展方法看起来更好,更容易理解, 就像说英语一样

总而言之,我认为扩展的缺点是它们的实现与标准类分离,对于不习惯它们的程序员看起来有些奇怪或困难,而它们的优点是它们提供了更易理解,更整洁和封装的用法的语言。