惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fortinet All Blogs
Attack and Defense Labs
Attack and Defense Labs
V2EX - 技术
V2EX - 技术
O
OpenAI News
S
Secure Thoughts
H
Heimdal Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Schneier on Security
Schneier on Security
H
Hacker News: Front Page
S
Security Affairs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Vercel News
Vercel News
Microsoft Security Blog
Microsoft Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
The Register - Security
The Register - Security
GbyAI
GbyAI
Cloudbric
Cloudbric
MongoDB | Blog
MongoDB | Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Y
Y Combinator Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
Scott Helme
Scott Helme
Hacker News - Newest:
Hacker News - Newest: "LLM"
The Cloudflare Blog
Recorded Future
Recorded Future
人人都是产品经理
人人都是产品经理
Cyberwarzone
Cyberwarzone
C
CERT Recently Published Vulnerability Notes
Webroot Blog
Webroot Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LangChain Blog
T
Tor Project blog
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Hacker News: Ask HN
Hacker News: Ask HN
Blog — PlanetScale
Blog — PlanetScale
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog RSS Feed
N
News and Events Feed by Topic
阮一峰的网络日志
阮一峰的网络日志
I
Intezer
V
V2EX
T
Tailwind CSS Blog
SecWiki News
SecWiki News
NISL@THU
NISL@THU
C
Check Point Blog

博客园 - calmzeal

微软更新导致的IIS7设置默认主页无效 【原创】Superset在windows下的安装配置 解决 Linux error while loading shared libraries: cannot open shared object file: No such file or directory 记录一次服务器CPU 100%的解决过程 Google Review中Zlib.Portable报错的一种排查解决方案 sql行转列 VS2010发布.NET2.0网站,出现“未预编译文件* 因此不能请求该文件”的解决办法 在 64 位版本的 Windows 上IIS ASP NET不支持JET4 问题 wse [Web 开发] 定制IE下载对话框的按钮(打开/保存) 【IE信息栏问题】本地html文件js被IE阻止的一些解决方法 分享一个js Tree - dTree SqlPlus Set常用设置 OleDB Transaction ORA-01000: maximum open cursors exceeded 超出打开游标的最大数 【转】神呀~~,给我个"本地数据库的替换方案"吧! 【CLR Via C#笔记】操作符重载 C#单件模式 【CLR Via C#笔记】 类型对象 [转] CSS完美兼容IE6/IE7/FF的通用方法
【CLR Via C#笔记】 值类型与拆装箱、参数传递
calmzeal · 2008-10-28 · via 博客园 - calmzeal

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)