C# 권투 및 언박싱 지식 편집

高洛峰
풀어 주다: 2017-01-24 14:25:17
원래의
1476명이 탐색했습니다.

1. Boxing과 unboxing은 추상적인 개념입니다.
2. Boxing은 값 유형을 참조 유형으로 변환합니다.

Unboxing은 참조 유형을 값 유형으로 변환합니다.

boxing 및 unboxing 기능을 사용합니다. 값 유형의 모든 값을 객체 유형

으로 변환하거나 그 반대로 변환하도록 허용하여 값 유형을 참조 유형에 연결할 수 있습니다. 예:

int val = 100; 
object obj = val; 
Console.WriteLine (“对象的值 = {0}", obj);
로그인 후 복사

이는 다음과 같은 박싱 프로세스입니다. 값 유형을 참조 유형으로 변환하는 과정

int val = 100; 
object obj = val; 
int num = (int) obj; 
Console.WriteLine ("num: {0}", num);
로그인 후 복사

언박싱 과정으로, 값 유형을 참조 유형으로 변환한 후, 참조 유형을 값 유형으로 변환하는 과정입니다. 🎜>

참고: boxed 개체만 unboxing할 수 있습니다.

3. .NET에서는 데이터 유형이 값 유형과 참조(C++ 포인터와 동일하지 않음) 유형으로 나누어지며, 이에 따라 메모리 할당이 두 가지로 나뉩니다. 방법 중 하나는 스택이고 다른 하나는 힙입니다(참고: 관리되는 힙입니다)
값 유형은 스택에만 할당됩니다.
참조 유형은 메모리와 관리되는 힙을 할당합니다.
관리되는 힙은 가비지 수집에 해당합니다.

4: 복싱/언박싱이란 무엇인가요?

Boxing: 가비지 수집 힙에 값 유형을 저장하는 데 사용됩니다. 박싱은 값 유형을 객체 유형 또는 값 유형이 구현하는 인터페이스 유형으로 암시적으로 변환하는 것입니다.
Unboxing: 객체 유형에서 값 유형으로 또는 인터페이스 유형에서 인터페이스를 구현하는 값 유형으로 명시적으로 변환합니다.

5: 포장은 왜 필요한가요? (값 유형을 참조 유형으로 변환하는 이유는 무엇입니까?)

가장 일반적인 시나리오 중 하나는 Object 유형의 매개변수를 사용하여 메소드를 호출하는 것입니다. 객체는 보편적인 사용을 위해 모든 유형을 지원할 수 있습니다. 값 유형(예: Int32)을 전달해야 하는 경우 boxing이 필요합니다.
또 다른 사용법은 일반이 아닌 컨테이너입니다. 보편성을 보장하기 위해 요소 유형은 객체로 정의됩니다. 따라서 값 유형 데이터를 컨테이너에 추가할 때 박싱이 필요합니다.

6: boxing/unboxing의 내부 작업

Boxing

값 유형에 대해 힙에 개체 인스턴스를 할당하고 해당 값을 새 개체에 복사합니다. 세 단계를 따르십시오.

새로 할당된 관리형 힙 메모리(크기는 값 유형 인스턴스의 크기에 메서드 테이블 포인터 및 SyncBlockIndex를 더한 크기)입니다.

값 유형의 인스턴스 필드를 새로 할당된 메모리에 복사합니다.
관리되는 힙에 새로 할당된 개체의 주소를 반환합니다. 이 주소는 개체에 대한 참조입니다.
어떤 사람들은 이것을 다음과 같이 이해합니다. Int32가 boxing된 경우 반환된 주소는 Int32를 가리킵니다. 이런 식으로 이해하는 것이 불가능하지는 않다고 생각하지만, 첫째로 포괄적이지 않고 둘째로 Int32를 가리키는 것이 (관리되는 힙에서) 그 본질을 말해주지 않는다는 점입니다.

Unboxing

객체 인스턴스를 검사하여 주어진 값 유형의 boxed 값인지 확인합니다. 인스턴스의 값을 값 유형 변수에 복사합니다.
일부 책에 따르면 unboxing은 참조 객체의 값 유형 부분에 대한 포인터만 얻고 콘텐츠 복사는 할당 문의 트리거입니다. 나는 그것이 중요하지 않다고 생각합니다. 가장 중요한 것은 개체 인스턴스의 특성을 확인하는 것입니다. unboxing 유형과 boxing 유형이 일치해야 합니다. 이 시점에서는 IL 계층에서 GetType과 같은 메서드가 호출되는 원리를 볼 수 없습니다. . 일치할 유형을 제거합니다(엄격한 일치가 필요하므로).

7: 박싱/언박싱이 실행 효율성에 미치는 영향

분명히 박싱 시 완전히 새로운 참조 객체가 생성되어 시간이 소모된다는 원리에서 볼 수 있습니다. 즉, 효율성이 저하됩니다.

어떻게 해야 할까요?
우선 복싱은 최대한 피해야 합니다.
예를 들어 위의 예제 2의 두 상황은 모두 피할 수 있습니다. 첫 번째 경우에는 함수를 오버로드하여 피할 수 있습니다. 두 번째 경우는 제네릭을 통해 피할 수 있습니다.
물론, 변환하려는 코드가 타사 어셈블리이고 변경할 수 없다면 절대적인 것은 없습니다.
boxing/unboxing 코드의 최적화를 위해서는 C#에서 boxing과 unboxing이 암시되어 있기 때문에 기본적인 방법은 코드를 분석하는 것이며, 가장 직접적인 분석 방법은 원리 구조를 이해하는 것입니다. 디컴파일된 IL 코드를 봅니다.

예를 들어 루프 본문에 중복된 박싱이 있을 수 있으며 최적화를 위해 고급 박싱을 사용할 수 있습니다.

8: 포장/개봉에 대한 더 나은 이해

포장/개봉은 위에서 언급한 것처럼 간단하고 명확하지 않습니다

예: 포장할 때 개체를 참조하려면, 추가 메소드 테이블 포인터가 있을 것입니다. 이것의 용도는 무엇입니까?

예제를 통해 더 자세히 알아볼 수 있습니다.

예:

Struct A : ICloneable
{
public Int32 x;
public override String ToString() {
return String.Format(”{0}”,x);
}
public object Clone() {
return MemberwiseClone();
}
}
static void main() 
{ 
A a; 
a.x = 100; 
Console.WriteLine(a.ToString()); 
Console.WriteLine(a.GetType()); 
A a2 = (A)a.Clone(); 
ICloneable c = a2; 
Ojbect o = c.Clone(); 
}
로그인 후 복사

a.ToString()。编译器发现A重写了ToString方法,会直接调用ToString的指令。因为A是值类型,编译器不会出现多态行为。因此,直接调用,不装箱。(注:ToString是A的基类System.ValueType的方法) 
a.GetType(),GetType是继承于System.ValueType的方法,要调用它,需要一个方法表指针,于是a将被装箱,从而生成方法表指针,调用基类的System.ValueType。(补一句,所有的值类型都是继承于System.ValueType的)。 
a.Clone(),因为A实现了Clone方法,所以无需装箱。 
ICloneable转型:当a2为转为接口类型时,必须装箱,因为接口是一种引用类型。 
c.Clone()。无需装箱,在托管堆中对上一步已装箱的对象进行调用。 
附:其实上面的基于一个根本的原理,因为未装箱的值类型没有方法表指针,所以,不能通过值类型来调用其上继承的虚方法。另外,接口类型是一个引用类型。对此,我的理解,该方法表指针类似C++的虚函数表指针,它是用来实现引用对象的多态机制的重要依据。

9:如何更改已装箱的对象

对于已装箱的对象,因为无法直接调用其指定方法,所以必须先拆箱,再调用方法,但再次拆箱,会生成新的栈实例,而无法修改装箱对象。有点晕吧,感觉在说绕口令。还是举个例子来说:(在上例中追加change方法) 

public void Change(Int32 x) { 
this.x = x; 
}
로그인 후 복사

调用: 

A a = new A(); 
a.x = 100; 
Object o = a; //装箱成o,下面,想改变o的值
((A)o).Change(200); //改掉了吗?没改掉
로그인 후 복사
没改掉的原因是o在拆箱时,生成的是临时的栈实例A,所以,改动是基于临时A的,并未改到装箱对象。

(附:在托管C++中,允许直接取加拆箱时第一步得到的实例引用,而直接更改,但C#不行。) 
那该如何是好? 
嗯,通过接口方式,可以达到相同的效果。 
实现如下: 

interface IChange { 
void Change(Int32 x); 
} 
struct A : IChange { 
… 
}
로그인 후 복사

调用:

((IChange)o).Change(200);//改掉了吗?改掉了
로그인 후 복사

为啥现在可以改?


在将o转型为IChange时,这里不会进行再次装箱,当然更不会拆箱,因为o已经是引用类型,再因为它是IChange类型,所以可以直接调用Change,于是,更改的也就是已装箱对象中的字段了,达到期望的效果。

10、将值类型转换为引用类型,需要进行装箱操作(boxing):

首先从托管堆中为新生成的引用对象分配内存
然后将值类型的数据拷贝到刚刚分配的内存中
返回托管堆中新分配对象的地址
可以看出,进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作。

将引用类型转换为值类型,需要进行拆箱操作(unboxing):

首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。
将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。
经过这2步,可以认为是同boxing是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同boxing操作中一样影响性能。

11、

NET的所有类型都是由基类System.Object继承过来的,包括最常用的基础类型:int, byte, short,bool等等,就是说所有的事物都是对象。

如果申明这些类型得时候都在堆(HEAP)中分配内存,会造成极低的效率!(个中原因以及关于堆和栈得区别会在另一篇里单独得说说!)
.NET如何解决这个问题得了?正是通过将类型分成值型(value)和引用型(regerencetype),

C#中定义的值类型和引用类型

值类型:原类型(Sbyte、Byte、Short、Ushort、Int、Uint、Long、Ulong、Char、Float、Double、Bool、Decimal)、枚举(enum)、结构(struct)
引用类型:类、数组、接口、委托、字符串等
值型就是在栈中分配内存,在申明的同时就初始化,以确保数据不为NULL;
引用型是在堆中分配内存,初始化为null,引用型是需要GARBAGE COLLECTION来回收内存的,值型不用,超出了作用范围,系统就会自动释放!
下面就来说装箱和拆箱的定义!
装箱就是隐式的将一个值型转换为引用型对象。比如:

int i=0;
Syste.Object obj=i;
로그인 후 복사

这个过程就是装箱!就是将i装箱!
拆箱就是将一个引用型对象转换成任意值型!比如:

int i=0;
System.Object obj=i;
int j=(int)obj;
로그인 후 복사

这个过程前2句是将i装箱,后一句是将obj拆箱!

更多c#装箱和拆箱知识整理相关文章请关注PHP中文网!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!