0%

真会C#?——协变、逆变、不变

什么是协变、逆变、不变

  • Covariance协变,当值作为返回值/out输出,例子:
1
public interface IEnumerable<out T>
  • Contravariance逆变,当值作为参数/in输入,例子:
1
public delegate void Action<in T>
  • Invariance不变,当值既是输入又是输出,例子:
1
public interface IList<T>
  • 下面有一个例子:这个例子的string element = strings1[3];语句会报错,若将IList类型的变量换成IEnumerable或跟Action<T>那样赋值就不会出错,因为IEnumerable的泛型只能作为输出,就不会出现从string->object转换的问题,而逆变和不变都会出现这个问题,但IEnumerable就会出现object->string转换问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Program
{
static void Main(string[] args)
{
/**
* IEnumerable<out T>的泛型类型是作为返回值输出的
*/
IEnumerable<string> strings = new List<string> { "a", "b", "c" };
IEnumerable<object> objects = strings;

/**
* IList<T>的泛型类型是既作输入也作为输出的,
* 这里的objects并不能隐式地转换为strings,因为这么做不是安全的,
* 因为IList的元素是作为输入的,如果能做隐式转换,则代表可以用Add方法
* 添加一个object,在执行strings[3]就存在向下转换的问题,它原本是一个
* object,但被当成了一个string,这就导致了类型转换的安全问题,有可能会抛出
* InvalidCastException异常。
* 如果你确保你的输入是一个string,那就可以使用显式地把objects进行转换
*/
IList<string> strings1 = new List<string> { "a", "b", "c" };
IList<object> objects1 = (IList<object>)strings;
objects1.Add(new object());
//这里会抛出InvalidCastException异常,因为string[3]并不是一个字符串
string element = strings1[3];
Console.WriteLine(element);
object o = new object();

/**
* Action<in T>的泛型类型是作为参数输入的,
* 由于string本身就是object,所以objAction能够很自然地转换为Action<string>类型
* 而反过来则不行,因为object不一定就是string类型。
*/
Action<object> objAction = obj => Console.WriteLine(obj);
Action<string> strAction = objAction;
objAction("Print me");
}
}
  • variance只能出现在接口和委托里面

Variance转换

  • Variance转换其实是引用转换的一个例子,引用转换是指,你无法改变其底层的值,只能改变编译时的类型。
  • 本体转换:变量从一个类型转换到相同类型的变量

合理的转换

  • 如果是从A到B的转换是本体转换或者隐式引用转换,则从IEnumerable<A>IEnumerable<B>的转换就是合理的:
    1. IEnumerable to IEnumerable
    2. IEnumerable to IEnumerable
    3. IEnumerable to IEnumerable

不合理的转换

  • 不合理的转换:
    1. IEnumerable to IEnumerable
    2. IEnumerable to IEnumerable
    3. (装箱)IEnumerable to IEnumerable
    4. (数值转换)IEnumerable to IEnumerable

使用Linq进行转换

  • 在linq中可以使用Cast方法来对输出进行类型转换,也可以在末端加上输出类型的泛型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Main(string[] args)
{
string[] strings = { "a", "ab", "abc" };
List<object> list1 = strings
.Where(x => x.Length > 1)
.Cast<object>()
.ToList();

List<object> list2 = strings
.Where(x => x.Length > 1)
.ToList<object>();
}
}

学习资料:[B站杨旭](

-------------本文结束感谢您的阅读-------------