C#学习记录-类型转换 01

73 阅读5分钟

C#学习记录-类型转换 01

1. 类型的划分

在C#中类型可以分为值类型和引用类型

继承关系

value-reference-types-common-type-system.png

图片来源

值类型:

  1. 继承自 System.ValueType 的内置值类型
  2. 继承自 System.Enum 的所有枚举类型
  3. 用户定义的 struct

引用类型:

  1. System.ObjectSystem.ValueTypeSystem.Enum 类本身
  2. 继承自System.Object 的所有内置类型
  3. 用户定义的 class、interface、delegate

存储规则

值类型:

  1. 存储在 **栈(Stack)**上
  2. 作为 struct 成员时,存储在
  3. 作为 class 成员时,存储在

引用类型:

  1. 实际数据存储在 **堆(Heap)**上
  2. 变量或参数(对实际数据的引用)存储在
  3. 作为 struct 成员时,存储在
  4. 作为 class 成员时,存储在

复制规则

值类型:

  1. 深拷贝,值复制
  2. 变量及数据本身
  3. 复制变量时:直接复制数据,新数据和原数据独立

引用类型:

  1. 浅拷贝,引用复制
  2. 变量仅是对实际数据的引用
  3. 复制变量时:复制对实际数据的引用,实际数据不变

特殊情况 :

  1. struct 内部包含引用类型时,可以看作特殊的混合类型,其复制规律不符合两者
  2. 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#中类型转换可以分为以下几类

值和值值和引用引用和引用
隐式转换装箱,值->引用协变,隐式转换
显示转换拆箱,引用->值逆变,显示转换

可扩展性/内涵

值类型之间转换

相同类别的值类型情况下

  1. 低位数->高位数,协变,隐式转换,无损
  2. 高位数->低位数,逆变,显示转换,有损耗

从低到高,其只需要对低位进行扩展即可 而从高到低,则需要对高位进行裁剪,损失了部分数据 我们可以认为低位相较于高位更易于扩展,其可扩展性更强

可扩展性/内涵:

  1. 低位数:可扩展性 较强,内涵 较少
  2. 高位数:可扩展性 较弱,内涵 较多

引用类型之间转换

同一继承关系下:

  1. 基类->派生类,隐式转换,安全
  2. 派生类->基类,显示转换,不安全

同一接口关系下:

  1. 接口->实现类,隐式转换,安全
  2. 实现类->接口,显示转换,不安全

而对于无继承关系的 A 和 B,显示转换,不安全

对于同一继承关系下的基类和派生类:

  1. 基类转换为派生类,只需要额外增加派生类新增的内容即可,扩展难度低
  2. 派生类转换为基类,则需要移除派生类新增的内容,可能会出现问题,扩展难度高
  3. 我们可以认为基类相较于高位更易于扩展,其可扩展性更强

对于接口继承同理

可扩展性/内涵:

  1. 基类:可扩展性 较强,内涵 较少
  2. 派生类:可扩展性 较弱,内涵 较多

可扩展性/内涵:

  1. 接口:可扩展性 较强,内涵 较少
  2. 实现类:可扩展性 较弱,内涵 较多

我们可以看出在类型转换上:

  1. 隐式转换意味着 可扩展性的降低 和 内涵的增加
  2. 显示转换意味着 可扩展性的提高 和 内涵的减少
  3. 更高的可扩展性也意味着内容更少更基础

泛型中的协变和逆变

官方文档参考

官方案例:

//案例 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 :

  1. IEnumerable<T>协变类型参数,其作用于返回值
  2. 在 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 :

  1. Action<T>逆变类型参数,作用于传入值
  2. 在 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);

装箱拆箱

装箱:值类型->引用类型,隐式 拆箱:引用类型->值类型,显示

装箱和拆箱同样遵循可扩展性的规律:

  1. 值类型 可扩展性 较强,内涵 较少
  2. 引用类型 可扩展性 较弱,内涵 较多
  3. 值->引用的过程,新增了其他内容,并构造一个新的对象
  4. 引用->值的过程,去除了其余内容仅保留了该值

因装箱过程中包含了一次对象的创建,因此其性能消耗比拆箱更高,这点和相同类型间的隐式转换和显示转换不同