zoukankan      html  css  js  c++  java
  • 【CLR Via C#笔记】 值类型与拆装箱、参数传递

    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)
  • 相关阅读:
    【转载】天才与柱子
    Windows Phone 7 隔离存储空间资源管理器
    (收藏)让你平步青云的十个谈话技巧
    (收藏)《博客园精华集》设计模式分册
    (收藏)C#开源资源大汇总
    (收藏)生活物语
    (收藏)C# ORM/持久层框架
    (收藏)《博客园精华集》AJAX与ASP.NET AJAX部分
    小型项目总结之五
    VS 打包完美解决方案
  • 原文地址:https://www.cnblogs.com/calmzeal/p/1321478.html
Copyright © 2011-2022 走看看