Dart Dili: Java ve C# Yeterince Keskin Olmadığında

Yayınlanan: 2022-03-11

2013'te Dart'ın resmi 1.0 sürümü, çoğu Google teklifinde olduğu gibi, biraz baskı aldı, ancak herkes Dart dili ile iş açısından kritik uygulamalar oluşturmak için Google'ın dahili ekipleri kadar istekli değildi. Beş yıl sonra Dart 2'nin iyi düşünülmüş yeniden inşası ile Google, dile olan bağlılığını kanıtlamış görünüyordu. Gerçekten de bugün geliştiriciler, özellikle de Java ve C# gazileri arasında ilgi görmeye devam ediyor.

Dart programlama dili birkaç nedenden dolayı önemlidir:

  • Her iki dünyanın da en iyisine sahiptir: Aynı anda derlenmiş, tür açısından güvenli bir dil (C# ve Java gibi) ve bir komut dosyası dilidir (Python ve JavaScript gibi).
  • Web ön ucu olarak kullanılmak üzere JavaScript'e aktarılır.
  • Her şeyde çalışır ve yerel mobil uygulamalarda derlenir, böylece onu neredeyse her şey için kullanabilirsiniz.
  • Dart, sözdiziminde C# ve Java'ya benzer, bu nedenle öğrenmesi hızlıdır.

Daha büyük kurumsal sistemlerin C# veya Java dünyasından olan bizler, tür güvenliğinin, derleme zamanı hatalarının ve linterlerin neden önemli olduğunu zaten biliyoruz. Birçoğumuz, alıştığımız tüm yapıyı, hızı, doğruluğu ve hata ayıklanabilirliğini kaybetme korkusuyla “yazılı” bir dili benimsemekte tereddüt ediyoruz.

Ancak Dart geliştirme ile bunların hiçbirinden vazgeçmek zorunda değiliz. Aynı dilde bir mobil uygulama, web istemcisi ve arka uç yazabilir ve Java ve C# hakkında hâlâ sevdiğimiz her şeyi elde edebiliriz!

Bu amaçla, sonunda bir Dart dili PDF'sinde özetleyeceğimiz, bir C# veya Java geliştiricisi için yeni olacak bazı önemli Dart dili örneklerini inceleyelim.

Not: Bu makale yalnızca Dart 2.x'i kapsar. Sürüm 1.x "tamamen hazır" değildi - özellikle, tür sistemi gerekli (C# veya Java gibi) yerine tavsiye niteliğindeydi (TypeScript gibi).

1. Kod Organizasyonu

İlk olarak, en önemli farklılıklardan birine gireceğiz: kod dosyalarının nasıl düzenlendiği ve referans verildiği.

Kaynak Dosyaları, Kapsam, Ad Alanları ve İçe Aktarmalar

C#'ta, bir sınıf koleksiyonu bir derlemeye derlenir. Her sınıfın bir ad alanı vardır ve genellikle ad alanları, dosya sistemindeki kaynak kodun organizasyonunu yansıtır - ancak sonuçta, derleme kaynak kod dosyasının konumu hakkında hiçbir bilgiyi tutmaz.

Java'da kaynak dosyalar bir paketin parçasıdır ve ad alanları genellikle dosya sistemi konumuna uygundur, ancak sonuçta bir paket yalnızca bir sınıflar topluluğudur.

Dolayısıyla her iki dilin de kaynak kodunu dosya sisteminden biraz bağımsız tutmanın bir yolu vardır.

Buna karşılık, Dart dilinde, her kaynak dosya, diğer kaynak dosyalarınız ve üçüncü taraf paketleriniz de dahil olmak üzere atıfta bulunduğu her şeyi içe aktarmalıdır. Aynı şekilde ad alanı yoktur ve dosyalara genellikle dosya sistemi konumları aracılığıyla başvurursunuz. Değişkenler ve işlevler yalnızca sınıflar değil, üst düzey olabilir. Bu yönlerden Dart daha çok senaryoya benzer.

Bu nedenle, düşüncenizi "bir sınıflar topluluğu"ndan daha çok "bir dizi dahil edilmiş kod dosyası" gibi bir şeye değiştirmeniz gerekecektir.

Dart, hem paket organizasyonunu hem de paketler olmadan geçici organizasyonu destekler. Dahil edilen dosyaların sırasını göstermek için paket içermeyen bir örnekle başlayalım:

 // file1.dart int alice = 1; // top level variable int barry() => 2; // top level function var student = Charlie(); // top level variable; Charlie is declared below but that's OK class Charlie { ... } // top level class // alice = 2; // top level statement not allowed // file2.dart import 'file1.dart'; // causes all of file1 to be in scope main() { print(alice); // 1 }

Bir kaynak dosyada atıfta bulunduğunuz her şey, "proje" düzeyi ve kapsama diğer kaynak öğelerini dahil etmenin başka bir yolu olmadığından, o dosya içinde bildirilmeli veya içe aktarılmalıdır.

Dart'ta ad alanlarının tek kullanımı, içe aktarmalara bir ad vermektir ve bu, o dosyadan içe aktarılan koda nasıl başvurduğunuzu etkiler.

 // file2.dart import 'file1.dart' as wonderland; main() { print(wonderland.alice); // 1 }

paketler

Yukarıdaki örnekler, kodu paketler olmadan düzenlemektedir. Paketleri kullanmak için kod daha spesifik bir şekilde düzenlenir. apples adlı bir paket için örnek bir paket düzeni:

  • apples/
    • pubspec.yaml — paket adını, bağımlılıkları ve diğer bazı şeyleri tanımlar
    • lib/
      • apples.dart —ithalat ve ihracat; bu, paketin herhangi bir tüketicisi tarafından içe aktarılan dosyadır
      • src/
        • seeds.dart — diğer tüm kodlar burada
    • bin/
      • runapples.dart giriş noktası olan ana işlevi içerir (bu çalıştırılabilir bir paketse veya çalıştırılabilir araçlar içeriyorsa)

Ardından, tek tek dosyalar yerine tüm paketleri içe aktarabilirsiniz:

 import 'package:apples';

Önemsiz uygulamalar her zaman paketler halinde düzenlenmelidir. Bu, başvuran her dosyada dosya sistemi yollarını tekrarlama zorunluluğunu ortadan kaldırır; artı, daha hızlı koşarlar. Ayrıca, paketinizi pub.dev'de paylaşmayı da kolaylaştırır, burada diğer geliştiriciler paketi kendi kullanımları için çok kolay bir şekilde alabilirler. Uygulamanız tarafından kullanılan paketler, kaynak kodun dosya sisteminize kopyalanmasına neden olur, böylece bu paketlerde istediğiniz kadar hata ayıklayabilirsiniz.

2. Veri Türleri

Dart'ın tür sisteminde, boş değerler, sayısal türler, koleksiyonlar ve dinamik türlerle ilgili bilinmesi gereken büyük farklılıklar vardır.

Her Yerde Null

C# veya Java'dan geldiğimizde, referans veya nesne türlerinden farklı olarak ilkel veya değer türlerine alışkınız. Değer türleri, pratikte yığında veya kayıtlarda tahsis edilir ve değerin kopyaları fonksiyon parametreleri olarak gönderilir. Bunun yerine referans türleri öbek üzerinde tahsis edilir ve yalnızca nesneye işaretçiler işlev parametreleri olarak gönderilir. Değer türleri her zaman belleği işgal ettiğinden, değer türü bir değişken boş olamaz ve tüm değer türü üyelerin başlatılmış değerlere sahip olması gerekir.

Dart bu ayrımı ortadan kaldırır çünkü her şey bir nesnedir; tüm türler nihai olarak Object türünden türetilir. Yani, bu yasal:

 int i = null;

Aslında, tüm ilkeller örtük olarak null olarak başlatılır. Bu, C# veya Java'da alıştığınız gibi tamsayıların varsayılan değerlerinin sıfır olduğunu varsayamayacağınız anlamına gelir ve boş denetimler eklemeniz gerekebilir.

İlginç bir şekilde, Null bile bir türdür ve null kelimesi bir Null örneğini ifade eder:

 print(null.runtimeType); // prints Null

Sayısal Türler Kadar Çok Değil

İşaretli ve işaretsiz tatlara sahip 8 ila 64 bit arasındaki bilinen tamsayı türlerinin aksine, Dart'ın ana tamsayı türü yalnızca int , 64 bitlik bir değerdir. (Ayrıca çok büyük sayılar için BigInt vardır.)

Dil sözdiziminin bir parçası olarak bayt dizisi olmadığından, ikili dosya içerikleri tamsayı listeleri olarak işlenebilir, yani List<Int> .

Bunun çok verimsiz olduğunu düşünüyorsanız, tasarımcılar bunu zaten düşünmüşlerdir. Uygulamada, çalışma zamanında kullanılan gerçek tamsayı değerine bağlı olarak farklı dahili temsiller vardır. Çalışma zamanı, bunu en iyi duruma getirebiliyorsa ve kutusuz modda bir CPU kaydı kullanabiliyorsa, int nesnesi için yığın bellek ayırmaz. Ayrıca, UInt8List kitaplığı byte_data ve diğer bazı optimize edilmiş temsiller sunar.

Koleksiyonlar

Koleksiyonlar ve jenerikler, alıştığımız şeylere çok benziyor. Unutulmaması gereken en önemli şey, sabit boyutlu dizilerin olmamasıdır: Bir diziyi kullanacağınız her yerde List veri türünü kullanın.

Ayrıca, koleksiyon türlerinin üçünü başlatmak için sözdizimsel destek vardır:

 final a = [1, 2, 3]; // inferred type is List<int>, an array-like ordered collection final b = {1, 2, 3}; // inferred type is Set<int>, an unordered collection final c = {'a': 1, 'b': 2}; // inferred type is Map<string, int>, an unordered collection of name-value pairs

Bu nedenle, bir Java dizisi, ArrayList veya Vector kullanacağınız Dart List kullanın; veya bir C# dizisi veya List . Java/C# HashSet kullanacağınız yerde Set kullanın. Java HashMap veya C# Dictionary kullanacağınız yerde Map kullanın.

3. Dinamik ve Statik Yazma

JavaScript, Ruby ve Python gibi dinamik dillerde, üyeler olmasa bile üyelere başvurabilirsiniz. İşte bir JavaScript örneği:

 var person = {}; // create an empty object person.name = 'alice'; // add a member to the object if (person.age < 21) { // refer to a property that is not in the object // ... }

Bunu çalıştırırsanız, person.age undefined olur, ancak yine de çalışır.

Benzer şekilde, JavaScript'te bir değişkenin türünü değiştirebilirsiniz:

 var a = 1; // a is a number a = 'one'; // a is now a string

Buna karşılık, Java'da, derleyicinin türü bilmesi gerektiğinden yukarıdaki gibi kod yazamazsınız ve var anahtar sözcüğünü kullansanız bile tüm işlemlerin yasal olup olmadığını kontrol eder:

 var b = 1; // a is an int // b = "one"; // not allowed in Java

Java yalnızca statik türlerle kodlamanıza izin verir. (Bazı dinamik davranışlar yapmak için iç gözlemi kullanabilirsiniz, ancak bu doğrudan sözdiziminin bir parçası değildir.) JavaScript ve diğer bazı tamamen dinamik diller yalnızca dinamik türlerle kodlamanıza izin verir.

Dart dili her ikisine de izin verir:

 // dart dynamic a = 1; // a is an int - dynamic typing a = 'one'; // a is now a string a.foo(); // we can call a function on a dynamic object, to be resolved at run time var b = 1; // b is an int - static typing // b = 'one'; // not allowed in Dart

Dart, tüm tür mantığının çalışma zamanında işlenmesine neden olan sözde tür dynamic sahiptir. a.foo() yu çağırma girişimi statik çözümleyiciyi rahatsız etmeyecek ve kod çalışacaktır, ancak böyle bir yöntem olmadığı için çalışma zamanında başarısız olacaktır.

C# başlangıçta Java gibiydi ve daha sonra dinamik destek eklendi, bu nedenle Dart ve C# bu konuda hemen hemen aynıdır.

4. Fonksiyonlar

İşlev Bildirimi Sözdizimi

Dart'taki işlev sözdizimi, C# veya Java'dan biraz daha hafif ve daha eğlencelidir. Sözdizimi bunlardan herhangi biridir:

 // functions as declarations return-type name (parameters) {body} return-type name (parameters) => expression; // function expressions (assignable to variables, etc.) (parameters) {body} (parameters) => expression

Örneğin:

 void printFoo() { print('foo'); }; String embellish(String s) => s.toUpperCase() + '!!'; var printFoo = () { print('foo'); }; var embellish = (String s) => s.toUpperCase() + '!!';

Parametre Geçişi

int ve String gibi temel öğeler de dahil olmak üzere her şey bir nesne olduğundan, parametre geçişi kafa karıştırıcı olabilir. C#'daki gibi bir ref parametresi geçmezken, her şey referansa göre iletilir ve fonksiyon, arayanın referansını değiştiremez. Nesneler, işlevlere aktarıldığında klonlanmadığından, bir işlev, nesnenin özelliklerini değiştirebilir. Bununla birlikte, int ve String gibi ilkeller için bu ayrım, bu türler değişmez olduğundan, etkili bir şekilde tartışmalıdır.

 var id = 1; var name = 'alice'; var client = Client(); void foo(int id, String name, Client client) { id = 2; // local var points to different int instance name = 'bob'; // local var points to different String instance client.State = 'AK'; // property of caller's object is changed } foo(id, name, client); // id == 1, name == 'alice', client.State == 'AK'

Opsiyonel Parametreler

C# veya Java dünyalarındaysanız, muhtemelen aşağıdaki gibi kafa karıştırıcı şekilde aşırı yüklenmiş yöntemlerle ilgili durumlara lanet etmişsinizdir:

 // java void foo(string arg1) {...} void foo(int arg1, string arg2) {...} void foo(string arg1, Client arg2) {...} // call site: foo(clientId, input3); // confusing! too easy to misread which overload it is calling

Veya C# isteğe bağlı parametrelerle, başka bir tür karışıklık vardır:

 // c# void Foo(string arg1, int arg2 = 0) {...} void Foo(string arg1, int arg3 = 0, int arg2 = 0) {...} // call site: Foo("alice", 7); // legal but confusing! too easy to misread which overload it is calling and which parameter binds to argument 7 Foo("alice", arg2: 9); // better

C#, çağrı sitelerinde isteğe bağlı bağımsız değişkenlerin adlandırılmasını gerektirmez, bu nedenle isteğe bağlı parametrelerle yeniden düzenleme yöntemleri tehlikeli olabilir. Yeniden düzenlemeden sonra bazı çağrı siteleri yasal olursa, derleyici onları yakalamaz.

Dart'ın daha güvenli ve çok esnek bir yolu var. Her şeyden önce, aşırı yüklenmiş yöntemler desteklenmez . Bunun yerine, isteğe bağlı parametreleri işlemenin iki yolu vardır:

 // positional optional parameters void foo(string arg1, [int arg2 = 0, int arg3 = 0]) {...} // call site for positional optional parameters foo('alice'); // legal foo('alice', 12); // legal foo('alice', 12, 13); // legal // named optional parameters void bar(string arg1, {int arg2 = 0, int arg3 = 0}) {...} bar('alice'); // legal bar('alice', arg3: 12); // legal bar('alice', arg3: 12, arg2: 13); // legal; sequence can vary and names are required

Aynı işlev bildiriminde her iki stili de kullanamazsınız.

async Anahtar Kelime Konumu

C#, async anahtar sözcüğü için kafa karıştırıcı bir konuma sahiptir:

 Task<int> Foo() {...} async Task<int> Foo() {...}

Bu, işlev imzasının eşzamansız olduğunu, ancak gerçekte yalnızca işlev uygulamasının eşzamansız olduğunu gösterir. Yukarıdaki imzalardan herhangi biri bu arayüzün geçerli bir uygulaması olacaktır:

 interface ICanFoo { Task<int> Foo(); }

Dart dilinde, async daha mantıklı bir yerdedir ve uygulamanın eşzamansız olduğunu gösterir:

 Future<int> foo() async {...}

Kapsam ve Kapanışlar

C# ve Java gibi, Dart da sözcüksel olarak kapsamlıdır. Bu, bir blokta bildirilen bir değişkenin, bloğun sonunda kapsam dışına çıktığı anlamına gelir. Yani Dart kapanışları aynı şekilde halleder.

Özellik sözdizimi

Java, get/set model özelliğini popüler hale getirdi, ancak dilin bunun için herhangi bir özel sözdizimi yok:

 // java private String clientName; public String getClientName() { return clientName; } public void setClientName(String value}{ clientName = value; }

C# bunun için sözdizimine sahiptir:

 // c# private string clientName; public string ClientName { get { return clientName; } set { clientName = value; } }

Dart, biraz farklı bir sözdizimi destekleyici özelliklere sahiptir:

 // dart string _clientName; string get ClientName => _clientName; string set ClientName(string s) { _clientName = s; }

5. Yapıcılar

Dart yapıcıları, C# veya Java'dan biraz daha fazla esnekliğe sahiptir. Güzel bir özellik, aynı sınıftaki farklı kurucuları adlandırma yeteneğidir:

 class Point { Point(double x, double y) {...} // default ctor Point.asPolar(double angle, double r) {...} // named ctor }

Yalnızca sınıf adıyla varsayılan bir kurucu çağırabilirsiniz: var c = Client();

Oluşturucu gövdesi çağrılmadan önce örnek üyeleri başlatmak için iki tür kısayol vardır:

 class Client { String _code; String _name; Client(String this._name) // "this" shorthand for assigning parameter to instance member : _code = _name.toUpper() { // special out-of-body place for initializing // body } }

Yapıcılar, üst sınıf kurucularını çalıştırabilir ve aynı sınıftaki diğer kuruculara yeniden yönlendirebilir:

 Foo.constructor1(int x) : this(x); // redirect to the default ctor in same class; no body allowed Foo.constructor2(int x) : super.plain(x) {...} // call base class named ctor, then run this body Foo.constructor3(int x) : _b = x + 1 : super.plain(x) {...} // initialize _b, then call base class ctor, then run this body

Java ve C#'da aynı sınıftaki diğer yapıcıları çağıran yapıcılar, her ikisinin de uygulamaları olduğunda kafa karıştırıcı olabilir. Dart'ta, yeniden yönlendiren yapıcıların bir gövdeye sahip olamayacağı sınırlaması, programcıyı yapıcı katmanlarını daha net hale getirmeye zorlar.

Bir işlevin yapıcı gibi kullanılmasına izin veren bir factory anahtar sözcüğü de vardır, ancak uygulama yalnızca normal bir işlevdir. Önbelleğe alınmış bir örneği veya türetilmiş bir türün örneğini döndürmek için kullanabilirsiniz:

 class Shape { factory Shape(int nsides) { if (nsides == 4) return Square(); // etc. } } var s = Shape(4);

6. Değiştiriciler

Java ve C#'da private , protected ve public gibi erişim değiştiricilerine sahibiz. Dart'ta bu büyük ölçüde basitleştirilmiştir: Üye adı bir alt çizgi ile başlıyorsa, paketin içinde (diğer sınıflar dahil) her yerde görünür ve dışarıdan arayanlardan gizlenir; yoksa her yerden görünür. Görünürlüğü belirtmek için private gibi anahtar kelimeler yoktur.

Başka bir değiştirici türü değiştirilebilirliği kontrol eder: final ve const anahtar sözcükleri bu amaç içindir, ancak farklı anlamlara gelirler:

 var a = 1; // a is variable, and can be reassigned later final b = a + 1; // b is a runtime constant, and can only be assigned once const c = 3; // c is a compile-time constant // const d = a + 2; // not allowed because a+2 cannot be resolved at compile time

7. Sınıf Hiyerarşisi

Dart dili arayüzleri, sınıfları ve bir tür çoklu kalıtımı destekler. Ancak, interface anahtar sözcüğü yoktur; bunun yerine, tüm sınıflar aynı zamanda arabirimlerdir, bu nedenle abstract bir sınıf tanımlayabilir ve ardından onu uygulayabilirsiniz:

 abstract class HasDesk { bool isDeskMessy(); // no implementation here } class Employee implements HasDesk { bool isDeskMessy() { ...} // must be implemented here }

Genişletme anahtar sözcüğü kullanılarak bir ana soy with extends sözcüğü kullanılarak diğer sınıflar ile çoklu kalıtım yapılır:

 class Employee extends Person with Salaried implements HasDesk {...}

Bu bildirimde, Employee sınıfı, Person ve Salaried türetilmiştir, ancak Person ana üst sınıftır ve Salaried , karışımdır (ikincil üst sınıf).

8. Operatörler

Alışkın olmadığımız bazı eğlenceli ve kullanışlı Dart operatörleri var.

Basamaklar , herhangi bir şey üzerinde bir zincirleme deseni kullanmanıza izin verir:

 emp ..name = 'Alice' ..supervisor = 'Zoltron' ..hire();

Yayılma operatörü, bir koleksiyonun bir başlatıcıdaki öğelerinin bir listesi olarak ele alınmasına izin verir:

 var smallList = [1, 2]; var bigList = [0, ...smallList, 3, 4]; // [0, 1, 2, 3, 4]

9. İplikler

Dart'ın JavaScript'e aktarılmasına izin veren iş parçacığı yoktur. Bunun yerine, hafızayı paylaşamamaları anlamında ayrı süreçlere benzeyen “izoleler” vardır. Çok iş parçacıklı programlama çok hataya açık olduğundan, bu güvenlik Dart'ın avantajlarından biri olarak görülüyor. İzolatlar arasında iletişim kurmak için aralarında veri akışı yapmanız gerekir; alınan nesneler, alıcı izolatın bellek alanına kopyalanır.

Dart Dili ile Geliştirin: Bunu Yapabilirsiniz!

Bir C# veya Java geliştiricisiyseniz, zaten bildikleriniz Dart dilini aşina olacak şekilde tasarlandığı için hızlı bir şekilde öğrenmenize yardımcı olacaktır. Bu amaçla, özellikle C# ve Java eşdeğerlerinden önemli farklılıklara odaklanarak referansınız için bir Dart kopya sayfası PDF'i hazırladık:

Dart dili hile sayfası PDF

Bu makalede gösterilen farklılıklar, mevcut bilgilerinizle birleştiğinde, Dart'ın ilk veya iki gününde üretken olmanıza yardımcı olacaktır. Mutlu kodlama!

İlgili: Hibrit Güç: Flutter Avantajları ve Faydaları