设计类型
一、类型基础
常见问题
- 面向对象语言的特点?
-
什么是面向对象语言
面向对象语言(Object-Oriented Language)是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。
语言中提供了类、继承等成分,有识认性、多态性、类别性和继承性四个主要特点。 -
面向对象语言的特点
面向对象语言刻画客观系统较为自然,便于软件扩充与复用。有四个主要特点:
(1)识认性,系统中的基本构件可识认为一组可识别的离散对象;
(2)类别性,系统具有相同数据结构与行为的所有对象可组成一类;
(3)多态性,对象具有惟一的静态类型和多个可能的动态类型;
(4)继承性,在基本层次关系的不同类中共享数据和操作。
其中,前三者为基础,继承是特色。四者(有时再加上动态绑定)结合使用,体现出面向对象语言的表达能力。
-
var o = new object(); // CLR背后帮我们做了哪些事情?
- 计算类型及其所有基类型(一直到System.Object)中所有的实例字段需要的字节数。注意,堆上每个对象都需要一些额外的成员,当中包含额外成员(类型对象指针(type object pointer)和同步块索引(sync block index))占用的字节数。
- 从托管堆中分配类型要求的字节数。
- 初始化对象的“类型对象指针”和“同步块索引”成员。
- 非静态类初始化:调用类型的实例构造器,传递在new调用中指定的实参,一般构造函数中会自动生成代码来自动调用基类构造器。
- 返回指向该对象的一个引用(或指针)。
is和as使用存在的坑点。
-
as
针对引用对象或者可为null的对象使用。
as不对改变对象的类型。
-
类型转换推荐写法
// @不推荐 if (o is Employee){ Employee e = (Employee)o; } // @推荐,简化以上写法,优化性能 Employee e = o as Employee; if(e != null){ }
- 命名空间和程序集的关系?
- 命名空间和程序集(实现类型的文件)不一定相关,同一个命名空间中的类型可能在不同程序集中实现
- “运行时”的相互关系(参考堆栈的内存分配)
- 线程创建时默认分配1MB的栈。
专业名词
类型对象指针
- 指向类型对象存储的地址,假如有一个类型Person,它在堆中有一块区域存储它内部的字段和成员以及两个额外成员(类型对象指针、 同步块索引 ),类型对象的类型对象指针指向的是System.Type类型对象的地址。
- 因为Person类型在内存中相对于System.Type也是作为一个对象存在的,System.Type类型也是一个类型对象,它的类型对象指针指向本身;
- 调用Object.GetType()方法返回的是类型对象指针的地址。
同步块
-
同步块机制
· 在.NET被加载时初始化同步块数组。
· 每一个被分配在堆上的对象都会包含两个额外的字段,其中一个存储类型指针,而另外一个就是同步块索引,初始时被赋值为-1。
· 当一个线程试图使用该对象进入同步时,会检查该对象的同步索引。如果索引为负数,则会在同步块数组中寻找或者新建一个同步块,并且把同步块的索引值写入该对象的同步索引中。如果该对象的同步索引不为负值,则找到该对象的同步块并且检查是否有其他线程在使用该同步块,如果有则进入等待状态,如果没有则申明使用该同步块。 -
同步块的概念
同步块是指.NET维护的同步块数组中的某个元素,负责提供线程同步的操作,当某个线程拥有了某个同步块时,其他线程就在试图访问该同步块时进入等待状态。
同步块索引
同步索引是每个堆内对象都会分配的一个字段。
堆
栈
参考内容
- 《CLR Via C#(第4版)》
- 揭示同步块索引(上):从lock开始
- 揭示同步块索引(中):如何获得对象的HashCode
- 揭示同步块索引(下):总结
二、基元类型、引用类型和值类型
基元类型
编译器直接支持的类型成为基元类型(Primitive Type),能直接映射到FCL中存在的类型。
常见问题
- System.Decimal没有响应的IL指令来处理Decimal值,无法检测溢出情况。
- UnChecked和Checked
引用类型和值类型
常见问题
声明为值类型的前提条件
优势是不作为对象在托管对上的分配。
-
必选条件
- 类型具有基元类型的行为,是十分简单的类型,没有成员会修改类型的任何实例字段。
- 类型不需要从其他任何类型继承。
- 类型也不派生出其他任何类型。
-
任一条件
- 类型的实例较小(16字节或更小)
- 类型的实例比较大(大于16字节),但不作为方法实参传递,也不从方法返回。
字段布局
使用StructLayoutAttribute特性,FieldOffsetAttribute特性指出字段第一个字节举例实例起始处的偏移量。
- 然而在.net托管环境中,CLR提供了更自由的方式来控制struct中Layout:我们可以在定义struct时,在struct上运用StructLayoutAttribute特性来控制成员的内存布局。默认情况下,struct实例中的字段在栈上的布局(Layout)顺序与声明中的顺序相同,即在struct上运用[StructLayoutAttribute(LayoutKind.Sequential)]特性,这样做的原因是结构常用于和非托管代码交互的情形。
- 如果我们正在创建一个与非托管代码没有任何互操作的struct类型,我们很可能希望改变C#编译器的这种默认规则,因此LayoutKind除了Sequential成员之外,还有两个成员Auto和Explicit,给StructLayoutAttribute传入LayoutKind.Auto可以让CLR按照自己选择的最优方式来排列实例中的字段;传入LayoutKind.Explicit可以使字段按照我们的在字段上设定的FieldOffset来更灵活的设置字段排序方式,但这种方式也挺危险的,如果设置错误后果将会比较严重。
装箱和拆箱
- 读懂相关IL代码,参考IL章节。
Equals和GethashCode重载
Equals
- 重写Equals方法参考范例
/// <summary>
/// 自定义对象类型重写
/// </summary>
public class A : IEquatable<A>
{
public string Id { get; set; }
// 实现IEquatable接口
public bool Equals(A other)
{
// 为空,肯定不相等
if (ReferenceEquals(null, other)) return false;
// 同一对象,必然相等
if (ReferenceEquals(this, other)) return true;
// 比较各个字段
if (!string.Equals(this.Id, other.Id, StringComparison.InvariantCulture))
{
return false;
}
return true;
}
public override bool Equals(object obj)
{
//this非空,obj如果为空,则返回false
if (ReferenceEquals(null, obj)) return false;
//如果为同一对象,必然相等
if (ReferenceEquals(this, obj)) return true;
//如果类型不同,则必然不相等
if (obj.GetType() != this.GetType()) return false;
//调用强类型对比
return Equals((A)obj);
}
public override int GetHashCode()
{
return this.Id == null ? 0 : StringComparer.InvariantCulture.GetHashCode(this.Id);
}
// 重写==号
public static bool operator ==(A left, A right)
{
return Equals(left, right);
}
// 重载!=号
public static bool operator !=(A left, A right)
{
return !Equals(left,right);
}
}
/// <summary>
/// ValueType类型比较
/// 注意:int?这种还是ValueType类型,注意**可为空**)。
/// </summary>
public struct B : IEquatable<B>
{
private string _id;
public B(string id)
{
this._id = id;
}
public bool Equals(B other)
{
// 比较字段是否相等
return string.Equals(this._id,other._id,StringComparison.InvariantCulture);
}
public override bool Equals(object obj)
{
// 为空不相等
if (ReferenceEquals(null, obj)) return false;
return obj is B && Equals((B)obj);
}
public override int GetHashCode()
{
return (this._id != null) ? StringComparer.InvariantCulture.GetHashCode(_id) : 0;
}
public static bool operator ==(B left,B right)
{
if (left == null && right == null)
return true;
if (left == null)
return right.Equals(left);
return left.Equals(right);
}
public static bool operator !=(B left, B right)
{
if (left == null && right == null)
return false;
if (left == null)
return !right.Equals(left);
return !left.Equals(right);
}
}
- 定义自己类型,重写Equals要符合相等性的4个特征:
- Equals必须自反,x.Equals(x)肯定返回true。
- Equals必须对称,x.Equals(y)和y.Equals(x)返回相同的值。
- Equals必须可传递,x.Equals(y)和y.Equals(z)返回true,则x.Equals(z)肯定也返回true。
- Equals必须一致,比较的两个值不变,Equals返回值也不能变。
- 实现IEquatable<string>,实现类型更加安全的Equals方法。
- 重写==和!=等操作符方法。
HashCode
- 重写hashcode的时候,遵守以下的规则:
- 计算算法要提供良好的随机分布,使哈希表获得最佳性能和减少hash冲突的可能性。
- 可在计算中调用基类的GetHashCode方法,并包含它的返回值。但是一般不要调用Object和ValueType的GetHashCode方法。
- 算法至少使用一个实例的字段。
- 理想情况下,算法使用的字段应该不可变(immutable),换句话说,字段在构造初始化后,在对象生存期“永不言变”。
- 算法执行速度尽量快。
- 包含相同值的两个对象应该返回相同的哈希码。
dynamic(比较重要,后续补充完整)
Visual C# 2010 引入了一个新类型 dynamic。 该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类 型检查。 大多数情况下,该对象就像具有类型 object 一样。 在编译时,将假定类型化为 dynamic 的元素支持任何操作。 因此,您不必考虑对象是从 COM API、从动态语言(例如 IronPython)、从 HTML 文档对象模型 (DOM)、从反射还是从程序中的其他位置获取自己的值。 但是,如果代码无效,则在运行时会捕获到错误。
- 应用场景
- 动态类型转换(需要考虑性能)
- 动态语言运行时(DLR,例如 IronPython 和 IronRuby 等动态编程语言的实现)
- COM 互操作
参考范例(继续改进)
- 覆写DynamicObject
/// <summary>
/// 适用于4.5版本或以上
/// </summary>
public class StaticMemberDynamicWrapper : DynamicObject
{
private readonly TypeInfo _typeInfo;
public StaticMemberDynamicWrapper(Type type)
{
this._typeInfo = type.GetTypeInfo();
}
public override IEnumerable<string> GetDynamicMemberNames()
{
return this._typeInfo.DeclaredMembers.Select(p => p.Name);
}
/// <summary>
/// 获取字段或者属性的值
/// </summary>
/// <param name="binder"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = null;
var field = FindField(binder.Name);
if (field != null)
{
result = field.GetValue(null);
return true;
}
var prop = FindProperty(binder.Name, true);
if (prop != null)
{
result = prop.GetValue(null,null);
return true;
}
return false;
}
/// <summary>
/// 设置属性或者字段值
/// </summary>
/// <param name="binder"></param>
/// <param name="value"></param>
/// <returns></returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var field = FindField(binder.Name);
if (field != null)
{
field.SetValue(null, value);
return true;
}
var prop = FindProperty(binder.Name, true);
if (prop != null)
{
prop.SetValue(null,value,null);
return true;
}
return false;
}
/// <summary>
/// 调用方法
/// </summary>
/// <param name="binder"></param>
/// <param name="args"></param>
/// <param name="result"></param>
/// <returns></returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
MethodInfo methodInfo = FindMethodInfo(binder.Name,args.Select(p=> p.GetType()).ToArray());
if (methodInfo == null)
{
result = null;
return false;
}
result = methodInfo.Invoke(null, args);
return true;
}
/// <summary>
/// 寻找方法
/// </summary>
/// <param name="name"></param>
/// <param name="types"></param>
/// <returns></returns>
private MethodInfo FindMethodInfo(string name, Type[] types)
{
return _typeInfo.DeclaredMethods.FirstOrDefault(p => p.Name == name && p.IsPublic && p.IsStatic && ParametersMatch(p.GetParameters(), types));
}
/// <summary>
/// 方法参数类型比较
/// </summary>
/// <param name="parameterInfos"></param>
/// <param name="types"></param>
/// <returns></returns>
private bool ParametersMatch(ParameterInfo[] parameterInfos, Type[] types)
{
if (parameterInfos == null || parameterInfos.Length <= 0)
{
return false;
}
for (int i = 0; i < types.Length; i++)
{
if (parameterInfos[i].ParameterType != types[i])
return false;
}
return true;
}
/// <summary>
/// 寻找属性
/// </summary>
/// <param name="name"></param>
/// <param name="get"></param>
/// <returns></returns>
private PropertyInfo FindProperty(string name, bool get)
{
if (get)
{
return _typeInfo.DeclaredProperties.FirstOrDefault(p => p.Name == name && p.GetMethod != null && p.GetMethod.IsPublic && p.GetMethod.IsStatic);
}
return _typeInfo.DeclaredProperties.FirstOrDefault(p => p.Name == name && p.SetMethod != null && p.SetMethod.IsPublic && p.SetMethod.IsStatic);
}
/// <summary>
/// 寻找字段
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private FieldInfo FindField(string name)
{
return this._typeInfo.DeclaredFields.FirstOrDefault(p => p.IsPublic && p.IsStatic && p.Name.Equals(name));
}
}
/// <summary>
/// 测试
/// </summary>
public class DyamicObjectTest
{
public void Run()
{
dynamic stringType = new StaticMemberDynamicWrapper(typeof(string));
var result = stringType.Concat("A", "B");
// 运行报错
//dynamic stringType2 = "";
//var result2 = stringType2.Concat("B", "C");
//Console.Write(result2);
dynamic sampleObject = new ExpandoObject();
sampleObject.Concat = (Func<string,string,string>)((c, d) => { return c + d; });
var result3 = sampleObject.Concat("A", "B");
Console.WriteLine(result3);
Console.Write(result);
}
}