Fantacy
人工智能,智能运营,智能客服,企业知识管理系统(Kmaster,SuperKM)

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(thisnull, 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(11);

            Console.WriteLine(p);

            p.Change(
22);
            Console.WriteLine(p);

            
//装箱
            Object o = p;
            Console.WriteLine(o);

            
//改变的是拆箱后 位于堆栈上的 值类型实例,而o存储的装箱后(堆上)的地址
            ((Point)o).Change(33);
            Console.WriteLine(o);

            
//同上
            ((IChangeBoxedPoint)p).Change(44);
            Console.WriteLine(p);

            
//Object 和 interface都是引用类型,此处没有拆装箱操作
            ((IChangeBoxedPoint)o).Change(55);
            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)
posted on 2008-10-28 16:42  calmzeal  阅读(624)  评论(0编辑  收藏  举报