C#学习记录-类型转换 01
1. 类型的划分
在C#中类型可以分为值类型和引用类型
继承关系
值类型:
- 继承自
System.ValueType的内置值类型 - 继承自
System.Enum的所有枚举类型 - 用户定义的 struct
引用类型:
System.Object、System.ValueType、System.Enum类本身- 继承自System.Object 的所有内置类型
- 用户定义的 class、interface、delegate
存储规则
值类型:
- 存储在 **栈(Stack)**上
- 作为 struct 成员时,存储在栈上
- 作为 class 成员时,存储在堆上
引用类型:
- 实际数据存储在 **堆(Heap)**上
- 变量或参数(对实际数据的引用)存储在栈上
- 作为 struct 成员时,存储在堆上
- 作为 class 成员时,存储在堆上
复制规则
值类型:
- 深拷贝,值复制
- 变量及数据本身
- 复制变量时:直接复制数据,新数据和原数据独立
引用类型:
- 浅拷贝,引用复制
- 变量仅是对实际数据的引用
- 复制变量时:复制对实际数据的引用,实际数据不变
特殊情况 :
- struct 内部包含引用类型时,可以看作特殊的混合类型,其复制规律不符合两者
- struct 内部包含 string 类型时,string 的复制规则符合值类型,为直接复制
在struct 和 class 中复制字符串的例子 :
public class Program
{
public struct TestStruct
{
public int value;
public string str;
public TestStruct(int value, string str)=>(this.value, this.str) = (value, str);
public void Print() => Console.WriteLine("{0}, {1}", value, str);
}
public class TestClass
{
public int value;
public string str;
public TestClass(int value, string str)=> (this.value, this.str) = (value, str);
public void Print() => Console.WriteLine("{0}, {1}", value, str);
}
public void Test()
{
TestStruct s1 = new TestStruct(123, "Hello");
TestStruct s2 = s1;
s2.value = 456; s2.str = "World";
TestClass c1 = new TestClass(123, "Hello");
TestClass c2 = c1;
c2.value = 456; c2.str = "World";
Console.WriteLine("struct中复制string");
s1.Print();
s2.Print();
Console.WriteLine("class中复制string");
c1.Print();
c2.Print();
}
}
其 Test方法 运行结果为:
struct中复制string
123, Hello
456, World
class中复制string
456, World
456, World
2. 类型转换
c#中类型转换可以分为以下几类
| 值和值 | 值和引用 | 引用和引用 |
|---|---|---|
| 隐式转换 | 装箱,值->引用 | 协变,隐式转换 |
| 显示转换 | 拆箱,引用->值 | 逆变,显示转换 |
可扩展性/内涵
值类型之间转换:
相同类别的值类型情况下
- 低位数->高位数,协变,隐式转换,无损
- 高位数->低位数,逆变,显示转换,有损耗
从低到高,其只需要对低位进行扩展即可 而从高到低,则需要对高位进行裁剪,损失了部分数据 我们可以认为低位相较于高位更易于扩展,其可扩展性更强
可扩展性/内涵:
- 低位数:可扩展性 较强,内涵 较少
- 高位数:可扩展性 较弱,内涵 较多
引用类型之间转换
同一继承关系下:
- 基类->派生类,隐式转换,安全
- 派生类->基类,显示转换,不安全
同一接口关系下:
- 接口->实现类,隐式转换,安全
- 实现类->接口,显示转换,不安全
而对于无继承关系的 A 和 B,显示转换,不安全
对于同一继承关系下的基类和派生类:
- 基类转换为派生类,只需要额外增加派生类新增的内容即可,扩展难度低
- 派生类转换为基类,则需要移除派生类新增的内容,可能会出现问题,扩展难度高
- 我们可以认为基类相较于高位更易于扩展,其可扩展性更强
对于接口继承同理
可扩展性/内涵:
- 基类:可扩展性 较强,内涵 较少
- 派生类:可扩展性 较弱,内涵 较多
可扩展性/内涵:
- 接口:可扩展性 较强,内涵 较少
- 实现类:可扩展性 较弱,内涵 较多
我们可以看出在类型转换上:
- 隐式转换意味着 可扩展性的降低 和 内涵的增加
- 显示转换意味着 可扩展性的提高 和 内涵的减少
- 更高的可扩展性也意味着内容更少更基础
泛型中的协变和逆变
官方案例:
//案例 1:协变类型参数
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
//案例 2:逆变类型参数
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
案例 1 :
IEnumerable<T>为协变类型参数,其作用于返回值- 在 d=b 中
IEnumerable<Base>协变为List<Derived>类型
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through the collection.
//
// 返回结果:
// An enumerator that can be used to iterate through the collection.
IEnumerator<T> GetEnumerator();
}
案例 2 :
Action<T>为逆变类型参数,作用于传入值- 在 b=d 中
Action<Derived>逆变为Action<Base>接受 b 的赋值
//
// 摘要:
// Encapsulates a method that has a single parameter and does not return a value.
//
//
// 参数:
// obj:
// The parameter of the method that this delegate encapsulates.
//
// 类型参数:
// T:
// The type of the parameter of the method that this delegate encapsulates.
public delegate void Action<in T>(T obj);
装箱拆箱
装箱:值类型->引用类型,隐式 拆箱:引用类型->值类型,显示
装箱和拆箱同样遵循可扩展性的规律:
- 值类型 可扩展性 较强,内涵 较少
- 引用类型 可扩展性 较弱,内涵 较多
- 值->引用的过程,新增了其他内容,并构造一个新的对象
- 引用->值的过程,去除了其余内容仅保留了该值
因装箱过程中包含了一次对象的创建,因此其性能消耗比拆箱更高,这点和相同类型间的隐式转换和显示转换不同