原则10:使用默认参数减少函数的重载

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处http://dsqiu.iteye.com

C# 现在已经在函数调用上支持命名参数了。这意味着名字形式的参数是你的类 public 接口的一部分。改变 public 参数的名字将破坏函数调用代码。这意味着很多时候要避免使用命名参数,或者避免改变 public 或 protected 方法的命名参数的名字。

当然,没有一个语言设计者增加的特性会增加你的难度。命名参数和默认参数防止了很多 API 的多而杂乱,特别是 Microsoft Office 的 COM API 。下面这段代码使用 COM 类的方法创建 Word 文档并插入一小段文本:

var wasted = Type.Missing; 
var wordApp = new Microsoft.Office.Interop.Word. Application(); 
wordApp.Visible = true;
Documents docs = wordApp.Documents;
Document doc = docs.Add(ref wasted, ref wasted, ref wasted, ref wasted);
Range range = doc.Range(0, 0);
range.InsertAfter("Testing, testing, testing. . .");

这段代码使用了小而没用的 Type.Missing 对象次数。很多 Office Interop 应用都会使用大量的 Type.Missing 对象。这些实例弄混了你的应用并隐藏了你构建软件的实际逻辑。

在 C# 语言中,这个混乱只要是因为添加了默认参数和命名参数导致的。默认参数意味着意味着这些 Office API 可以使用默认值对那些所有使用 Type.Missing 的地方。简化上面的小段代码:

var wordApp = new Microsoft.Office.Interop.Word.Application(); 
wordApp.Visible = true;
Documents docs = wordApp.Documents;
Document doc = docs.Add();
Range range = doc.Range(0, 0);
range.InsertAfter("Testing, testing, testing. . .");

命名参数的作用是在任何有默认参数的 API 中,你只需要制定你想要使用的参数。这个简单并省去了多次重载。实际上,四个不同参数,你需要重载15个不同的 Add() 来达到命名参数和默认参数相同的灵活性。记住一些 Office API 有多达16个参数,默认参数和命名参数就是一个很大帮助。

我在参数列表使用 ref 修饰符,但在 C# 4.0的另一个改变就是在 COM 中这是默认的。这是因为 COM ,一般来说,使用对象引用来传递参数,所有几乎所有参数都是按引用来传递参数的,即使函数内没有对参数进行改变。实际上, Range() 调用通过引用传入值(0,0)的。我们没有包含 ref 修饰符,因为那将是明显的误导。实际,在多少代码中,我们都没有包含 ref 修饰符去调用 Add() 。上面我就是这样做的,你可以去查看下实际的 API 签名。

当然,仅仅是因为 COM 和 Office API 的命名参数和默认参数,这不意味着你需要限制使用它们在 Office Interop 应用中。实际上,这是不能的。开发者使用命名参数调用你的 API 无论你是否希望他们这么做。

下面的方法:

private void SetName(string lastName, string firstName) 
{
    //elided
}

使用命名参数调用避免命名参数次序的混乱:

SetName(lastName: "Wagner", firstName: "Bill");

注解命名参数保证别人后面看这段代码不用去想参数的位置是否正确。开发者使用命名参数将阅读代码的人的更清晰。当调用的函数有多个相同类型的参数,使用命名参数会使你的代码更加具有可读性。

改变参数名字的破坏会表现得很有趣。在 MSIL 中参数名字存储在函数调用点,而不是函数入口。你可以改变参数名字并且发布的组件不会破坏任何使用这个组件的代码。使用这个组件的开发者会有一个重要的改变当需要重新编译升级版本,但之前的版本的客户端代码仍然可以正确运行。所以你至少不会破坏已存在的程序集。使用你代码的开发者会懊恼,但是不会对你抱怨出现的问题。例如,假设你修改 SetName() 的参数名字:

public void SetName(string Last, string First);

你将会编译和发布这个程序集作为一个补丁。任何调用这个方法的程序集都能继续运行,即使包含了指定参数名字的 SetName 的调用。然而,当客户端开发者想要升级他们的程序集,像下面的代码都不会通过编译:

SetName(lastName : "Wanger",firstName : "Bill");

改变默认参数的值童谣需要调用者重写编译保证这个更新一致。如果你编译你的程序集并发布这个补丁,所有已存在的调用将继续使用之前的默认值。

当然,你也不会随意的对使用你的组件的开发者带来麻烦。所以,你必须把参数的名字当做组件 public 接口的一部分。改变参数名字在客户端代码再编译时会有问题。

此外,增加参数(即使是默认参数)都会在运行时有问题。默认参数的实现跟命名参数是类似的。调用点会包含 MSIL 用于反射出默认值的存在和具体值时多少的注解。函数入口替换调用者没有明确指定的默认参数的值。

所以,即使增加一个默认参数,都会在运行时出现问题。如果它们有默认参数,在编译时不会有问题。

至此,经过一番解释,这个指导应该更加清晰了。为了初始版本的发布,使用默认参数和命名参数可以创造使用者想要得到重载组合。但是,一旦你创建未来的版本,你必须创建额外参数的重载。那样已经存在的客户端程序会继续有效。此外,在任何将来的版本中,避免更改参数名称。它们现在是你的 public 接口的一部分。

小结:

这篇跨了一个星期,因为自己脖子到现在还是疼的,然后公司搬家,这个星期脖子一直都很痛,加上上班的工作任务也重,会到家就直接洗澡休息了,连续休息了一周感觉还是不错的,至少脸上的“平静”了很多,气色也好了很多,但是又觉得很不心安,感觉荒废了,我要加油,继续努力。

欢迎各种不爽,各种喷,写这个纯属个人爱好,秉持”分享“之德!

有关本书的其他章节翻译请点击查看,转载请注明出处,尊重原创!

如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件([email protected])交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

转载请在文首注明出处:http://dsqiu.iteye.com/blog/1986873

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)