1. 值类型都是从 System.ValueType继承的,并且都是Sealed。无法再次被继承。
在Reflector中查看ValueType原型如下,重写了Equals, ToString,GetHashCode.
因而在调用这些方法的时候,无需进行装箱操作:
[Serializable, ComVisible(true)]
public abstract class ValueType
{
// Methods
protected ValueType();
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool CanCompareBits(object obj);
public override bool Equals(object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool FastEqualsCheck(object a, object b);
[MethodImpl(MethodImplOptions.InternalCall)]
public override extern int GetHashCode();
public override string ToString();
}
public override string ToString()
{
return base.GetType().ToString();
}
以System.Byte为例:
public override string ToString()
{
return Number.FormatInt32(this, null, NumberFormatInfo.CurrentInfo);
}
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern string FormatInt32(int value, string format, NumberFormatInfo info);
可以看到Byte的ToString方法参数中已经使用的是值类型参数(this)。
而对于GetType或MemberwiseClone。是从Object集成的非虚方法。这些方法期望this参数是指向堆上对象的一个指针。
因此在调用时需要进行装箱操作
[Serializable, ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class Object
{
// Methods
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public Object();
public virtual bool Equals(object obj);
public static bool Equals(object objA, object objB);
private void FieldGetter(string typeName, string fieldName, ref object val);
private void FieldSetter(string typeName, string fieldName, object val);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override void Finalize();
private FieldInfo GetFieldInfo(string typeName, string fieldName);
public virtual int GetHashCode();
[MethodImpl(MethodImplOptions.InternalCall)]
public extern Type GetType();
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern bool InternalEquals(object objA, object objB);
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int InternalGetHashCode(object obj);
[MethodImpl(MethodImplOptions.InternalCall)]
protected extern object MemberwiseClone();
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static bool ReferenceEquals(object objA, object objB);
public virtual string ToString();
}
2. 值类型(比如结构体)中定义的成员不应修改类型的任何实例字段。
下面的代码演示了在值类型中提供更改字段的方法后,在调用中容易出现问题
class Program
{
internal interface IChangeBoxedPoint
{
void Change(Int32 _x, Int32 _y);
}
struct Point : IChangeBoxedPoint
{
private Int32 X, Y;
public Point(Int32 _x, Int32 _y)
{
this.X= _x;
this.Y = _y;
}
public void Change(Int32 _x, Int32 _y)
{
this.X = _x;
this.Y = _y;
}
public override string ToString()
{
return string.Format("({0},{1})",X,Y);
}
}
static void Main(string[] args)
{
Point p = new Point(1, 1);
Console.WriteLine(p);
p.Change(2, 2);
Console.WriteLine(p);
//装箱
Object o = p;
Console.WriteLine(o);
//改变的是拆箱后 位于堆栈上的 值类型实例,而o存储的装箱后(堆上)的地址
((Point)o).Change(3, 3);
Console.WriteLine(o);
//同上
((IChangeBoxedPoint)p).Change(4, 4);
Console.WriteLine(p);
//Object 和 interface都是引用类型,此处没有拆装箱操作
((IChangeBoxedPoint)o).Change(5, 5);
Console.WriteLine(o);
Console.ReadKey();
}
}
执行结果为:
(1,1)
(2,2)
(2,2)
(2,2)
(2,2)
(5,5)
上述第3个和第4个并没有输出(3,3)(4,4)
这是因为改变的是拆箱之后的堆栈中的值类型,而原装箱的引用不会变化。
不仔细分析很难看出正确的结果,当然没必要时刻当心这个问题,只需将struct 改成class后,一切都清晰多了。
3. 参数传递
c#中函数参数传递包括传值和传址类型。对于值类型,传值将复制一份值类型的Copy并传递给函数参数,而传值将把值类型在堆栈上的存储地址传递给函数参数;
而对于引用类型,传值将复制引用类型在堆栈上的引用(指针),该copy和原引用类型指向同一对象,因此也可以修改对象内容,
而传址将传递该引用类型在堆栈上指针的Copy,因此可以修改对象本身包括新创对象。
Q:传址方式的两种out 和ref 有什么区别和联系?
A:1. 从CLR和IL的角度看,2者是一致的:都生成对被传递内容的指针。
2. 关键区别在于编译器保证代码的正确性,在对引用类型的传递时,out 传递需要保证函数过程中实例化,而ref 将检查传入引用是否已经实例化。
3. 只存在与out 和 ref 差异的重载是不合法的。
Q: 引用类型按值传递也能在函数中改变对象内容,字符串是引用类型,为什么表现得和值类型差不多?
A: 先看下列代码:
Code
从结果看,String类型和Int32类型表现得差不多。
实际上这是因为字符串对象的不可改变性造成的,即我们不能改变String类型在堆中的内容,每次赋值其实是在堆中重新创建一个String对象,并将新地址赋给原引用。
按值传递字符串时,原引用s1和形参s指向堆中同一字符串对象,对s赋值时,堆中将新建内容为1的字符串对象,并且s将指向它。但这并不会对原引用s1造成任何影响。
4. params关键字实现可变参数传递
void f( params Int32[] values){
foreach (Int32 i in values) ...
}
调用:f();f(1);f(1,2);f(3,4,2,23,3)
由于数值对象在堆中分配,最终需要垃圾收集器回收,使用params会导致一些额外的开销,最好多定义几个常用的重载。如:
f(Int32 i)
f(Int32 i1,Int32 i2)