C#에서 복싱과 언복싱이 필요한 이유는 무엇입니까?
C#에서 복싱과 언복싱이 필요한 이유는 무엇입니까?
나는 복싱과 언복싱이 무엇인지 알지만, 그것의 실제 사용을 이해할 수 없습니다.제가 그것을 왜 어디서 사용해야 합니까?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
왜죠
값이 기본 데이터를 통형시을템갖가예나기데타방내는식를터이이이본참형형고조추)과 완전히 다른 방식으로 기본 할 수 입니다.int
참조 유형과 완전히 다른 32비트 버킷일 뿐입니다.)
이렇게 생각해 보세요..o
object
그리고 이제 당신은.int
그리고 당신은 그것을 넣고 싶어합니다.o
.o
어딘가에 대한 언급이고, 그리고.int
분명히 어딘가에 대한 언급이 아닙니다 (결국, 그것은 단지 숫자일 뿐입니다.그래서, 여러분이 하는 일은 이것입니다: 여러분은 새로운 것을 만듭니다.object
할 수 int
그런 다음 해당 개체에 대한 참조를 할당합니다.o
우리는 이 과정을 "복싱"이라고 부릅니다.
따라서 통합 유형 시스템(즉, 참조 유형과 값 유형의 표현이 매우 다르며 두 가지를 "표현"하는 공통 방법을 원하지 않는 경우)을 사용하지 않아도 됩니다.만약 당신이 신경쓰지 않는다면.int
의 기본적인 가치를 . ( 대신에 즉가나타냄치를근있인음본적가그고지들, 의대신에즉)있음▁(▁represent▁(냄나▁have)int
참조 유형이기도 하고 기본 가치에 대한 참조만 저장합니다.) 그러면 복싱이 필요하지 않습니다.
어디에 사용해야 합니까?
를 들어, 컬렉션 유형 예: 이컬션유형렉전입니다.ArrayList
기하는만만 먹습니다.object
즉, 어딘가에 사는 것에 대한 참조만 저장합니다.복싱 없이는 당신은 그것을 넣을 수 없습니다.int
할수 있습니다.하지만 복싱을 하면 할 수 있습니다.
이제 제네릭 시대에는 이것이 실제로 필요하지 않으며 일반적으로 문제에 대해 생각하지 않고 즐겁게 지낼 수 있습니다.그러나 다음과 같은 몇 가지 주의 사항이 있습니다.
이것은 정확합니다.
double e = 2.718281828459045;
int ee = (int)e;
이것은 아닙니다:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
대신 다음 작업을 수행해야 합니다.
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
먼저, 우리는 명시적으로 박스를 풀어야 합니다.double
((double)o
) 을 에 int
.
다음의 결과는 무엇입니까?
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
다음 문장으로 넘어가기 전에 잠시 생각해 보세요.
당신이 말했다면True
그리고.False
좋아요! 잠깐, 뭐라고요?ㅠㅠㅠ==
참조 유형에서는 기준 값이 동일한지 여부가 아니라 참조가 동일한지 확인하는 참조-값을 사용합니다.이것은 위험할 정도로 쉽게 범할 수 있는 실수입니다. 더 ▁perhaps다▁even▁more것▁subtle입니아마.
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
인도할것다니도 인쇄됩니다.False
!
더 좋은 말은:
Console.WriteLine(o1.Equals(o2));
다행히도 인쇄가 될 것입니다.True
.
마지막 미묘함은
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
출력은 얼마입니까?상황에 따라 다릅니다.한다면Point
입니다.struct
은 "그면출력은러은출"입니다.1
하지만 만약에Point
입니다.class
은 "그면출력은러은출"입니다.2
복싱 변환은 행동의 차이를 설명하는 박스화된 값의 복사본을 만듭니다.
.NET 프레임워크에는 값 유형과 참조 유형의 두 가지 유형이 있습니다.이는 OO 언어에서 비교적 일반적입니다.
객체 지향 언어의 중요한 기능 중 하나는 유형에 구애받지 않는 방식으로 인스턴스를 처리할 수 있다는 것입니다.이것을 다형성이라고 합니다.우리는 다형성을 이용하고 싶지만, 두 종류의 다른 종류를 가지고 있기 때문에, 우리는 그것들을 하나 또는 다른 하나를 같은 방식으로 다룰 수 있는 어떤 방법이 있어야 합니다.
이제 옛날로 돌아가 보겠습니다(마이크로소프트의 1.0).NET), 이 새로운 실패한 제네릭 헐라벌루는 없었습니다.값 유형과 참조 유형에 서비스를 제공할 수 있는 단일 인수가 있는 메서드를 작성할 수 없습니다.그것은 다형성의 위반입니다.그래서 복싱은 가치 유형을 물체에 강요하는 수단으로 채택되었습니다.
만약 이것이 가능하지 않다면, 프레임워크는 다른 종류의 종을 받아들이는 것이 유일한 목적인 방법과 클래스로 흩어져 있을 것입니다.뿐만 아니라 값 유형이 실제로 공통된 유형의 상위 항목을 공유하지 않기 때문에 각 값 유형(비트, 바이트, int16, int32 등)에 대해 다른 메서드 오버로드가 있어야 합니다.
복싱은 이런 일이 일어나는 것을 막았습니다.그리고 그것이 영국인들이 복싱 데이를 기념하는 이유입니다.
이것을 이해하는 가장 좋은 방법은 C#이 기반으로 하는 하위 수준의 프로그래밍 언어를 살펴보는 것입니다.
C와 같은 가장 낮은 수준의 언어에서는 모든 변수가 한 곳으로 이동합니다.스택.변수를 선언할 때마다 변수는 스택에 저장됩니다.이러한 값은 bool, 바이트, 32비트 int, 32비트 int 등과 같은 원시 값만 될 수 있습니다.스택은 단순하면서도 빠릅니다.변수가 추가되면 변수가 하나씩 위에 올라가므로 처음 선언할 때는 0x00, 다음은 0x01, 다음은 RAM에서 0x02 등으로 표시됩니다.또한 변수는 컴파일 시 미리 주소를 지정하는 경우가 많기 때문에 프로그램을 실행하기도 전에 변수의 주소를 알 수 있습니다.
C++와 같은 다음 레벨 업에서는 힙이라고 불리는 두 번째 메모리 구조가 도입됩니다.당신은 여전히 대부분 스택에 살고 있지만 포인터라는 특수 int를 스택에 추가할 수 있고, 객체의 첫 번째 바이트에 대한 메모리 주소를 저장하고, 객체는 힙에 삽니다.힙은 프로그램이 실행될 때 스택 변수와 달리 선형으로 쌓였다가 아래로 쌓이지 않기 때문에 관리 비용이 많이 듭니다.그들은 특별한 순서 없이 왔다 갔다 할 수 있고, 자라고 줄어들 수 있습니다.
포인터를 다루는 것은 어렵습니다.메모리 누수, 버퍼 오버런 및 좌절의 원인이 됩니다.구조를 위한 C#.
더 높은 수준의 C#에서는 포인터에 대해 생각할 필요가 없습니다.Netframework(C++로 작성)는 이를 고려하여 개체 참조로 제공하고 성능을 위해 풀, 바이트 및 int와 같은 단순한 값을 값 유형으로 저장할 수 있습니다.후드 아래에서 클래스를 인스턴스화하는 개체 및 항목은 값비싼 메모리 관리 힙으로 이동하는 반면, 값 유형은 낮은 수준의 C-초고속 스택으로 이동합니다.
기본적으로 서로 다른 두 가지 메모리 개념(및 스토리지 전략) 간의 상호 작용을 코더의 관점에서 단순하게 유지하기 위해 가치 유형은 언제든지 박스화할 수 있습니다.상자를 사용하면 값이 스택에서 복사되어 개체에 삽입되고 힙에 배치됩니다. 즉, 참조 월드와 더 비싸지만 유동적인 상호 작용입니다.다른 답변에서 알 수 있듯이, 예를 들어 다음과 같이 말할 때 이 문제가 발생합니다.
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
Boxing의 장점을 잘 보여주는 것은 null에 대한 것입니다.
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
우리의 목표는 기술적으로 힙에 복사된 우리의 boob의 복사본을 가리키는 스택의 주소입니다.우리는 o를 null로 확인할 수 있습니다. 왜냐하면 bool은 박스로 포장되어 거기에 놓았기 때문입니다.
일반적으로 당신은 필요하지 않는 한 복싱을 피해야 합니다. 예를 들어 논쟁의 대상으로 int/boole/what를 전달하는 것입니다.에는 몇 가지 기본 구조가 있습니다.가치 유형을 객체로 전달하는 것을 여전히 요구하는 네트(따라서 복싱이 필요함), 그러나 대부분의 경우 복싱을 할 필요가 없습니다.
Boxing이 필요한 과거 C# 구조의 완전하지 않은 목록으로, 피해야 할 사항은 다음과 같습니다.
이벤트 시스템은 단순하게 사용하는 경주 조건을 가지고 있는 것으로 밝혀졌으며 비동기를 지원하지 않습니다.복싱 문제를 추가하면 아마 피해야 할 것입니다.(예를 들어 Generics를 사용하는 비동기 이벤트 시스템으로 대체할 수 있습니다.
이전의 스레드화 및 타이머 모델은 매개 변수에 박스를 강제로 적용했지만 훨씬 깨끗하고 효율적인 비동기/대기로 대체되었습니다.
.Net 1.1 컬렉션은 Generics보다 먼저 출시되었기 때문에 전적으로 Boxing에 의존했습니다.이것들은 여전히 시스템에서 작동하고 있습니다.컬렉션.새 코드에서는 시스템에서 수집을 사용해야 합니다.컬렉션.일반적으로, 복싱을 피하는 것 외에도 더 강력한 유형 안전성을 제공합니다.
Boxing을 강제하는 위의 역사적 문제를 다루어야 하는 경우를 제외하고는 Value Type을 개체로 선언하거나 전달하는 것을 피해야 하며, 나중에 Boxing it이 Boxing될 것이라는 것을 알고 있을 때 성능이 저하되는 것을 피해야 합니다.
아래의 Per Mikael 제안:
실행 방법
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
이것이 아님
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
갱신하다
이 답변은 원래 Int32, Bool 등이 복싱을 유발한다고 제안했지만, 실제로는 가치 유형에 대한 단순한 별칭입니다.그것은.Net에는 Bool, Int32, String 및 C#과 같은 유형이 있으며 기능적 차이 없이 Bool, Int, String으로 별칭을 지정합니다.
복싱은 실제로 사용하는 것이 아닙니다. 필요할 때 동일한 방식으로 참조 및 값 유형을 처리할 수 있도록 런타임에서 사용하는 것입니다.예를 들어 배열 목록을 사용하여 정수 목록을 보관하는 경우 정수는 배열 목록의 개체 유형 슬롯에 맞게 상자로 표시됩니다.
이제 일반 컬렉션을 사용하면, 이것은 거의 사라집니다.을 하는 경우List<int>
행해지지 - 은행않았니다습해지지복싱다▁done▁the▁there니않았습▁-▁no.List<int>
정수를 직접 고정할 수 있습니다.
Boxing(복싱) 및 Unboxing(복싱 해제)은 특히 값 유형 개체를 참조 유형으로 처리하는 데 사용되며, 실제 값을 관리되는 힙으로 이동하고 참조를 통해 값에 액세스합니다.
상자 및 상자 해제 없이는 값 유형을 참조로 전달할 수 없습니다. 즉, 값 유형을 개체 인스턴스로 전달할 수 없습니다.
마지막으로 데이터베이스에서 일부 데이터를 검색하는 코드를 작성할 때(LINQ를 SQL로 사용하지 않고 오래된 ADO.NET을 사용함),
int myIntValue = (int)reader["MyIntValue"];
기본적으로, 만약 당신이 제네릭 이전의 오래된 API로 작업하고 있다면, 당신은 복싱을 접하게 될 것입니다.그것을 제외하면, 그것은 그렇게 흔하지 않습니다.
객체를 매개 변수로 사용해야 하는 함수가 있지만 전달해야 하는 값 유형이 다를 때는 복싱이 필요합니다. 이 경우 값 유형을 객체 데이터 유형으로 변환한 후 함수에 전달해야 합니다.
사실이 아닌 것 같습니다. 대신 이것을 사용해 보십시오.
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
그것은 잘 작동합니다, 저는 복싱/언복싱을 사용하지 않았습니다. (컴파일러가 뒤에서 하지 않는 한?)
.net에서 개체의 모든 인스턴스 또는 이로부터 파생된 모든 유형은 해당 유형에 대한 정보를 포함하는 데이터 구조를 포함합니다..net의 "실제" 값 유형에는 이러한 정보가 포함되어 있지 않습니다.개체에서 파생된 유형을 수신할 것으로 예상되는 루틴에서 값 유형의 데이터를 조작할 수 있도록 시스템은 각 값 유형에 대해 동일한 멤버와 필드를 가진 해당 클래스 유형을 자동으로 정의합니다.복싱은 이 클래스 유형의 새 인스턴스를 만들어 값 유형 인스턴스에서 필드를 복사합니다.상자 해제는 클래스 유형의 인스턴스에서 값 유형의 인스턴스로 필드를 복사합니다.값 유형에서 생성되는 모든 클래스 유형은 아이러니하게도 이름이 지정된 클래스 ValueType(이름에도 불구하고 실제로는 참조 유형임)에서 파생됩니다.
복싱은 힙의 개체에 있는 데이터를 오프셋으로 사용하여 값을 참조 유형으로 변환하는 것입니다.
복싱이 실제로 무엇을 하는지에 대해서요.여기 몇 가지 예가 있어요.
모노 C++
void* mono_object_unbox (MonoObject *obj)
{
MONO_EXTERNAL_ONLY_GC_UNSAFE (void*, mono_object_unbox_internal (obj));
}
#define MONO_EXTERNAL_ONLY_GC_UNSAFE(t, expr) \
t result; \
MONO_ENTER_GC_UNSAFE; \
result = expr; \
MONO_EXIT_GC_UNSAFE; \
return result;
static inline gpointer
mono_object_unbox_internal (MonoObject *obj)
{
/* add assert for valuetypes? */
g_assert (m_class_is_valuetype (mono_object_class (obj)));
return mono_object_get_data (obj);
}
static inline gpointer
mono_object_get_data (MonoObject *o)
{
return (guint8*)o + MONO_ABI_SIZEOF (MonoObject);
}
#define MONO_ABI_SIZEOF(type) (MONO_STRUCT_SIZE (type))
#define MONO_STRUCT_SIZE(struct) MONO_SIZEOF_ ## struct
#define MONO_SIZEOF_MonoObject (2 * MONO_SIZEOF_gpointer)
typedef struct {
MonoVTable *vtable;
MonoThreadsSync *synchronisation;
} MonoObject;
모노에서 개체의 상자를 해제하는 것은 개체의 2g 포인터 오프셋(예: 16바이트)에서 포인터를 캐스팅하는 프로세스입니다.gpointer
입니다.void*
이것은 정의를 볼 때 의미가 있습니다.MonoObject
분명히 데이터의 헤더일 뿐이기 때문입니다.
C++
C++로 값을 입력하려면 다음과 같은 작업을 수행합니다.
#include <iostream>
#define Object void*
template<class T> Object box(T j){
return new T(j);
}
template<class T> T unbox(Object j){
T temp = *(T*)j;
delete j;
return temp;
}
int main() {
int j=2;
Object o = box(j);
int k = unbox<int>(o);
std::cout << k;
}
가 참조 인 메서드가 제한된 경우: 메드가 예변사경는우하용로수제됨한로개클스매래가참서메드조서유일반형만예▁when▁a▁a▁constsay)▁only▁via▁the(우▁(▁as제됨▁method한▁class▁a▁type▁generic▁takes▁methodnew
제약 조건)에 대한 참조 유형을 전달할 수 없으므로 상자에 표시해야 합니다.
이는 다음을 수행하는 모든 방법에 대해서도 마찬가지입니다.object
매개 변수로 - 참조 유형이어야 합니다.
일반적으로 값 유형을 입력하지 않으려는 경우가 많습니다.
그러나 이 기능이 유용한 경우는 거의 없습니다.예를 들어 1.1 프레임워크를 대상으로 지정해야 하는 경우 일반 컬렉션에 액세스할 수 없습니다..NET 1.1 컬렉션을 사용하려면 값 유형을 시스템으로 처리해야 합니다.개체 - 복싱/언복싱을 발생시킵니다.
.NET 2.0+에서 이 기능이 유용한 경우는 여전히 있습니다.값 유형을 포함한 모든 유형을 개체로 직접 처리할 수 있다는 점을 활용하려면 언제든지 복싱/언복싱을 사용해야 할 수 있습니다.(일반 컬렉션에서 T 대신 개체를 사용하여) 컬렉션에 모든 유형을 저장할 수 있기 때문에 유용할 수 있지만, 일반적으로 유형 안전성이 손실되기 때문에 이를 피하는 것이 좋습니다.그러나 복싱이 자주 발생하는 한 가지 경우는 Reflection을 사용할 때입니다. Reflection의 많은 호출은 값 유형을 미리 알 수 없기 때문에 값 유형을 작업할 때 복싱/복싱 해제가 필요합니다.
값될 때 합니다.object
는 자동적으로하기 때문에해야 하느냐가 입니다.object
.
object
C#과 같은 정적 유형 언어의 주요 이점인 형식 안전을 우회하기 때문에 절대적으로 필요한 경우에만 사용해야 합니다.그러나 컴파일 시 값의 유형을 알 수 없는 경우에는 필요할 수 있습니다.
예를 들어 ADO.NET 프레임워크를 통해 데이터베이스 필드 값을 읽는 경우.반환된 값은 정수이거나 문자열이거나 다른 값일 수 있으므로 형식은 다음과 같아야 합니다.object
그리고 고객 코드는 적절한 캐스팅을 수행해야 합니다.와 같은 사용하기 에 "Link-to-SQL"을 합니다.object
피할 수 있습니다.
에는 제릭이도전입다에음같과있컬다습은니렉이션네는기되ions▁like▁before▁collect있다와 같은 컬렉션이 있습니다.ArrayList
이 항목 유형다같습다니로 되어 .object
이것은 당신이 목록에 무엇이든 저장할 수 있다는 것을 의미하며, 당신은 유형 시스템이 불평하지 않고 숫자 목록에 문자열을 추가할 수 있다는 것을 의미합니다.제네릭은 이 문제를 해결하고 가치 유형 컬렉션을 사용할 때 복싱을 불필요하게 만듭니다.
다음과 같이 입력합니다.object
필요한 경우가 거의 없으며, 사용자는 이를 피하고 싶어합니다.일반적으로 코드가 값 유형과 참조 유형을 모두 처리해야 하는 경우 일반적으로 더 나은 솔루션입니다.
언급URL : https://stackoverflow.com/questions/2111857/why-do-we-need-boxing-and-unboxing-in-c
'programing' 카테고리의 다른 글
파이썬을 사용하여 Excel 문서 구문 분석 (0) | 2023.05.20 |
---|---|
VB.NET 코드를 C#로 마이그레이션할 때 for 루프가 다르게 작동하는 이유는 무엇입니까? (0) | 2023.05.20 |
종료 코드 1( 호출을 보려면 -v 사용), Xcode 8, Swift 3에서 링커 명령이 실패했습니다. (0) | 2023.05.15 |
출시 후 iOS 앱에 디버거를 연결하는 방법은 무엇입니까? (0) | 2023.05.15 |
QueryParser에 여러 필드를 통합하는 방법은 무엇입니까? (0) | 2023.05.15 |