前进中的蜗牛

番茄大叔

水滴穿石,非一日之功;没有量变,何来质变。

.Net之美读书笔记1

值类型和引用类型

  1. 栈(stack)是一种先进后出的数据结构,在内存中,变量会被分配在栈上来进行操作。
  2. 堆(heap)是用于为引用类型的实例(对象)分配空间的内存区域,在堆上创建一个对象,
    会将对象的地址传给栈上的变量。

值类型

当声明一个值类型的变量的时候的时候,变量本身包含了值类型的全部字段,值变量分配到线程堆栈(Thread Stack)上。

	public struct ValPoint
	{
		public int x;
		public ValPoint(int x)
		{
			this.x = x;
		}
	}
    //调用
    ValPoint vPoint1 = new ValPoint(10);

值类型结构图

  • struct 调用struct上的方法前,需要对其所有的字段进行赋值;
  • struct 有隐式的无参构造函数,构造函数对struct成员初始化(通过new 调用构造函数),值类型被赋予0,引用类型被赋予null;
  • struct 自定义构造函数时,构造函数内必须为每个struct成员初始化;

引用类型

当声明一个引用类型变量,并使用new操作符创建引用类型实例的时候,该引用变量会被分配到线程栈(Stack)上,变量保存了位于堆(Heap)上的引用类型的实例的内存地址。

	public class RefPoint
	{
		public int x;
		public RefPoint(int x)
		{
			this.x = x;
		}
	}
    //调用
    RefPoint rPoint1 = new RefPoint(10);

引用类型结构图:

装箱和拆箱

装箱的步骤

  1. 在堆(Heap)为新生成的对象实例分配内存
  2. 将栈(Stack)的值复制到堆(Head)生的对象
  3. 将堆创建的对象的地址返回给引用类型变量
			int i = 1;
			object boxed = i;

拆箱相反的操作,所以装箱和拆箱是耗时的操作。

对象判等

引用类型判等

System.Object基类型中判等方法有3个,对三个方法分析

    //比较引用变量是否指向堆(Heap)上的同一实例对象
	public static bool ReferenceEquals(object objA, object objB)
    {
        return objA == objB;
    }

    //实例方法,供派生类重写(override),(派生类未重写默认用基类,但只有指向同一实例对象返回true)
    public virtual bool Equals(object obj)
    {
        return RuntimeHelpers.Equals(this, obj);
    }
    // 如果一个为null返回false
    public static bool Equals(object objA, object objB)
    {
        return objA == objB || (objA != null && objB != null && objA.Equals(objB));
    }

下面通过一些代码来验证方法

	RefPoint rPoint1 = new RefPoint(1);
    RefPoint rPoint2 = rPoint1;

    result = (rPoint1 == rPoint2);//True
    Console.WriteLine(result);
    result = rPoint1.Equals(rPoint2);//True
    Console.WriteLine(result);

    RefPoint rPoint3 = new RefPoint(2);
    RefPoint rPoint4 = new RefPoint(2);

    result = (rPoint3 == rPoint4);//False
    Console.WriteLine(result);
    result = rPoint3.Equals(rPoint4);//False
    Console.WriteLine(result);

值类型的判等

值类型隐式继承ValueType,ValueType继承Object并override了Equals方法。override后的Equals方法主要比较步骤

		public override bool Equals(object obj)
		{
            //1.传入的对象是否为null
			if (obj == null)
			{
				return false;
			}
            //2.类型判等
			RuntimeType runtimeType = (RuntimeType)base.GetType();
			RuntimeType left = (RuntimeType)obj.GetType();
			if (left != runtimeType)
			{
				return false;
			}
            //3.为系统基本类型可直接比较
			if (ValueType.CanCompareBits(this))
			{
				return ValueType.FastEqualsCheck(this, obj);
			}
            //4.利用反射遍历值类型成员(这里有递归)
			FieldInfo[] fields = runtimeType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			for (int i = 0; i < fields.Length; i++)
			{
				object obj2 = ((RtFieldInfo)fields[i]).UnsafeGetValue(this);
				object obj3 = ((RtFieldInfo)fields[i]).UnsafeGetValue(obj);
				if (obj2 == null)
				{
					if (obj3 != null)
					{
						return false;
					}
				}
				else if (!obj2.Equals(obj3))
				{
					return false;
				}
			}
			return true;
		}

来电代码验证

	ValPoint vPoint1 = new ValPoint(1);
    ValPoint vPoint2 = new ValPoint(1);

    result = vPoint2.Equals(vPoint2);
    Console.WriteLine(result);//True

分析复杂判等情况

  1. 装箱
ValPoint vPoint1 = new ValPoint(1);
result = object.ReferenceEquals(vPoint1, vPoint1);
Console.WriteLine(result);//False

小伙伴困惑了,为什么明明是同一个变量放回false。其实ReferenceEquals函数的参数类型为object类型,
这里发生了隐式装箱,所以在堆(Heap)为两个不同实例。
2. override Equals方法

	ValPoint vPoint1 = new ValPoint(1);
    ValPoint vPoint2 = new ValPoint(1);
    object obj1 = vPoint1;
    object obj2 = vPoint2;
    result = (obj1.Equals(obj2));//True

返回True,不知大家是否答对。因为struct隐式继承ValueType,所以obj1.Equals(obj2)实际调用的是ValueType类型override的Equals方法。
3. 值类型中含有引用类型成员

	public struct ValLine
	{
		public RefPoint rPoint;
		public ValPoint vPoint;

		public ValLine(RefPoint rPoint,ValPoint vPoint)
		{
			this.rPoint = rPoint;
			this.vPoint = vPoint;
		}
	}
    //调用
        RefPoint rPoint = new RefPoint(1);
        ValPoint vPoint1 = new ValPoint(1);
        ValPoint vPoint2 = new ValPoint(1);

        ValLine vLine1 = new ValLine(rPoint, vPoint1);
        ValLine vLine2 = new ValLine(rPoint, vPoint2);

        result = vLine1.Equals(vLine2);//True

大家怎么想的放回true,ValueType写Equals。利用反射遍历ValLine 类型成员,RefPoint引用类型因为指向堆(Heap)上同一实例未返回false,继续遍历ValPoint 类型下的Equals的方法未返回false。

对象复制

浅复制

指的是对 对象的成员来区分的。对象的成员是值类型时会复制其本身;对象的成员是引用类型时仅仅复制引用,而不在堆(Heap)上创建新实例。
引用类型需要怎样实现浅复制,需要实现ICloneable接口。需要添加类

	public class RefLine:ICloneable
	{
		public RefPoint rPoint;
		public ValPoint vPoint;
		public RefLine(RefPoint rPoint, ValPoint vPoint)
		{
			this.rPoint = rPoint;
			this.vPoint = vPoint;
		}
		public object Clone()
		{
			return MemberwiseClone();
		}
	}
    //调用验证值类型
    	ValPoint vPoint = new ValPoint(1);
        RefPoint rPoint = new RefPoint(2);
        ValLine vLine1 = new ValLine(rPoint, vPoint);
        ValLine vLine2 = vLine1;
        vLine2.vPoint.x = 3;
        vLine2.rPoint.x = 4;
        Console.WriteLine(vLine1.vPoint.x);//1
        Console.WriteLine(vLine1.rPoint.x);//4
    //调用验证引用类型
        ValPoint vPoint = new ValPoint(1);
        RefPoint rPoint = new RefPoint(2);
        RefLine rLine1 = new RefLine(rPoint, vPoint);
        RefLine rLine2 = rLine1.Clone() as RefLine;
        rLine2.vPoint.x = 3;
        rLine2.rPoint.x = 4;
        Console.WriteLine(rLine1.vPoint.x);//1
        Console.WriteLine(rLine1.rPoint.x);//4

值类型浅复制结构图:

引用类型浅复制结构图:

深复制

深复制就是将引用成员指向的对象也进行复制。引用成员可能指向引用,所以深复制非常复杂,简单的解决办法序列化。

	[Serializable]
	public struct ValPoint
	{
		public int x;
		public ValPoint(int x)
		{
			this.x = x;
		}
	}

    [Serializable]
	public class RefPoint
	{
		public int x;
		public RefPoint(int x)
		{
			this.x = x;
		}
	}
    [Serializable]
	public class RefLine
	{
		public RefPoint rPoint;
		public ValPoint vPoint;
		public RefLine(RefPoint rPoint, ValPoint vPoint)
		{
			this.rPoint = rPoint;
			this.vPoint = vPoint;
		}
		public object Clone()
		{
			BinaryFormatter bf = new BinaryFormatter();
			MemoryStream ms = new MemoryStream();
			bf.Serialize(ms, this);
            //注意设置流的起始位置
			ms.Position = 0;
			return (bf.Deserialize(ms));
		}
	}
    //调用
    ValPoint vPoint = new ValPoint(1);
    RefPoint rPoint = new RefPoint(2);
    RefLine rLine1 = new RefLine(rPoint, vPoint);
    RefLine rLine2 = rLine1.Clone() as RefLine;
    
    rLine2.vPoint.x = 3;
    rLine2.rPoint.x = 4;
    Console.WriteLine(rLine1.vPoint.x);//1
    Console.WriteLine(rLine1.rPoint.x);//2

不可变类型

string类型是一种特殊的引用类型,称作不可变类型。那么我们需要怎样自己创建一个不可变类型?
不可变类型需要具有的两个特性

  • 对象原子性:对象的状态是一个整体,如果一个字段改变,其他字段也要做出相应改变。
  • 对象的常量性: 对象的状态一旦确定,就不能再次更改了。
public struct Address
	{
		//对象的常量型
		private readonly string province;
		private readonly string city;
		private readonly string zip;
		//对一般引用类型(常量性需要修改)
		private readonly string[] phones;

		//保证对象的原子性,构造函数每次要么成功要么失败
		public Address(string province, string city, string zip, string[] phones)
		{
			this.city = city;
			this.province = province;
			this.zip = zip;
			this.phones = phones;
			CheckZip(zip);
		}

		public string Province { get { return province; } }
		public string City { get { return city; } }
		public string Zip { get { return zip; } }
		//防止修改引用类型内的成员变量
		private string[] Phones
		{
			get
			{
				string[] rtn = new string[phones.Length];
				phones.CopyTo(rtn, 0);
				return rtn;
			}
		}
		private void CheckZip(string value)
		{
			string pattern = @"\d{6}";
			if (!Regex.IsMatch(value, pattern))
			{
				throw new Exception("Zip is invalid!");
			}
		}
	}

《.Net 之美》 作者:张子阳

posted @ 2017-12-08 18:28  LoveTomato  阅读(297)  评论(2编辑  收藏  举报