可选参数和命名实参
可选参数和命名实参如同一对好基友,因为它们经常一起使用
可选参数
可选参数重在“可选”,即在调用方法时,该参数可以明确制定实参,也可以不指定。如下面代码中定义的方法就包含3个参数,一个必备参数和两个可选参数
代码语言:javascript复制static void Test(int x, int y = 10, string name = "")
{
}
在以上代码中,参数 x 是必选参数,即调用方法必须为其指定实参;而参数 y 和参数 name 为可选参数,即可以不用为它们指定实参
在使用可选参数时,需要注意一下几个约束条件
- 所有可选参数必须位于必选参数之后
- 可选参数的默认值必须为常量,如数字、常量字符串、null、const 成员和枚举成员等
- 参数数组不能为可选参数
- 用
ref
或out
关键字标识的参数不能被设置为可选参数
命名实参
当调用带有可选参数的方法时,如果我们省略了一个参数,编译器默认为我们省略的是最后一个参数。但是如果我们只想省略第二个参数怎么办?下面的代码是使用命名实参的一个简单例子
代码语言:javascript复制class Program
{
static void Main(string[] args)
{
Test(10, name: "Hello");
Test(10, y: 10, name: "Hello");
}
static void Test(int x, int y = 10, string name = "")
{
Console.WriteLine($"{name}:{x},{y}");
}
}
从以上代码可以看出,命名实参就是在为实参指定具体的名称,这样编译器将判断参数的名称是否正确,然后将指定的值赋给对应的参数,从而达到只省略第二个参数的目的
泛型的可变性
在 C# 2.0 中,泛型并不具备可变形,这种可变形是指协变性和逆变性。我们知道,面向对象的继承中就蕴含可变性,当方法声明返回的类型为Stream
时,可以在实现中返回一个FileStream
类型,这里就存在一个隐式转换。引用类型数组也存在这种从子类引用到父类引用的转化,例如string[]
可以转换为object[]
string[] strs = new string[3];
object[] objs = strs;
那么,泛型中的泛型参数是否也支持这样的转换呢?C# 2.0 确实是不支持的,但因为有了这样的需求,微软便适应地做出了改进,在 C# 4.0 中引入了泛型的协变性和逆变性
协变性
协变性指的是泛型类型参数可以从一个派生类隐式地转换为基类
C# 4.0 引入out
关键字来标注泛型参数,以示支持协变性,为了更好的说明,下面使用 .Net 类库中的public interface IEnumerable<out T>
接口为例,做泛型协变性的演示:
class Program
{
static void Main(string[] args)
{
List<object> listObject = new List<object>();
List<string> listStr = new List<string>();
listObject.AddRange(listStr); // 成功
listStr.AddRange(listObject); // 失败
}
}
协变性很好理解,派生类可以隐式转换为基类,反之则失败
逆变性
逆变性是指泛型类型参数可以从一个基类隐式地转换为派生类,C# 4.0 引入in
关键字来标记泛型参数,以示其支持逆变性,下面以 .Net 类库中的接口public interface IComparer<in T>
为例进行演示:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
List<object> listObject = new List<object>();
List<string> listString = new List<string>();
IComparer<object> objComparer = new TestComparer();
IComparer<string> stringComparer = new TestComparer();
listString.Sort(objComparer);
listObject.Sort(stringComparer); // 编译报错
Console.ReadKey();
}
}
public class TestComparer : IComparer<object>
{
public int Compare([AllowNull] object x, [AllowNull] object y)
{
return x.ToString().CompareTo(y.ToString());
}
}
}
从以上代码中,listStrings
变量的Sort
方法应接受IComparer<string>
类型的参数,虽然传入的实参为IComparer<object>
类型,但因为``IComparer泛型接口支持逆变,所以可将
object转为
string类型,于是代码
listStrings.Sort(objComparer)`也就可以编译通过了
而listObject
变量的Sort
方法则应接受IComparer<Object>
类型参数,但代码listObject.Sort(objComparer2)
却传入了IComparer<string>
类型的变量。由于IComparer<in T>
接口泛型参数只支持逆变,不支持协变,所以不能把IComparer<string>
类型隐式地转换为IComparer<object>
,所以会出现编译错误
协变和逆变的注意事项
并不是所有类型都支持泛型类型参数的协变和逆变性,下面总结了使用这两个特性时需要注意的地方
- 只有借口和委托才支持协变和逆变(如
Func<out TResult>
、Action<in T>
),类或泛型方法的类型参数都不支持协变和逆变 - 协变和逆变只适用于引用类型,值类型不支持协变和逆变(因为可变性存在引用转换的过程,而值类型变量存储的就是对象本身,并不是对象的应用),所以
List<int>
无法转换为IEnumerable<object>
- 必须显示地使用
in
或out
来标记类型参数 - 委托的可变性不要在多播委托中使用