C # işaretsiz int kullanmaktan kaçınmalı mıyım?


23

Geçenlerde C # 'da imzasız tamsayıların kullanımı hakkında düşündüm (ve diğer "yüksek seviye diller" için de benzer bir argüman söylenebilir sanırım)

Bir tam sayıya ihtiyaç duyduğumda normalde bir tamsayı boyutunun ikilemiyle karşı karşıya kalmazsam, bir örnek bir Person sınıfının yaş özelliği olabilir (ancak soru özelliklerle sınırlı değildir). Aklımda, görebildiğim kadarıyla, işaretsiz bir tamsayı ("uint"), imzalı bir tamsayı ("int") - okunabilirlik üzerinde kullanmanın yalnızca bir avantajı var. Bir yaşın ancak pozitif olabileceği fikrini ifade etmek istersem, yaş tipini uint olarak ayarlayarak bunu başarabilirim.

Öte yandan, imzasız tamsayılarla ilgili hesaplamalar her türlü hataya yol açabilir ve iki yaşın çıkarılması gibi işlemlerin yapılmasını zorlaştırır. (Bunu, Java'nın işaretsiz tamsayıları çıkarmasının nedenlerinden biri olduğunu okudum)

C # durumunda, belirleyicideki bir koruma maddesinin iki dünyanın en iyisini veren bir çözüm olacağını düşünebilirim, ancak, örneğin, bir yaş bazı yöntemlere geçtiğinde bu geçerli olmayacaktı. Bir çözüm, Yaş adı verilen bir sınıf tanımlamak ve mülk yaşının orada tek şey olmasını sağlamak olacaktır, ancak bu kalıp Bana birçok sınıf yarattıracak ve bir karışıklık kaynağı olacaktı (diğer geliştiriciler bir nesnenin yalnızca sarıcı olduğunu bilmezlerdi). ve daha sofistike bir şey olduğunda).

Bu konuda genel en iyi uygulamalar nelerdir? Bu tür bir senaryo ile nasıl başa çıkmalıyım?



1
Ayrıca, imzasız int, CLS uyumlu değildir; bu, diğer .NET dillerinden bunları kullanan API'leri arayamazsınız anlamına gelir.
Nathan Cooper,

2
@NathanCooper: ... "bunları kullanmak API'ler diyemezsin bazı diğer diller". Onlar için meta veriler standartlaştırılmıştır, bu nedenle imzalanmamış türleri destekleyen DO'nun tüm .NET dilleri birlikte çalışacaktır.
Ben Voigt

5
Özel örneğinizi ele almak için, her şeyden önce Age adlı bir mülküm olmazdı. Birthday veya CreationTime veya her neyse, diye bir mülküm olurdu ve yaşı hesapladım.
Eric Lippert,

2
“... ama bu kalıp Bana birçok sınıf yarattı ve bir karışıklık kaynağı olurdu” aslında yapılacak doğru şey. Sadece rezil İlkel Obsession anti paternini arayın .
Songo

Yanıtlar:


24

.NET Framework tasarımcıları, birkaç nedenden dolayı "genel amaçlı numara" olarak 32 bit işaretli bir tamsayı seçti:

  1. Negatif sayıları, özellikle de -1 (Çerçevenin bir hata durumunu belirtmek için kullandığı; negatif sayılar bir indeksleme bağlamında anlamlı olmasa da, her yerde indekslemenin zorunlu olduğu bir işaretli int'nin kullanılmasının nedeni) işleyebilir.
  2. Ekonomik olarak neredeyse her yerde kullanılabilecek kadar küçük olmakla birlikte çoğu amaca hizmet edecek kadar büyüktür.

İmzasız girişleri kullanmanın nedeni okunabilirlik değildir ; Sadece imzasız bir int'nin sağladığı matematiği alma yeteneğine sahip.

Koruma maddeleri, validasyon ve sözleşme ön koşulları, geçerli sayısal aralıkları garanti etmek için tamamen kabul edilebilir yollardır. Nadiren, gerçek dünyadaki bir sayısal aralık sıfır ve 2 32 -1 (veya seçtiğiniz sayısal sayı aralığı ne olursa olsun) arasındaki sayıya tekabül eder, bu nedenle uintarabirim sözleşmenizi pozitif sayılarla sınırlandırmak için konunun dışında.


2
Güzel cevap! Ayrıca, imzasız bir int'nin istemeden yanlışlıkla daha fazla hata üretebileceği bazı durumlar olabilir (muhtemelen hemen tespit edilse de, ancak biraz kafa karıştırıcı olabilir) - imzasız bir int sayacı ile ters döngü yapmayı düşünün, çünkü bir boyut tamsayıdır: for (uint j=some_size-1; j >= 0; --j)- whoops ( C #) bir sorun olup olmadığından emin değilim! Bu konuyu daha önce C tarafında imzasız int kullanmaya çalıştığımda kodda buldum - ve intdaha sonra lehine değiştirmeye karar verdik ve daha az derleyici uyarısı ile hayatımız çok daha kolaydı.

14
"Nadiren gerçek dünyadaki sayısal aralık sıfır ve 2 ^ 32-1 arasında bir sayıya karşılık gelir." Tecrübelerime göre, 2 ^ 31'den büyük bir sayıya ihtiyacınız olacaksa, 2 ^ 32'den büyük sayılara da ihtiyaç duyacağınız çok muhtemeldir. o nokta.
Mason Wheeler

3
@ Panzercrisis: Bu biraz şiddetli. Muhtemelen söylemek daha doğru olur "Kullanım intçoğu zaman olduğunu tespit kongre olduğu için ve çoğu insanlar rutin kullanılan görmek için beklemek olacak budur. Kullanım uintEğer bir özel Kapasitesi gerektirdiğinde uint." Çerçeve tasarımcılarının bu sözleşmeyi kapsamlı bir şekilde takip etmeye karar verdiklerini unutmayın, bu nedenle uintbirçok Çerçeve bağlamında bile kullanamazsınız (tür uyumlu değildir).
Robert Harvey

2
@Panzercrisis Aşırı derecede güçlü bir ifadeyle olabilir; ama ben Win32 apis'i çağırmak dışında çağrıda bulunduğumda hiç kullanılmamış bir tür C # kullanıp kullanmadığımdan emin değilim.
Dan Neely,

4
Gerçekten de oldukça nadirdir. İmzasız girişleri kullandığım tek zaman bit titreyen senaryolar.
Robert Harvey,

8

Genellikle, verileriniz için her zaman mümkün olan en spesifik veri türünü kullanmalısınız.

Örneğin, bir veritabanından veri çekmek için Entity Framework kullanıyorsanız, EF, veritabanında kullanılana en yakın veri türünü otomatik olarak kullanır.

C # ile bununla ilgili iki sorun var.
İlk olarak, çoğu C # geliştiricisi yalnızca inttam sayıları temsil etmek için kullanır (kullanmak için bir neden olmadıkça long). Bu, diğer geliştiricilerin veri türünü kontrol etmeyi düşünmeyecekleri anlamına gelir, bu nedenle yukarıda belirtilen taşma hatalarını alırlar. İkinci ve daha kritik bir konu, oldu / .NET'in o orijinal aritmetik operatörler yalnızca desteklenen int, uint, long, ulong, float, çift ve decimal*. Bugün hala durum budur (bkz. Bölüm C # 5.0 'da belirtilen 7.8.4 ). Aşağıdaki kodu kullanarak bunu kendiniz test edebilirsiniz:

byte a, b;
a = 1;
b = 2;
var c = a - b;      //In visual studio, hover over "var" and the tip will indicate the data type, or you can get the value from cName below.
string cName = c.GetType().Namespace + '.' + c.GetType().Name;

Bizim sonucu byte- ' bytedir int( System.Int32).

Bu iki konu, çok yaygın olan “sadece tüm sayılar için int kullan” uygulamasına yol açtı.

Bu nedenle sorunuzu yanıtlamak için, C # 'da aşağıdakilere uymamanız iyi bir fikirdir int:

  • Otomatik bir kod üreticisi farklı bir değer kullandı (Entity Framework gibi).
  • Projedeki diğer tüm geliştiriciler daha az yaygın veri türlerini kullandığınızın farkındadır (veri türünü ve neden kullandığınızı gösteren bir yorum ekleyin).
  • Daha az yaygın veri türleri zaten projede yaygın olarak kullanılmaktadır.
  • Program daha az yaygın veri türünün faydalarını, (a arasındaki fark böylece, RAM tutmak gerekir Bunlardan 100 milyona sahip bytebir intveya intbir longkritik olduğu veya imzasız aritmetik farklılıkların bahsedilmiştir).

Veriler üzerinde matematik yapmanız gerekirse, genel türlere bağlı kalın.
Unutmayın, bir türden diğerine yayın yapabilirsiniz. Bu, bir CPU durma noktasından daha az verimli olabilir, bu nedenle 7 genel türden biriyle muhtemelen daha iyi olursunuz, ancak gerekirse bir seçenektir.

Numaralandırma ( enum), yukarıdaki yönergelere ilişkin kişisel istisnalarımdan biridir. Yalnızca birkaç seçeneğim varsa, numaralandırmayı bayt veya kısa olarak belirteceğim . Eğer bayraklı bir numarada bu son bite ihtiyacım olursa uint, bayrak değerini ayarlamak için hex kullanabilmek için tip belirteceğim .

Değer kısıtlama koduna sahip bir özellik kullanıyorsanız, özet etiketinde hangi kısıtlamaların olduğunu ve neden olduğunu açıkladığınızdan emin olun.

* C # takma adları, bunun yerine System.Int32bir C # sorusu olduğu gibi .NET adları yerine kullanılır .

Not: .NET geliştiricilerin (bulamadığım) sınırlı sayıda aritmetik işlevi ve bunun için endişelenmeme nedenlerini gösteren bir blog veya makalesi vardı. Hatırladığım kadarıyla, diğer veri türlerine destek eklemek için hiçbir planları olmadığını belirtti.

Not: Java, imzalanmamış veri türlerini desteklememektedir ve daha önce 8 veya 16 bit tam sayıları desteklememiştir. Birçok C # geliştiricisi bir Java altyapısından geldiğinden veya her iki dilde de çalışması gerektiğinden, bir dilin sınırlamaları bazen yapay olarak diğerine deyatılabilir.


Benim genel kuralım basitçe, "yapamazsan int, kullan" dır.
PerryC

@PerryC En yaygın kongre olduğuna inanıyorum. Cevabımın amacı, dil özelliklerini kullanmanıza izin veren daha eksiksiz bir kongre sağlamaktı.
16'da 18

6

Esas olarak iki şeyin farkında olmanız gerekir: temsil ettiğiniz veriler ve hesaplamalarınızdaki ara adımlar.

Yaşın olması kesinlikle mantıklı geliyor unsigned int, çünkü genellikle olumsuz yaşları dikkate almıyoruz. Ama sonra bir yaşını diğerinden çıkarmaktan söz ediyorsun. Bir tamsayıyı diğerinden sadece kör bir şekilde çıkarırsak, daha önce olumsuz yaşların bir anlam ifade etmediğine karar vermiş olsak bile, negatif bir sayıyla sonuçlanabilir. Bu durumda, hesaplamanızın imzalı bir tamsayı ile yapılmasını istersiniz.

İmzasız değerlerin kötü olup olmadığına ilişkin olarak, imzasız değerlerin kötü olduğunu söylemenin çok büyük bir genelleme olduğunu söyleyebilirim. Java, belirttiğiniz gibi işaretsiz değerlere sahip değil ve beni sürekli rahatsız ediyor. A byte, 0-255 veya 0x00-0xFF arasında bir değere sahip olabilir. Ancak, 127'den (0x7F) büyük bir baytı başlatmak istiyorsanız, onu negatif bir sayı olarak yazmanız veya bir bayta bir tamsayı atmanız gerekir. Bu gibi görünen bir kod ile bitirdiniz:

byte a = 0x80; // Won't compile!
byte b = (byte) 0x80;
byte c = -128; // Equal to b

Yukarıdakiler beni sonuna kadar rahatsız ediyor. Bayt ile uğraşan çoğu aklı için mükemmel bir değer olsa da, bir baytın 197 değerine sahip olmasına izin verilmiyor. Tamsayıyı atabilir veya negatif değeri bulabilirim (bu durumda 197 == -59). Ayrıca şunu da düşün:

byte a = 70;
byte b = 80;
byte c = a + b; // c == -106

Görebildiğiniz gibi, geçerli değerlere sahip iki bayt eklemek ve geçerli bir değere sahip bir bayt ile sona ermek, işareti değiştirerek sona erer. Sadece bu değil, 70 + 80 == -106 olduğu hemen belli değil. Teknik olarak bu bir taşmadır, fakat bence (bir insan olarak) bir bayt, 0xFF altındaki değerler için taşmamalıdır. Kağıt üzerinde biraz aritmetik yaptığımda, 8. bitin işaret biti olduğunu düşünmüyorum.

Bit seviyesinde birçok tamsayı ile çalışıyorum ve her şeyin imzalanması genellikle her şeyi daha az sezgisel ve başa çıkmakta zorlaştırıyor, çünkü negatif bir sayının sağa kaydırılmasının numaranızda yeni rakamlar verdiğini hatırlamak zorundasınız 1. Oysa işaretsiz bir tamsayıyı sağa kaydırmak bunu asla yapmaz. Örneğin:

signed byte b = 0b10000000;
b = b >> 1; // b == 0b1100 0000
b = b & 0x7F;// b == 0b0100 0000

unsigned byte b = 0b10000000;
b = b >> 1; // b == 0b0100 0000;

Sadece gerekli olmamam gerektiğini düşündüğüm ilave adımlar atıyor.

byteYukarıda kullandığımda , aynı 32 bit ve 64 bit tam sayılar için de geçerlidir. Yok olmak unsignedsakıncalıyor ve Java gibi hiçbir şekilde izin vermeyen yüksek seviyeli diller olması beni şok ediyor. Ancak çoğu insan için bu bir sorun değildir, çünkü birçok programcı bit düzeyinde aritmetik ile uğraşmaz.

Sonunda, bunları bit olarak düşünüyorsanız, işaretsiz tamsayıları kullanmanız ve sayı olarak düşündüğünüzde imzalı tamsayıları kullanmanız yararlı olur.


7
İmzasız integral tipleri olmayan (özellikle baytlar için) dillerdeki hayal kırıklığınızı paylaşıyorum, ancak bunun burada sorulan soruya doğrudan bir cevap olmadığından korkuyorum. Belki de şöyle bir sonuç ekleyebilirsin: “
Değerlerini

1
Yukarıdaki yorumda söylediğim şey buydu. aynı şekilde düşünen başka birini görmekten memnun oldum.
robert bristow-johnson
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.