Neden C # 'da boks ve kutudan çıkarmaya ihtiyacımız var?


323

Neden C # 'da boks ve kutudan çıkarmaya ihtiyacımız var?

Boks ve boksun ne olduğunu biliyorum, ama gerçek kullanımını anlayamıyorum. Neden ve nerede kullanmalıyım?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing

Yanıtlar:


480

Neden

Birleştirilmiş bir tür sistemine sahip olmak ve değer türlerinin, referans türlerinin temeldeki verilerini temsil etme biçiminden temel verilerinin tamamen farklı bir temsiline sahip olmasına izin vermek (örn. An int, yalnızca bir referanstan tamamen farklı olan otuz iki bitlik bir kovadır) ) kullanımı önerilir.

Şöyle düşünün. Bir değişken otürünüz var object. Ve şimdi bir tane var intve içine koymak istiyorsunuz o. obir yerde bir şeye referanstır ve bir yerde bir şeye intkesin olarak referans değildir (sonuçta bu sadece bir sayıdır). Yani, yaptığınız şey: objectsaklayabilecek yeni bir şey intyaparsınız ve o nesneye bir başvuru atarsınız o. Bu işleme "boks" diyoruz.

Bu nedenle, birleşik bir tür sistemine sahip olmakla ilgilenmiyorsanız (yani, referans türleri ve değer türleri çok farklı temsillere sahipse ve ikisini "temsil etmek" için ortak bir yol istemiyorsanız), boksa ihtiyacınız yoktur. intTemel değerlerini temsil etmeyi umursamıyorsanız (örneğin, bunun yerine intreferans türleri de var ve sadece temel değerlerine bir referans depolayın), o zaman boksa ihtiyacınız yoktur.

nerede kullanmalıyım.

Örneğin, eski koleksiyon türü ArrayListyalnızca objects yiyor . Yani, sadece bir yerde yaşayan şeylere referansları saklar. Boks olmadan intböyle bir koleksiyona alamazsınız . Ama boks ile yapabilirsiniz.

Şimdi, jenerikler günlerinde buna gerçekten ihtiyacınız yok ve genellikle konuyu düşünmeden neşeyle devam edebilirsiniz. Ancak dikkat edilmesi gereken birkaç uyarı var:

Doğru:

double e = 2.718281828459045;
int ee = (int)e;

Bu değil:

double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception

Bunun yerine bunu yapmalısınız:

double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;

Öncelikle double( (double)o) işaretini açıkça kaldırmalı ve sonra bunu bir int.

Aşağıdakilerin sonucu nedir:

double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);

Bir sonraki cümleye geçmeden önce bir saniye düşünün.

Dediyseniz Trueve Falseharika! Bir dakika ne? Bunun nedeni ==, referans türlerinde, temel değerlerin eşit olup olmadığını değil, başvuruların eşit olup olmadığını kontrol eden başvuru eşitliğini kullanmasıdır. Bu yapmak çok kolay bir hatadır. Belki daha da ince

double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);

ayrıca yazdıracak False!

Söylemesi daha iyi:

Console.WriteLine(o1.Equals(o2));

o zaman şükürler olsun ki basar True.

Son bir incelik:

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

Çıktı nedir? Değişir! Eğer Pointbir olduğunu structçıkıştan 1ama eğer Pointbir classçıkıştan 2! Bir boks dönüşümü, davranıştaki farkı açıklayan kutunun değerinin bir kopyasını oluşturur.


@Jason İlkel listelerimiz varsa, herhangi bir boks / kutuyu kullanmanın bir nedeni olmadığını mı söylüyorsunuz?
Pacerier

"İlkel liste" ile ne demek istediğinden emin değilim.
jason

3
Eğer performansına etkisi konuşmak memnun Could boxingve unboxing?
Kevin Meredith


2
Mükemmel cevap - saygın kitaplarda okuduğum açıklamaların çoğundan daha iyi.
FredM

58

.NET çerçevesinde iki tür türü vardır - değer türleri ve başvuru türleri. Bu, OO dillerinde nispeten yaygındır.

Nesneye yönelik dillerin önemli özelliklerinden biri, örnekleri tip-agnostik bir şekilde ele alma yeteneğidir. Buna polimorfizm denir . Polimorfizmden yararlanmak istediğimiz için, ancak iki farklı türümüz olduğu için, bunları bir araya getirmenin bir yolu olmalı, böylece birini ya da diğerini aynı şekilde idare edebiliriz.

Şimdi, eski günlerde (Microsoft.NET 1.0), bu yeni çıkmış jenerikler hullabaloo yoktu. Bir değer türüne ve bir başvuru türüne hizmet verebilecek tek bir bağımsız değişkeni olan bir yöntem yazamazsınız. Bu bir polimorfizm ihlalidir. Böylece boks, bir değer türünü bir nesneye zorlamak için bir araç olarak benimsenmiştir.

Bu mümkün olmasaydı, çerçeve tek amacı diğer türleri kabul etmek olan yöntem ve sınıflarla dolup taşacaktı. Sadece bu değil, aynı zamanda değer türleri ortak bir tür atayı gerçekten paylaşmadığından, her bir değer türü (bit, bayt, int16, int32, vb.) İçin farklı bir yöntem aşırı yüklemesi yapmanız gerekir.

Boks bunun olmasını engelledi. İşte bu yüzden İngilizler Boks Günü'nü kutluyor.


1
Jeneriklerden önce, birçok şey yapmak için otomatik boks gerekliydi; jeneriklerin varlığı göz önüne alındığında, eski kod ile uyumluluğu sürdürmek için ihtiyaç olmasa, bence .net zımni boks dönüşümleri olmadan daha iyi olurdu. Gibi bir değer türü Döküm List<string>.Enumeratoriçin IEnumerator<string>verim çoğunlukla sınıf türü gibi davranan bir nesne, ancak bir kırık ile Equalsyöntem. Döküm için daha iyi bir yol List<string>.Enumeratoriçin IEnumerator<string>bir özel dönüşüm operatörü aramak olurdu, ama bu bir zımni dönüşüm önler varlığı.
supercat

42

Bunu anlamanın en iyi yolu, C #'ın üzerine inşa ettiği alt düzey programlama dillerine bakmaktır.

C gibi en alt düzey dillerde, tüm değişkenler tek bir yere gider: Yığın. Bir değişkeni her bildirdiğinizde, Yığına gider. Bunlar yalnızca bir bool, bayt, 32 bit int, 32 bit uint, vb. Gibi ilkel değerler olabilir. Yığın hem basit hem de hızlıdır. Değişkenler eklendikçe sadece bir diğerinin üstüne giderler, bu yüzden ilk bildirdiğinizde 0x00, diğeri 0x01'de, diğeri RAM'de 0x02'de vb. Oturur. Ayrıca, değişkenler genellikle derleme Böylece programı çalıştırmadan önce adresleri bilinir.

Bir sonraki aşamada, C ++ gibi, Yığın adı verilen ikinci bir bellek yapısı tanıtılır. Hala yığın içinde yaşıyorsanız, ancak bir nesnenin ilk baytı için bellek adresini depolayan ve nesnenin yığın içinde yaşadığı Pointers adı verilen özel ints eklenebilir. Yığın bir tür karışıklık ve bakımı biraz pahalı, çünkü Yığın değişkenlerinin aksine, bir program yürütülürken doğrusal olarak yukarı ve aşağı yığılmazlar. Belirli bir sırayla gelip gidebilirler ve büyüyebilir ve küçülebilirler.

İşaretçilerle uğraşmak zordur. Bunlar bellek sızıntılarının, arabellek taşmalarının ve hayal kırıklığının sebebidir. Kurtarmaya C #.

Daha yüksek bir düzeyde, C #, işaretçileri düşünmenize gerek yoktur - .Net çerçevesi (C ++ ile yazılmış) bunları sizin için düşünür ve bunları Nesnelere Referanslar olarak sunar ve performans için daha basit değerleri saklamanızı sağlar Değer Türleri olarak bools, bayt ve ints gibi. Kaputun altında, bir Sınıfı başlatan nesneler ve şeyler pahalı, Bellek Yönetimli Yığın'a giderken, Değer Türleri düşük seviyeli C - süper hızlı olan aynı Yığına girer.

Bu iki temel bellek kavramı (ve depolama stratejileri) arasındaki etkileşimi bir kodlayıcının bakış açısından basit tutmak adına, Değer Türleri herhangi bir zamanda Bokslanabilir. Boks, değerin Yığından kopyalanmasına, bir Nesneye konmasına ve Yığın üzerine yerleştirilmesine neden olur - daha pahalı, ancak Referans dünyası ile akışkan etkileşimi. Diğer cevapların işaret ettiği gibi, örneğin şunları söylediğinizde bu gerçekleşir:

bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!

Boksun avantajının güçlü bir örneği null için bir kontroldür:

if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false

Nesnemiz o teknik olarak Yığındaki, B yığınımızın Yığına kopyalanan bir kopyasına işaret eden bir adrestir. Bo, kutulu ve oraya konduğu için null değerini kontrol edebiliriz.

Genel olarak, boksa ihtiyacınız yoksa, örneğin bir argümana bir nesne olarak int / bool / herhangi bir şey iletmekten kaçınmalısınız. .Net'te Değer Türlerini nesne olarak geçirmeyi talep eden bazı temel yapılar vardır (ve bu nedenle Boks gerektirir), ancak çoğu zaman asla Box'a ihtiyacınız yoktur.

Kaçınmanız gereken Boks gerektiren tarihsel C # yapılarının kapsamlı olmayan bir listesi:

  • Etkinlik sistemi , saf kullanımda bir Yarış Koşulu olduğu ortaya çıkar ve zaman uyumsuzluğu desteklemez. Boks problemini ekleyin ve muhtemelen bundan kaçınılmalıdır. (Örneğin Generics kullanan bir zaman uyumsuz olay sistemiyle değiştirebilirsiniz.)

  • Eski Diş Açma ve Zamanlayıcı modelleri, parametreleri üzerinde bir Kutu zorladı, ancak daha temiz ve daha verimli olan async / await ile değiştirildi.

  • Net 1.1 Koleksiyonlar tamamen Boks'a dayanıyordu çünkü Generics'ten önce geldiler. Bunlar hala System.Collections'da tekmeliyor. Herhangi bir yeni kodda Bokstan kaçınmanın yanı sıra size daha güçlü tip güvenliği de sağlayan System.Collections.Generic Koleksiyonlarını kullanmalısınız .

Boksu zorlayan yukarıdaki tarihsel problemlerle uğraşmak zorunda kalmadıkça ve daha sonra Boxed olacağını bildiğinizde daha sonra Boxing'in performans isabetinden kaçınmak istemiyorsanız, Değer Türlerinizi nesne olarak beyan etmekten veya geçmekten kaçınmalısınız.

Mikael'in aşağıdaki önerisine göre:

Bunu yap

using System.Collections.Generic;

var employeeCount = 5;
var list = new List<int>(10);

Bu değil

using System.Collections;

Int32 employeeCount = 5;
var list = new ArrayList(10);

Güncelleme

Bu cevap aslında Int32, Bool vb. Önerdi, aslında Değer Türleri için basit takma adlar olduklarında boksa neden olur. Yani .Net, Bool, Int32, String ve C # gibi türlere sahiptir ve herhangi bir işlevsel fark olmadan bool, int, string için takma adlar kullanır.


4
Bana yüzlerce programcı ve BT uzmanının yıllar içinde açıklayamadıklarını öğrettiniz, ancak kaçınmak yerine ne yapmanız gerektiğini söylemek için değiştirin , çünkü takip etmek biraz zorlaştı .. zemin kuralları çoğu zaman gitmez 1 bunu yapamazsın, bunun yerine yapamazsın
Mikael Puusaari

2
Bu cevap yüzlerce CEVAP olarak işaretlenmeliydi!
Pouyan

3
c # "Int" yoktur, int ve Int32 vardır. bir değer türü ve diğer değer türü kaydırma bir başvuru türü belirten yanlış olduğuna inanıyoruz. yanılmıyorsam Java için bu doğru, ama C # değil. C # 'da IDE'de mavi görünen olanlar yapı tanımları için takma adlardır. So: int = Int32, bool = Boolean, string = Dize. Boolean üzerinden bool kullanmanın nedeni, MSDN tasarım yönergelerinde ve kurallarında olduğu gibi önerilmesidir. Aksi takdirde bu cevabı seviyorum. Ama bana yanlış olduğunu kanıtlayana veya cevabını düzeltene kadar oy kullanacağım.
Heriberto Lugo

2
Bir değişkeni int, diğerini Int32 veya bool ve Boolean olarak bildirirseniz - sağ tıklatma ve görüntüleme tanımı, bir yapı için aynı tanımla sonuçlanır.
Heriberto Lugo

2
@HeribertoLugo doğru, "Değer Türlerinizi bool yerine Bool olarak bildirmekten kaçınmalısınız" satırı yanlıştır. OP'nin işaret ettiği gibi, boolünüzü (veya Boolean'ı veya başka bir değer türünü) Object olarak bildirmekten kaçınmalısınız. bool / Boolean, int / Int32, sadece C # ve .NET arasındaki takma adlardır
STW

21

Boks gerçekten kullandığınız bir şey değildir - çalışma zamanının kullandığı bir şeydir, böylece gerektiğinde referans ve değer türlerini aynı şekilde işleyebilirsiniz. Örneğin, bir tamsayıların listesini tutmak için bir ArrayList kullandıysanız, tamsayılar ArrayList'teki nesne türü yuvalarına sığacak şekilde kutulanmışlardır.

Genel koleksiyonları kullanarak bu hemen hemen ortadan kalkar. Bir oluşturursanız List<int>, hiçbir boks yapılmaz - List<int>tamsayıları doğrudan tutabilir.


Bileşik dize biçimlendirme gibi şeyler için yine de boksa ihtiyacınız var. Jenerik ilaç kullanırken bu kadar sık ​​göremeyebilirsiniz, ama kesinlikle hala orada.
Jeremy S

1
true - ADO.NET'te her zaman görünür - sql parametre değerleri gerçek veri türü ne olursa olsun tüm nesnelerdir
Ray

11

Boks ve Kutudan Çıkarma, değer türü nesneleri başvuru türü olarak işlemek için özel olarak kullanılır; gerçek değerlerini yönetilen öbeğe taşıma ve değerlerine referans olarak erişme.

Boks ve kutudan çıkarma olmadan değer türlerini asla referans olarak geçemezsiniz; ve bu, nesne türlerini değer türleri olarak geçiremeyeceğiniz anlamına gelir.


neredeyse 10 yıl sonra hala güzel cevap efendim +1
snr

1
Sayısal türlerin referansı ile geçiş, boks olmayan dillerde bulunur ve diğer diller değer türlerini boks yapmadan Nesne örnekleri olarak ele alır ve değeri yığına taşır (örneğin, işaretçilerin 4 bayt sınırına hizalandığı dinamik dillerin uygulamaları alt dördü kullanır) değerin tam bir nesneden ziyade bir tam sayı veya sembol olduğunu gösteren referans bitleri; bu tür değer türleri değiştirilemez ve bir işaretçi ile aynı boyuttadır).
Pete Kirkham

8

Bir şey kutusundan çıkarmak zorunda kaldım son yer bir veritabanından bazı veriler alınan bazı kod yazarken oldu (kullanmıyordum SQL LINQ , sadece düz eski ADO.NET ):

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

Temel olarak, jeneriklerden önce eski API'lerle çalışıyorsanız boksla karşılaşırsınız. Bunun dışında, o kadar yaygın değil.


4

Parametre olarak nesneye ihtiyaç duyan bir işleve sahip olduğumuzda, ancak geçirilmesi gereken farklı değer türlerine sahip olduğumuzda boks gereklidir, bu durumda, işleve geçmeden önce değer türlerini nesne veri türlerine dönüştürmemiz gerekir.

Bunun doğru olduğunu düşünmüyorum, bunun yerine şunu deneyin:

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

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

Bu iyi çalışıyor, boks / kutulama kullanmadım. (Derleyici bunu perde arkasında yapmadıkça?)


Bunun nedeni, her şeyin System.Object öğesinden miras almasıdır ve yönteme ekstra bilgi içeren bir nesne verirsiniz, bu nedenle temel olarak test yöntemini beklediği şeyle ve özellikle herhangi bir şey beklemediğinden beklediğiniz herhangi bir şeyle çağırıyorsunuzdur. .NET'te çok şey perde arkasında ve bunun çok basit bir dil olmasının nedeni
Mikael Puusaari

1

.Net dosyasında, her Object örneği veya bundan türetilen herhangi bir tür, türü hakkında bilgi içeren bir veri yapısı içerir. .Net içindeki "Gerçek" değer türleri bu tür bilgileri içermez. Değer türlerindeki verilerin nesneden türetilen türler almayı bekleyen rutinler tarafından değiştirilmesine izin vermek için, sistem her bir değer türü için aynı üyelere ve alanlara sahip karşılık gelen bir sınıf türünü otomatik olarak tanımlar. Boks, alanları bir değer türü örneğinden kopyalayarak bu sınıf türünde yeni bir örnek oluşturur. Kutudan çıkarma, sınıf türünün bir örneğindeki alanları değer türünün bir örneğine kopyalar. Değer türlerinden oluşturulan tüm sınıf türleri ironik olarak adlandırılan ValueType sınıfından türetilir (ismine rağmen aslında bir referans türüdür).


0

Bir yöntem yalnızca bir referans türünü parametre olarak aldığında (kısıtlama yoluyla bir sınıf olarak sınırlandırılan genel bir yöntem diyelim new), ona bir referans türü iletemezsiniz ve bunu kutlamanız gerekir.

Bu aynı zamanda çektiğiniz yöntemleri için geçerlidir objectparametre olarak - bu olacak olması bir başvuru türü olması.


0

Genel olarak, genellikle değer türlerinizi bokslamaktan kaçınmak istersiniz.

Bununla birlikte, bunun yararlı olduğu nadir durumlar vardır. Örneğin 1.1 çerçevesini hedeflemeniz gerekiyorsa, genel koleksiyonlara erişemezsiniz. .NET 1.1'de koleksiyonların herhangi bir şekilde kullanılması, değer türünüzün System.Object olarak ele alınmasını gerektirir ve bu da boks / kutudan çıkmaya neden olur.

Bunun hala .NET 2.0 ve sonraki sürümlerinde yararlı olması gereken durumlar vardır. Değer türleri de dahil olmak üzere tüm türlerin doğrudan bir nesne olarak değerlendirilebileceği gerçeğinden yararlanmak istediğinizde, boks / kutulamayı kullanmanız gerekebilir. Bu, zaman zaman kullanışlı olabilir, çünkü bir koleksiyondaki herhangi bir türü kaydetmenize izin verir (genel bir koleksiyonda T yerine nesne kullanarak), ancak genel olarak, tür güvenliğini kaybettiğiniz için bundan kaçınmak daha iyidir. Boksun sık sık meydana geldiği durumlardan biri de Yansıma'yı kullanmaktır - yansımadaki çağrıların çoğu değer türleriyle çalışırken boks / kutudan çıkarmayı gerektirir, çünkü tür önceden bilinmemektedir.


0

Boks, bir değerin öbek üzerindeki bir nesnede bazı uzaklıktaki verilerle bir referans türüne dönüştürülmesidir.

Boks aslında ne yapar. İşte bazı örnekler

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

Mono'da kutudan çıkarma, nesnedeki 2 noktadan oluşan bir ofsette bir işaretçi (örn. 16 bayt) döküm işlemidir. A gpointerbir void*. Tanımına bakıldığında bu mantıklıMonoObject sadece veri için bir başlık olduğu için .

C ++

C ++ 'da bir değeri kutulamak için şöyle bir şey yapabilirsiniz:

#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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.