关于C# 4.0新特性“缺省参数”的实现详解

2020-06-17 08:59:30王旭

二、实现缺省参数的两个特性:OptionalAttribute和DefaultParameterValueAttribute

为什么缺省参数的默认值只能接受常量呢?如果你了解了缺省参数的本质,这就不是一个问题。那么缺省参数究竟是如何实现的呢?

和很多语言层面特性(语法糖)的实现一样,缺省参数也是编译器为我们玩的一个小花招,而真正编译后的东西都是我们再熟悉不过的玩意儿。当包含缺省参数的C#代码经过编译后,缺省参数体现在两个特殊的自定义特性OptionalAttribute和DefaultParameterValueAttribute 。前者将参数标识为缺省参数,后者指定其缺省值。

  1: [ComVisible(true), AttributeUsage(AttributeTargets.Parameter, Inherited=false)]
  2: public sealed class OptionalAttribute : Attribute
  3: {
  4: }
  5: 
  6: [AttributeUsage(AttributeTargets.Parameter)]
  7: public sealed class DefaultParameterValueAttribute : Attribute
  8: {
  9:   public DefaultParameterValueAttribute(object value);
 10:   public object Value {get; }
 11: }

对于最开始我们定义的TestMethod方法,编译后的形式如下所示。

  1: private static void TestMethod(string foo, 
  2:   [Optional, DefaultParameterValue("Bar")] string bar, 
  3:   [Optional, DefaultParameterValue("Baz")] string baz)
  4: {
  5:   //Others..
  6: }

正是因为缺省参数的默认值最终是作为DefaultParameterValueAttribute的参数存在的,所以它必须是常量。

三、直接通过OptionalAttribute和DefaultParameterValueAttribute 定义缺省参数

既然缺省参数最终体现为OptionalAttribute和DefaultParameterValueAttribute 这两个特性,我们是否可以直接通过它们来定义缺省参数呢?答案是:当然可以,下面的代码一样可以正常执行。

  1: static void Main(string[] args)
  2: {
  3:   TestMethod("Foo");
  4:   TestMethod("Foo","Bar1");
  5:   TestMethod("Foo","Bar1","Baz1");
  6: }
  7: 
  8: private static void TestMethod(string foo, 
  9:   [Optional, DefaultParameterValue("Bar")] string bar, 
 10:   [Optional, DefaultParameterValue("Baz")] string baz)
 11: {
 12:   //Others..
 13: }

如果调用含有缺省参数的方法,并且没有显示指定该参数,编译器在编译的时候会自动将默认值附加上去。对于上面的Main方法,下面是与之等效的编译后代码。

  1: private static void Main(string[] args)
  2: {
  3:   TestMethod("Foo", "Bar", "Baz");
  4:   TestMethod("Foo", "Bar1", "Baz");
  5:   TestMethod("Foo", "Bar1", "Baz1");
  6: }

虽然说我们通过OptionalAttribute和DefaultParameterValueAttribute 这两个特性也可以定义缺省参数,但是当我们将缺省参数定义在普通参数之前是,编译器不会报错。倒是方法中缺省参数实际上就相当于普通参数了。

  1: static void Main(string[] args)
  2: {
  3:   //TestMethod("Foo","Baz");
  4:   //上面的方法调用无效
  5:   TestMethod("Foo","Bar1","Baz1");
  6: }
  7: private static void TestMethod(string foo, 
  8:   [Optional, DefaultParameterValue("Bar")] string bar, 
  9:   string baz)
 10: {
 11:   //Others..
 12: }