C# 学习笔记(15)—— C# 4.0

2023-10-20 18:51:58 浏览数 (1)

可选参数和命名实参

可选参数和命名实参如同一对好基友,因为它们经常一起使用

可选参数

可选参数重在“可选”,即在调用方法时,该参数可以明确制定实参,也可以不指定。如下面代码中定义的方法就包含3个参数,一个必备参数和两个可选参数

代码语言:javascript复制
static void Test(int x, int y = 10, string name = "")
{
}

在以上代码中,参数 x 是必选参数,即调用方法必须为其指定实参;而参数 y 和参数 name 为可选参数,即可以不用为它们指定实参

在使用可选参数时,需要注意一下几个约束条件

  • 所有可选参数必须位于必选参数之后
  • 可选参数的默认值必须为常量,如数字、常量字符串、null、const 成员和枚举成员等
  • 参数数组不能为可选参数
  • refout关键字标识的参数不能被设置为可选参数

命名实参

当调用带有可选参数的方法时,如果我们省略了一个参数,编译器默认为我们省略的是最后一个参数。但是如果我们只想省略第二个参数怎么办?下面的代码是使用命名实参的一个简单例子

代码语言: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[]

代码语言:javascript复制
string[] strs = new string[3];
object[] objs = strs;

那么,泛型中的泛型参数是否也支持这样的转换呢?C# 2.0 确实是不支持的,但因为有了这样的需求,微软便适应地做出了改进,在 C# 4.0 中引入了泛型的协变性和逆变性

协变性

协变性指的是泛型类型参数可以从一个派生类隐式地转换为基类

C# 4.0 引入out关键字来标注泛型参数,以示支持协变性,为了更好的说明,下面使用 .Net 类库中的public interface IEnumerable<out T>接口为例,做泛型协变性的演示:

代码语言:javascript复制
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> 为例进行演示:

代码语言:javascript复制
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>
  • 必须显示地使用inout来标记类型参数
  • 委托的可变性不要在多播委托中使用

0 人点赞