C #에서 권투 및 언 박싱이 필요한 이유는 무엇입니까?


323

C #에서 권투 및 언 박싱이 필요한 이유는 무엇입니까?

복싱과 언 박싱이 무엇인지 알고 있지만 실제 사용을 이해할 수는 없습니다. 왜 그리고 어디서 사용해야합니까?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing

답변:


480

통합 형식 시스템을 보유하고 값 형식이 참조 형식이 기본 데이터를 나타내는 방식과 기본 데이터를 완전히 다르게 표현할 수 있도록 허용 (예 : inta는 참조와 완전히 다른 32 비트의 버킷 임) 유형).

이것을 이렇게 생각하십시오. o유형 의 변수 가 object있습니다. 그리고 지금 당신은 int그것을 가지고 o있습니다. o는 어딘가에 대한 참조이며, 어딘가에 int대한 참조가 아닙니다 (결국 숫자 일뿐입니다). 그래서 당신이하는 일은 이것입니다 :를 object저장할 수있는 새로운 것을 만들고 int그 객체에 대한 참조를에 할당합니다 o. 우리는이 과정을 "복싱"이라고 부릅니다.

따라서 통합 유형 시스템에 신경 쓰지 않는다면 (즉, 참조 유형과 값 유형이 매우 다른 표현을 가지고 있고 두 가지를 "표현"하는 일반적인 방법을 원하지 않는 경우) 권투가 필요하지 않습니다. int기본 값 을 나타내는 것에 신경 쓰지 않는다면 (즉, int참조 유형이기도하고 기본 값에 대한 참조를 저장하는 경우) 권투가 필요하지 않습니다.

어디서 사용해야합니까?

예를 들어, 이전 컬렉션 유형 ArrayListobjects 만 먹는다 . 즉, 어딘가에 사는 것에 대한 참조 만 저장합니다. 권투가 없으면 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);

다음 문장으로 넘어 가기 전에 잠시 생각해보십시오.

당신이 말한 경우 TrueFalse큰! 무엇을 기다립니다? 이는 ==참조 유형에서 기본 값이 아닌 참조가 동일한 지 확인하는 참조 평등을 사용 하기 때문 입니다. 이것은 위험한 실수입니다. 아마도 더 미묘한

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);

출력은 무엇입니까? 때에 따라 다르지! 경우 PointA는 struct다음 출력은 1있지만, 경우에 Point있는 class다음 출력은 2! 복싱 변환은 동작의 차이를 설명하는 복싱되는 값의 복사본을 만듭니다.


@Jason 프리미티브리스트가 있다면, 권투 / 언 박스를 사용할 이유가 없다고 말하는가?
Pacerier

"원시 목록"이 무슨 뜻인지 잘 모르겠습니다.
Jason

3
boxing및 의 성능 영향에 대해 이야기 해 주 unboxing시겠습니까?
케빈 메러디스


2
훌륭한 답변-잘 알려진 책에서 읽은 대부분의 설명보다 낫습니다.
FredM

58

.NET 프레임 워크에는 값 유형과 참조 유형의 두 가지 유형이 있습니다. 이것은 OO 언어에서 비교적 일반적입니다.

객체 지향 언어의 중요한 기능 중 하나는 유형에 관계없이 인스턴스를 처리 할 수 ​​있다는 것입니다. 이것을 다형성 이라고합니다 . 우리는 다형성을 이용하기를 원하지만, 두 종류의 다른 유형이 있기 때문에, 그것들을 하나로 모으는 방법이 있어야 하나 또는 다른 방법으로 처리 할 수 ​​있습니다.

이제 옛날 (Microsoft.NET의 1.0)로 돌아가서이 새로운 제네릭 훌라 바루는 없었습니다. 값 유형과 참조 유형을 처리 할 수있는 단일 인수가있는 메소드를 작성할 수 없습니다. 그것은 다형성의 위반입니다. 따라서 복싱은 가치 유형을 객체로 강제하는 수단으로 채택되었습니다.

이것이 가능하지 않다면, 프레임 워크는 다른 유형의 유형을 수용하는 유일한 목적을 가진 메소드와 클래스로 어지럽 힐 것입니다. 뿐만 아니라 값 유형은 공통 유형 조상을 공유하지 않으므로 각 값 유형 (비트, 바이트, int16, int32 등)에 대해 서로 다른 메서드 오버로드를 가져야합니다.

권투는 이것이 일어나지 않도록 막았습니다. 그리고 이것이 영국이 박싱 데이를 축하하는 이유입니다.


1
제네릭 이전에는 많은 작업을 수행하기 위해 자동 권투가 필요했습니다. 그러나 제네릭의 존재로 인해 이전 코드와의 호환성을 유지할 필요가 없었기 때문에 .net은 권투 변환을 암시하지 않으면 더 좋을 것이라고 생각합니다. 값 유형을 캐스팅하면 클래스 유형처럼 동작하지만 메서드 가 깨진 객체 List<string>.EnumeratorIEnumerator<string>생성 Equals됩니다. 캐스트에 더 좋은 방법 List<string>.Enumerator으로 IEnumerator<string>사용자 정의 변환 연산자를 호출하는 것입니다,하지만 묵시적 변환 방지의 존재.
supercat

42

이를 이해하는 가장 좋은 방법은 C #이 구축 한 하위 프로그래밍 언어를 보는 것입니다.

C와 같은 가장 낮은 수준의 언어에서 모든 변수는 스택으로 이동합니다. 변수를 선언 할 때마다 스택으로 이동합니다. 이들은 bool, byte, 32-bit int, 32-bit uint 등과 같은 기본 값일 수 있습니다. Stack은 단순하고 빠릅니다. 변수가 추가됨에 따라 변수가 하나 위로 올라가므로 처음 선언하는 것은 0x00, 다음은 0x01, 다음은 RAM의 0x02 등입니다. 또한 변수는 종종 컴파일시 사전 주소 지정됩니다. 프로그램을 실행하기 전에 주소를 알 수 있습니다.

다음 단계에서는 C ++과 같이 힙이라는 두 번째 메모리 구조가 도입되었습니다. 여전히 스택에 살고 있지만 Pointers 라는 특수 정수 를 스택에 추가하여 Object의 첫 번째 바이트에 대한 메모리 주소를 저장하고 Object가 힙에 있습니다. 힙은 일종의 혼란스럽고 유지 보수 비용이 많이 듭니다. 스택 변수와 달리 프로그램이 실행될 때 선형으로 쌓여서 쌓이지 않습니다. 그들은 특정한 순서로왔다 갔다 할 수 있고, 자라고 줄어들 수 있습니다.

포인터 다루기가 어렵습니다. 메모리 누수, 버퍼 오버런 및 좌절의 원인입니다. 구조에 C #.

더 높은 수준의 C #에서는 포인터에 대해 생각할 필요가 없습니다. .Net 프레임 워크 (C ++로 작성)는이를 고려하여이를 객체에 대한 참조로 제공하고 성능을 위해 더 간단한 값을 저장할 수 있도록합니다. bools, bytes 및 int와 같은 Value Types. 후드 아래에서 클래스를 인스턴스화하는 객체와 물건은 값 비싼 메모리 관리 힙을 사용하고 값 유형은 저수준 C-초고속과 동일한 스택에 저장됩니다.

코더의 관점에서 근본적으로 다른 두 가지 메모리 개념 (및 저장 전략) 간의 상호 작용을 간단하게 유지하기 위해 언제라도 Value Types를 박스로 만들 수 있습니다. 복싱은 스택에서 값을 복사하여 객체를 넣고 힙에 배치합니다. 더 비싸지 만 참조 세계와의 유동적 상호 작용입니다. 다른 답변에서 지적했듯이 예를 들어 다음과 같이 말할 때 발생합니다.

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

우리의 객체 o는 기술적으로 스택의 주소로서 힙에 복사 된 bool b의 복사본을 가리 킵니다. bool이 Boxed되어 있기 때문에 o를 null로 확인할 수 있습니다.

int / bool / what을 인수로 전달하기 위해 필요한 경우가 아니면 권투를 피해야합니다. .Net에는 여전히 값 유형을 객체로 전달해야하며 (권투가 필요) 기본 구조가 있지만 대부분 상자에 넣을 필요는 없습니다.

복싱이 필요한 역사적 C # 구조의 전체 목록은 피해야합니다.

  • 이벤트 시스템 순진한 경쟁 조건을 가지고 있으며 비동기를 지원하지 않습니다. 권투 문제에 추가하고 아마 피해야합니다. (예를 들어 Generics를 사용하는 비동기 이벤트 시스템으로 대체 할 수 있습니다.)

  • 이전 스레딩 및 타이머 모델은 매개 변수에 Box를 강제했지만 async / await로 대체되었으며 훨씬 깨끗하고 효율적입니다.

  • .Net 1.1 컬렉션은 제네릭보다 먼저 나왔기 때문에 권투에 전적으로 의존했습니다. 이들은 여전히 ​​System.Collections에서 발로 움직입니다. 새로운 코드에서, 당신은 권투를 피하는 것 외에도 강력한 타입 안전성을 제공 하는 System.Collections.Generic의 Collections를 사용해야합니다 .

복싱을 강제하는 위의 역사적 문제를 처리해야하는 경우가 아니라면 나중에 Value Box를 선언하거나 전달하지 말아야합니다.

아래 미카엘의 제안 :

이 작업을 수행

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 등이 boxing을 제안했습니다. 실제로 값 유형의 간단한 별칭 일 때입니다. 즉, .Net에는 Bool, Int32, String 및 C #과 같은 유형이 있으며 기능상의 차이없이 bool, int, string으로 별칭을 지정합니다.


4
수백 명의 프로그래머와 IT 전문가가 몇 년 동안 설명 할 수 없었던 점을 가르쳐 주었지만 , 따르기가 어려워서 피해야하는 대신 수행 해야 할 것을 말하도록 변경했습니다 . .이 작업을 수행 할 수 없다, 대신에이 작업을 수행
미카엘 Puusaari

2
이 답변은 100 번 답변으로 표시되었습니다!
Pouyan

3
C #에는 "Int"가없고 int와 Int32가 있습니다. 하나는 가치 유형이고 다른 하나는 가치 유형을 감싸는 참조 유형이라고 잘못 말한 것 같습니다. 내가 잘못 생각하지 않는 한, Java에서는 사실이지만 C #에서는 그렇지 않습니다. C #에서 IDE에서 파란색으로 표시되는 것은 구조체 정의의 별칭입니다. 따라서 : int = Int32, bool = Boolean, string = String. 부울 오버 부울을 사용하는 이유는 MSDN 디자인 지침 및 규칙과 같이 제안되기 때문입니다. 그렇지 않으면이 답변을 좋아합니다. 그러나 나는 당신이 나를 잘못 증명하거나 대답에서 고칠 때까지 투표를 중단 할 것입니다.
Heriberto Lugo

2
변수를 int로 선언하고 다른 변수를 Int32로 선언하거나 부울 및 부울-마우스 오른쪽 단추를 클릭하고 정의를 정의하면 구조체에 대해 동일한 정의로 끝납니다.
Heriberto Lugo

2
@HeribertoLugo가 정확합니다. "값 유형을 bool 대신 Bool로 선언하지 말아야합니다"라는 문구가 잘못되었습니다. OP가 지적했듯이 bool (또는 Boolean 또는 다른 값 유형)을 Object로 선언하지 않아야합니다. bool / Boolean, int / Int32는 C #과 .NET 사이의 별명입니다. docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
STW

21

복싱은 실제로 사용하는 것이 아닙니다. 런타임에서 사용하는 것이기 때문에 필요할 때 동일한 방식으로 참조 및 값 유형을 처리 할 수 ​​있습니다. 예를 들어, ArrayList를 사용하여 정수 목록을 보유한 경우 ArrayList의 객체 유형 슬롯에 맞게 정수가 상자로 표시됩니다.

이제 일반 컬렉션을 사용하면 거의 사라집니다. 을 만들면 List<int>권투가 수행되지 않습니다 List<int>. 정수를 직접 보유 할 수 있습니다.


복합 문자열 형식과 같은 것들에 대해서는 여전히 권투가 필요합니다. 제네릭을 사용할 때 자주 볼 수는 없지만 여전히 존재합니다.
Jeremy S

1
true-ADO.NET에서도 항상 나타납니다. 실제 데이터 유형이 무엇이든 SQL 매개 변수 값은 모두 '개체'입니다.
Ray

11

복싱 및 Unboxing은 특히 값 형식 개체를 참조 형식으로 처리하는 데 사용됩니다. 실제 값을 관리되는 힙으로 이동하고 참조로 값에 액세스합니다.

boxing과 unboxing이 없으면 절대 참조로 값 유형을 전달할 수 없습니다. 즉, 값 유형을 Object 인스턴스로 전달할 수 없습니다.


거의 10 년이 지난 후에도 여전히 좋은 답변 sir +1
snr

1
숫자 유형을 참조로 전달하면 권투가없는 언어로 존재하며 다른 언어에서는 값을 권투없이 객체의 인스턴스로 처리하고 값을 힙으로 이동합니다 (예 : 포인터가 4 바이트 경계에 정렬되는 동적 언어의 구현은 하위 4 개를 사용함) 값이 전체 객체가 아닌 정수 또는 기호임을 나타내는 참조 비트; 이러한 값 유형은 변경할 수 없으며 포인터와 크기가 동일합니다).
피트 커캄

8

내가 뭔가를 풀어야했던 마지막 장소는 데이터베이스에서 일부 데이터를 검색하는 코드를 작성할 때였습니다 ( LINQ to SQL 사용하지 않고 일반 오래된 ADO.NET ).

int myIntValue = (int)reader["MyIntValue"];

기본적으로 제네릭 이전의 이전 API로 작업하는 경우 복싱이 발생합니다. 그 외에는 일반적이지 않습니다.


4

복싱은 객체로 매개 변수를 필요로하는 함수가있을 때 필요하지만, 전달해야하는 다른 값 유형이있는 경우, 함수에 전달하기 전에 먼저 값 유형을 객체 데이터 유형으로 변환해야합니다.

나는 그것이 사실이라고 생각하지 않습니다, 대신 이것을 시도하십시오 :

class Program
    {
        static void Main(string[] args)
        {
            int x = 4;
            test(x);
        }

        static void test(object o)
        {
            Console.WriteLine(o.ToString());
        }
    }

그것은 잘 작동합니다, 나는 권투 / unboxing을 사용하지 않았습니다. (컴파일러가 그것을 배후에서 수행하지 않는 한?)


모든 것이 System.Object에서 상속되기 때문에 메서드에 추가 정보가 포함 된 개체를 제공하기 때문에 기본적으로 테스트 대상에 예상되는 것과 특별히 기대하지 않는 모든 것을 테스트 메서드를 호출합니다. .NET의 많은 부분이 뒤에서 이루어지고 사용하기 매우 간단한 언어 인 이유
Mikael Puusaari

1

.net에서 Object의 모든 인스턴스 또는 그로부터 파생 된 모든 유형에는 해당 유형에 대한 정보가 포함 된 데이터 구조가 포함됩니다. .net의 "실제"값 유형에는 이러한 정보가 포함되어 있지 않습니다. 오브젝트에서 파생 된 유형을 수신 할 것으로 예상되는 루틴이 값 유형의 데이터를 조작 할 수 있도록하기 위해 시스템은 각 값 유형에 대해 동일한 멤버 및 필드를 가진 해당 클래스 유형을 자동으로 정의합니다. Boxing은 값 유형 인스턴스에서 필드를 복사하여이 클래스 유형의 새 인스턴스를 만듭니다. Unboxing은 클래스 유형의 인스턴스에서 값 유형의 인스턴스로 필드를 복사합니다. 값 형식에서 생성 된 모든 클래스 형식은 아이러니하게 명명 된 클래스 ValueType (이름에도 불구하고 실제로 참조 형식 임)에서 파생됩니다.


0

메소드가 참조 유형을 매개 변수로만 사용하는 경우 (예 : 일반 메소드는 매개 변수를 통해 클래스로 제한됨) new ) 참조 유형을 전달할 수 없으므로 상자에 넣어야합니다.

이것은 또한 가지고 어떤 방법도 마찬가지입니다 object매개 변수로 -이 것 참조 형식이 될 수 있습니다.


0

일반적으로 값 유형을 상자에 넣지 않으려 고합니다.

그러나 이것이 유용한 경우는 드물다. 예를 들어 1.1 프레임 워크를 대상으로해야하는 경우 일반 컬렉션에 액세스 할 수 없습니다. .NET 1.1에서 컬렉션을 사용하려면 값 형식을 System.Object로 처리해야합니다.

.NET 2.0 이상에서 여전히 유용한 경우가 있습니다. 값 유형을 포함한 모든 유형을 객체로 직접 처리 할 수 ​​있다는 사실을 활용하려면 언제든지 권투 / 박스 해제를 사용해야합니다. 이것은 일반 컬렉션에서 T 대신 객체를 사용하여 컬렉션의 모든 유형을 저장할 수 있기 때문에 때로는 편리 할 수 ​​있지만 일반적으로 형식 안전성을 잃을 때 피하는 것이 좋습니다. 그러나 복싱이 자주 발생하는 경우는 리플렉션을 사용하는 경우입니다. 리플렉션을 사용하는 많은 통화는 미리 유형을 알 수 없으므로 값 유형으로 작업 할 때 복싱 / 언 박싱이 필요합니다.


0

복싱은 힙에있는 개체의 일부 오프셋에서 데이터를 사용하여 값을 참조 유형으로 변환하는 것입니다.

실제로 권투는 무엇을합니까. 여기 몇 가지 예가 있어요

모노 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_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;

모노에서 Unboxing은 객체에서 2 개의 gpointer 오프셋 (예 : 16 바이트)에서 포인터를 캐스팅하는 프로세스입니다. A 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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.