学习笔记2_命名和可选参数_协变与逆变

命名和可选参数

在开发项目过程中,有时候需要编写一个接收多个参数的方法.例如查询某个销售记录情况,查询条件为销售日期,商品类别,商品编号,销售城市的任意组合.如果指定了某个条件,则根据这个条件进行过滤,否则不对此条件

进行限制.方法如下

static List<string> Select(DateTime from, DateTime to, string categoryId, string productId, string cityId)
{
    return null;
}

如果要调用此方法,必须为所有的参数指定一个值,即使不根据这个条件进行查询

var list = Select(DateTime.Parse("2010-1-1"), DateTime.Parse("2012-1-1"), null, null, "1001");

在C#4.0中,允许为方法的参数指定默认值,则此参数成为可选参数,调用方法的时候不必给这个参数传值.C#4.0还支持命名参数,调用方法的时候,调用方法可以通过参数名称来为某个特定的参数传值.

注意:可选参数必须位于方法列表参数的最后.即在可选参数后面不允许出现不可选参数.

利用C#4.0的可选参数,上述代码可以修改为:

//可选参数必须位于方法列表的最后
static List<string> NewSelect(DateTime from, DateTime to, string categoryId = null, string productId = null, string cityId = null)
{
    return null;
}

上面的查询可以修改为:

var list2 = NewSelect(DateTime.Parse("2010-1-4"), DateTime.Parse("2012-1-1"));

如果要查询某个城市的销售情况,可以使用

var list3 = NewSelect(DateTime.Parse("2010-1-4"), DateTime.Parse("2012-1-1"), cityId: "1001");

协变和逆变

在C#4.0中引入了协变(Covariance)和逆变(Contravariance),以增强泛型接口和委托.

在C#中,将一个IList<string>类型转换IList<object>是不允许的.下面代码编译时会出错

IList<string> words = new List<string> { "this", "is", "a", "array" };
IList<object> objects = words;

"无法将类型IList<string>隐式转换为IList<object>".C#之所以不允许将IList<string>转换为IList<object>是出于安全的原因.如果允许,则考虑如下代码:

IList<string> words = new List<string> { "this", "is", "a", "array" };
//假定可以编译
IList<object> objects = words;
objects[0] = 50;
objects[1] = 123.456;
objects[2] = new Class1();
string s = objects[2];

假如可以将IList<string>转换为IList<object>,由于IList是引用类型,objects和words其实是同一个对象.通过objects可以向列表中添加任意类型的对象,然后通过words试图得到一个string类型时就会出错.

虽然IList<string>不可以转换为IList<object>,但是IEnumerable<string>却可以转换为IEnumerable<object>,代码如下:

//虽然IList<string>不可以转换为IList<object>,但是IEnumerable<string>可以转换为IEnumerable<object>
//从派生类泛型接口向基类泛型接口的转换称为协变 out T
IEnumerable<string> words = new List<string> { "this", "is", "a", "string", "array" };
IEnumerable<object> objects = words;

C#允许从IEnumerable<string>到IEnumerable<object>的转换,是由于IEnumerable<T>接口没有修改数据的方法,不会出现前述的类型安全问题.

这种从派生类泛型接口向基类泛型接口的转换称为协变.

C#是如何知道什么情况下允许协变呢?这是通过泛型定义实现的.查看这两个泛型接口的定义,可以得到如下代码:

public interface IEnumerable<out T> : IEnumerable
{}
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{}

在IEnumerable泛型接口的类型参数T前面有一个out关键字,而IList泛型接口则没有这个out关键字.泛型接口类型参数T前面的out表示T只出现在接口的输出位置(如方法的返回值),而不会出现在输入位置(如方法输入参数).

像这种在类型参数前有out的泛型接口允许协变

与协变相对的概念成为逆变,即将一个基类型的泛型接口转换为派生类型的泛型接口,代码如下

//将一个基类型的泛型接口转换为派生类型的泛型接口
//in T
//用于指示协变和逆变的in out 关键字只能用于修饰泛型接口和委托的类型参数,不能用于类、不能用于类、结构、方法等。
//所修饰的类型T必须是引用类型,不能是值类型
IComparer<object> objectComparer = getObjectComparer();
IComparer<string> stringComparer = getStringComparer();
//逆变
stringComparer = objectComparer;

通常允许从派生类向基类的隐式转换,基类却不能隐式转换为派生类型.对于IComparer<T>接口来说,由基类接口向派生类接口转换是是有意义的.如代码中一个可以比较object的类型的比较器,自然可以成为比较string类型的比较器.查看

IComparer泛型接口的定义,得到如下代码:

public interface IComparer<in T>
{}

在IComparer泛型接口的参数T前面有个in关键字,表示此泛型接口中的参数只允许出现在输入位置(如方法的输入参数),而不允许出现在输出位置(如方法的返回值).

像这种在类型参数前面有in的泛型接口允许逆变.

原文地址:https://www.cnblogs.com/qingkongwanli/p/2789633.html