Düşüncemi C ++ 'dan C #' ye nasıl taşıyabilirim?


12

Deneyimli bir C ++ geliştiricisiyim, dili ayrıntılı olarak biliyorum ve bazı özelliklerini yoğun bir şekilde kullandım. Ayrıca, OOD prensiplerini ve tasarım modellerini biliyorum. Şimdi C # öğreniyorum ama C ++ zihniyet kurtulmak değil duygu durduramıyorum. Kendimi C ++ güçlü yönlerine o kadar sıkı bağladım ki, bazı özellikler olmadan yaşayamam. Ve onlar için C # iyi bir geçici çözüm veya yedek bulamıyorum.

Hangi iyi uygulamalar , tasarım kalıpları , C # 'da C ++ perspektifinden farklı deyimler önerebilirsiniz? Nasıl mükemmel bir C ++ tasarım C # aptal görünmüyor?

Özellikle, başa çıkmak için iyi bir C # -ish yolu bulamıyorum (son örnekler):

  • Deterministik temizlik gerektiren kaynakların ömrünü kontrol etmek (dosyalar gibi). Bu usingelinde olması kolaydır, ancak kaynağın mülkiyeti aktarılırken [... iş parçacıkları arasında] nasıl düzgün kullanılır? C ++ 'da basitçe paylaşılan işaretçiler kullanmak ve doğru zamanda' çöp toplama 'ilgilenmek istiyorum.
  • Belirli jenerikler için geçersiz kılan işlevlerle sürekli mücadele (C ++ 'ta kısmi şablon uzmanlığı gibi şeyleri seviyorum). Sadece C # herhangi bir genel programlama yapmak için herhangi bir girişimi terk mi? Belki jenerikler amaç için sınırlıdır ve belirli bir problem alanı dışında bunları kullanmak C # değil mi?
  • Makro benzeri işlevsellik. Genel olarak kötü bir fikir olsa da, bazı sorun alanları için başka bir çözüm yoktur (örneğin, yalnızca hata ayıklama sürümlerine gitmesi gereken günlüklerde olduğu gibi bir ifadenin koşullu değerlendirmesi). Onlara sahip if (condition) {...}olmamak daha fazla kazan plakası koymak zorunda olduğum anlamına geliyor ve hala yan etkileri tetiklemek açısından eşit değil.

# 1 benim için zor, cevabı kendim bilmek istiyorum. # 2 - Basit bir C ++ kod örneği verebilir ve neden buna ihtiyacınız olduğunu açıklayabilir misiniz? # 3 için - C#başka kod oluşturmak için kullanın . Bir okuyabilir CSVveya XMLgirdi olarak ya da ne var dosya ve bir oluşturmak C#veya bir SQLdosyayı. Bu fonksiyonel makroları kullanmaktan daha güçlü olabilir.
İş

Makro benzeri işlevsellik konusunda hangi belirli şeyleri yapmaya çalışıyorsunuz? Ne buldum genellikle C ++ bir makro ile yapardı ne işlemek için bir C # dil yapısı var. Ayrıca C # önişlemci yönergelerine de göz
atın

C ++ makroları ile yapılan birçok şey C # yansıması ile yapılabilir.
Sebastian Redl

Birisi "Doğru iş için doğru araç - ihtiyacınız olana göre bir dil seçin" dediğinde evcil hayvanımdan biri. Genel amaçlı dillerdeki büyük programlar için bu işe yaramaz, yararsız bir ifadedir. Bununla birlikte, C ++ 'nın hemen hemen tüm diğer dillerden daha iyi olduğu şey deterministik kaynak yönetimi. Deterministik kaynak yönetimi, programınızın birincil özelliği veya alıştığınız bir şey mi? Önemli bir kullanıcı özelliği değilse, buna aşırı odaklanmış olabilirsiniz.
Ben

Yanıtlar:


16

Deneyimlerime göre, bunu yapacak kitap yok. Forumlar deyimlere ve en iyi uygulamalara yardımcı olur, ancak sonunda zaman ayırmanız gerekecek. C # 'da bir dizi farklı problemi çözerek pratik yapın. Neyin zor / garip olduğuna bakın ve bir dahaki sefere daha az zor ve daha az garip hale getirmeye çalışın. Benim için bu süreç "anladım" dan yaklaşık bir ay önce sürdü.

Odaklanacak en büyük şey bellek yönetimi eksikliğidir. C ++ 'da bu, verdiğiniz her tasarım kararına dayanır, ancak C #' da karşı üretkentir. C # 'da, genellikle nesnelerinizin ölümünü veya diğer durum geçişlerini yoksaymak istersiniz . Bu tür bir ilişki gereksiz kuplaj ekler.

Çoğunlukla, yeni araçları en etkili şekilde kullanmaya odaklanın, eski araçlara bir bağlantının üzerine çıkmayın.

Nasıl mükemmel bir C ++ tasarım C # aptal görünmüyor?

Farklı deyimlerin farklı tasarım yaklaşımlarına doğru ilerlediğini fark ederek. (Çoğu) dil arasında sadece 1: 1 bir şey taşıyamazsınız ve diğer tarafta güzel ve deyimsel bir şey bekleyemezsiniz.

Ancak belirli senaryolara yanıt olarak:

Paylaşılan yönetilmeyen kaynaklar - Bu C ++ için kötü bir fikirdi ve C # için de kötü bir fikirdi. Bir dosyanız varsa açın, kullanın ve kapatın. İş parçacıkları arasında paylaşmak sadece sorun istiyor ve yalnızca bir iş parçacığı gerçekten kullanıyorsa, o iş parçacığını aç / kapat / kapat. Herhangi bir nedenle gerçekten uzun ömürlü bir dosyanız varsa , bunu temsil etmek için bir sınıf oluşturun ve uygulamanın bunu gerektiği gibi yönetmesine izin verin (çıkıştan önce kapatın, Uygulama Kapanış olaylarına yakın bağlayın, vb.)

Kısmi Şablon Uzmanlığı - Şansınız yok, gerçi siz C # daha fazla kullandığımda Y ++ 'ye düşmek için kullandığım şablon kullanımını daha fazla buluyorum. T her zaman bir int olduğunda neden genel bir şey yapalım ? Diğer senaryolar en iyi C # 'da arayüzlerin liberal uygulaması ile çözülür. Bir arabirim veya delege türü, değiştirmek istediğinizi güçlü bir şekilde yazabiliyorsa jeneriklere ihtiyacınız yoktur.

Önişlemci koşulları - C # vardır #if, çıldırmak. Daha da iyisi, bir derleyici sembolü tanımlanmamışsa bu işlevi koddan düşürecek koşullu özelliklere sahiptir .


Kaynak yönetimi ile ilgili olarak, C # 'da yönetici sınıflarına (statik veya dinamik olabilir) sahip olmak oldukça yaygındır.
rwong

Paylaşılan kaynaklarla ilgili olarak: C # 'da yönetilenler kolaydır (eğer yarış koşulları ilgisizse), yönetilmeyenler çok acayiptir.
Deduplicator

@rwong - ortak, ancak yönetici sınıfları hala kaçınılması gereken bir anti-desen.
Telastyn

Bellek yönetimi ile ilgili olarak, C # 'a geçmenin özellikle başlangıçta bunu daha zor hale getirdiğini gördüm. Bence C ++ 'dan daha farkında olmalısınız. C ++ 'da, her nesnenin ne zaman ve nerede tahsis edildiğini, yeniden konumlandırıldığını ve referans verildiğini tam olarak bilirsiniz. C # 'da, tüm bunlar sizden gizlidir. Birçok kez C # 'da bir nesneye referans alındığını ve bellek sızmaya başladığını bile fark etmiyorsunuz. Genellikle C ++ 'da anlaşılması önemsiz olan C #' daki bellek sızıntılarını takip ederek eğlenin. Tabii ki, deneyim ile bu C # nüanslarını öğrenirsiniz, böylece daha az sorun haline gelirler.
Dunk

4

Gördüğüm kadarıyla, deterministik Yaşam Boyu Yönetimine izin veren tek şey IDisposable, fark ettiğiniz gibi, böyle bir kaynağın Sahipliğini Aktarmanız gerektiğinde bozulan (dil veya tip sistem desteği yok).

Sizinle aynı teknede olmak - C ++ geliştiricisi C # atm yapıyor. - C # türleri / imzalarındaki Sahiplik Aktarımı'nı (dosyalar, db-bağlantıları, ...) modellemek için destek eksikliği bana çok sinir bozucu geldi, ancak "sadece" güvenmek için oldukça iyi çalıştığını itiraf etmeliyim konvansiyon ve API belgelerinde bu hakkı elde etmek için.

  • Kullanım IDisposabledeterministik temiz kadar yapmak IFF gerekli.
  • A'nın sahipliğini aktarmanız gerektiğinde IDisposable, ilgili API'nın bu konuda açık olduğundan ve usingbu durumda kullanmamaya dikkat edin , çünkü usingtabiri caizse "iptal edilemez".

Evet, modern C ++ (hareket hareketleri, vb.) İle tip sisteminde ifade edebileceğiniz kadar zarif değil, ama işi hallediyor ve (şaşırtıcı bir şekilde) C # topluluğu bir bütün olarak görünmüyor çok fazla bakım, AFAICT.


2

İki özel C ++ özelliğinden bahsettiniz. RAII'yi tartışacağım:

C # toplanan çöp olduğu için genellikle uygulanamaz. En yakın C # yapısı muhtemelen IDisposableaşağıdaki gibi kullanılan arabirimdir:

using (FileStream fs = File.OpenRead(@"c:\path\filename.txt"))
{
   //Do stuff with the file.
} //File will be closed here.

IDisposableYönetilen kaynaklar için gerekli olmadığından, sık sık uygulamayı beklememelisiniz (ve bunu yapmak biraz ilerlemiştir). Bununla birlikte, herhangi bir uygulama için usingyapıyı kullanmalısınız (veya Disposemümkünse kendinizi çağırmalısınız using) IDisposable. Bunu karıştırsanız bile, iyi tasarlanmış C # sınıfları bunu sizin için halletmeye çalışacaktır (finalizör kullanarak). Bununla birlikte, çöp toplanmış bir dilde RAII modeli tam olarak çalışmaz (toplama deterministik olmadığından), bu nedenle kaynak temizliğiniz ertelenir (bazen felaket olarak).


RAII benim en büyük sorunum. Diyelim ki bir iş parçacığı tarafından oluşturulmuş ve başka bir iş parçasına verilmiş bir kaynağım var. Bu ipliğin artık ihtiyaç duymadığı bir zamanda atıldığından nasıl emin olabilirim? Tabii ki Dispose diyebilirim ama sonra C ++ 'da paylaşılan işaretçilerden daha çirkin görünüyor. İçinde RAII diye bir şey yok.
Andrew

@Andrew Açıkladığınız şey, sahiplik aktarımı anlamına geliyor gibi görünüyor, bu yüzden şimdi ikinci iş parçacığı Disposing()dosyadan sorumludur ve muhtemelen kullanmalıdır using.
svick

@Andrew - Genel olarak bunu yapmamalısınız. Bunun yerine iş parçacığına dosyayı nasıl alacağınızı ve kullanım ömrüne sahip olmasına izin vermelisiniz.
Telastyn

1
@Telastyn Bu, yönetilen bir kaynak üzerinde sahiplik vermek C # C ile karşılaştırıldığında daha büyük bir sorun olduğu anlamına mı geliyor? Oldukça şaşırtıcı.
Andrew

6
@Andrew: Özetleyeyim: C ++ 'da, tüm kaynakların düzgün, yarı otomatik yönetimini elde edersiniz. C # 'da, belleğin tamamen otomatik yönetimini ve diğer her şeyin tamamen manuel yönetimini elde edersiniz. Bunu değiştirmek için gerçek bir şey yok, bu yüzden tek gerçek sorunuz "bunu nasıl düzeltebilirim?" Değil, sadece "buna nasıl alışırım?"
Jerry Coffin

2

C ++ geliştiricileri korkunç C # kodu yazmak gördük gibi, bu çok iyi bir soru.

C # "güzel sözdizimi ile C ++" olarak düşünmemek en iyisidir. Düşüncelerinizde farklı yaklaşımlar gerektiren farklı dillerdir. C ++ sizi CPU ve belleğin ne yapacağını sürekli düşünmeye zorlar. C # böyle değil. C # özellikle CPU ve bellek hakkında düşünmemeniz ve bunun yerine yazdığınız iş alanı hakkında düşünmeniz için tasarlanmıştır .

Gördüğüm bunun bir örneği, C ++ geliştiricilerinin döngüler için kullanmak isteyecekleri, çünkü foreach'den daha hızlı. Bu C # 'da genellikle kötü bir fikirdir, çünkü yinelenen koleksiyonun olası türlerini (ve dolayısıyla kodun yeniden kullanılabilirliği ve esnekliğini) kısıtlar.

Ben C ++ C # yeniden ayarlamak için en iyi yolu denemek ve farklı bir bakış açısıyla kodlama yaklaşım olduğunu düşünüyorum. İlk başta bu zor olacak, çünkü yıllar boyunca yazdığınız kodu filtrelemek için beyninizdeki "CPU ve bellek ne yapıyor" iş parçacığını kullanmaya alışık olacaksınız. Ancak C # 'da, bunun yerine iş etki alanındaki nesneler arasındaki ilişkileri düşünmelisiniz. "Bilgisayar ne yapıyor" yerine "ne yapmak istiyorum".

Nesneler listesindeki her şeye bir şey yapmak istiyorsanız, listeye for döngüsü yazmak yerine, bir yöntem alıp döngü IEnumerable<MyObject>kullanın foreach.

Özel örnekleriniz:

Deterministik temizlik gerektiren kaynakların ömrünü kontrol etmek (dosyalar gibi). El ele sahip olmak kolaydır, ancak kaynağın mülkiyeti aktarıldığında [... iş parçacıkları arasında] nasıl düzgün kullanılır? C ++ 'da basitçe paylaşılan işaretçiler kullanmak ve doğru zamanda' çöp toplama 'ilgilenmek istiyorum.

Bunu asla C # 'da yapmamalısınız (özellikle evreler arasında). Bir dosyaya bir şey yapmanız gerekiyorsa, aynı anda tek bir yerde yapın. Sınıflarınız arasında geçirilen yönetilmeyen kaynağı yöneten ancak iş parçacıkları arasında bir Dosya iletmeyi denemeyen ve ayrı sınıfların yazma / okuma / kapatma / açma olan bir sarıcı sınıf yazmak iyi (ve gerçekten iyi bir uygulama). Yönetilmeyen kaynakların sahipliğini paylaşmayın. DisposeTemizleme ile ilgilenmek için deseni kullanın .

Belirli jenerikler için geçersiz kılan işlevlerle sürekli mücadele (C ++ 'ta kısmi şablon uzmanlığı gibi şeyleri seviyorum). Sadece C # herhangi bir genel programlama yapmak için herhangi bir girişimi terk mi? Belki jenerikler amaç için sınırlıdır ve belirli bir problem alanı dışında bunları kullanmak C # değil mi?

C #'daki jenerikler jenerik olacak şekilde tasarlanmıştır. Jenerik uzmanlıkları türetilmiş sınıflar tarafından ele alınmalıdır. List<T>Bir List<int>ya da bir ise neden farklı davranmalı List<string>? Üzerindeki List<T>tüm işlemler herhangi birine uygulanacak şekilde geneldir List<T>. A .Addyönteminin davranışını değiştirmek istiyorsanız List<string>, türetilmiş bir sınıf MySpecializedStringCollection : List<string>veya MySpecializedStringCollection : IList<string>jenerik dahili olarak kullanan ancak işleri farklı bir şekilde yapan bir türetilmiş sınıf oluşturun . Bu, Liskov İkame İlkesini ihlal etmekten ve sınıfınızı kullanan diğerlerini kraliyetle sarsmaktan kaçınmanıza yardımcı olur.

Makro benzeri işlevsellik. Genel olarak kötü bir fikir olsa da, bazı sorun alanları için başka bir çözüm yoktur (örneğin, yalnızca hata ayıklama sürümlerine gitmesi gereken günlüklerde olduğu gibi bir ifadenin koşullu değerlendirmesi). Onlara sahip olmamak eğer (koşul) {...} kazan plakasını daha fazla koymam gerektiği anlamına gelir ve hala yan etkileri tetiklemek açısından eşit değildir.

Diğerlerinin söylediği gibi, bunu yapmak için önişlemci komutlarını kullanabilirsiniz. Nitelikler daha da iyidir. Genel olarak öznitelikler, bir sınıf için "temel" işlev olmayan şeyleri işlemenin en iyi yoludur.

Kısacası, C # yazarken, CPU ve bellek hakkında değil, tamamen iş alanı hakkında düşünmeniz gerektiğini unutmayın. Gerekirse daha sonra optimize edebilirsiniz , ancak kodunuz eşlemeye çalıştığınız işletme ilkeleri arasındaki ilişkileri yansıtmalıdır.


3
WRT. kaynak aktarımı: "bunu asla yapmamalısınız" IMHO'yu kesmez. Çoğu zaman, çok iyi bir tasarım, bir IDisposable( ham kaynak değil ) bir sınıftan diğerine aktarılmasını önerir : Bunun , aktarılan akış için mükemmel bir anlam ifade ettiği StreamWriter API'sine bakın Dispose(). Ve C # dilinde delik yatıyor - bunu tip sisteminde ifade edemezsiniz.
Martin Ba

2
"Gördüğüm bunun bir örneği, C ++ geliştiricilerinin döngüler için kullanmak isteyecekleri, çünkü foreach'ten daha hızlılar." Bu C ++ 'da da kötü bir fikir. Sıfır maliyetli soyutlama, C ++ 'ın büyük gücüdür ve foreach veya diğer algoritmalar çoğu durumda döngü için elle yazılmışdan daha yavaş olmamalıdır.
Sebastian Redl

1

Benim için en farklı olan şey, herşeyi yeni yapma eğilimiydi . Bir nesne istiyorsanız, bunu bir rutine aktarmayı ve temel verileri paylaşmayı denemeyi unutun, C # deseninin sadece kendinize yeni bir nesne yaratmak olduğu anlaşılıyor. Bu, genel olarak, C # yolundan çok daha fazla gibi görünüyor. İlk başta bu desen çok verimsiz görünüyordu, ama sanırım C # 'da çoğu nesne oluşturmak hızlı bir işlemdir.

Bununla birlikte, beni sadece sinirlendiren statik sınıflar da var, çünkü her zaman sadece onu kullanmanız gerektiğini bulmak için bir tane oluşturmaya çalışıyorsunuz. Hangisinin kullanılacağını bilmek o kadar kolay değil.

Bir C # programında artık ihtiyacım olmadığında görmezden gelmek için oldukça mutluyum, ama sadece bu nesnenin ne içerdiğini hatırlayın - genellikle sadece bazı 'alışılmadık' veriler varsa, sadece bellekten oluşan nesneler sadece kendiliğinden uzaklaşın, başkalarının atılması gerekir ya da onları nasıl yok edeceğinize bakmanız gerekir - manuel olarak veya değilse bir blok kullanarak.

Çok hızlı bir şekilde alırsınız, C # VB'den neredeyse hiç farklı değildir, bu yüzden aynı RAD aracı olarak tedavi edebilirsiniz (CB'yi VB.NET gibi düşünmeye yardımcı olursa, sözdizimi sadece biraz ve VB'yi kullandığım eski günlerim beni RAD gelişimi için doğru zihniyete soktu). Tek zor bit, hangi .net kütüphanelerinin hangi bitini kullanmanız gerektiğini belirlemektir. Google kesinlikle burada senin arkadaşın!


1
Altta yatan verileri paylaşmak için bir nesneyi rutine aktarmak, en azından sözdizimi perspektifinden C # 'da mükemmel şekilde iyidir.
Brian
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.