4. Sınıflar, Yapılar, Arayüzler
4.1. Örnek üzerinden nesne
4.1.1. Genel Bilgiler
Şimdi örnek üzerinden nesne yönelimli programlamayı ele alacağız. Bir nesne, durumunu tanımlayan verileri (bunlara alanlar, öznitelikler vb. denir) ve işlevleri (bunlara yöntemler denir) içeren bir varlıktır. Bir nesne, sınıf olarak adlandırılan bir modele göre oluşturulur:
public class C1{
Type1 p1; // p1 alanı
Type2 p2; // p2 alanı
…
Type3 m3(…){ // m3 yöntemi
…
}
Type4 m4(…){ // m4 yöntemi
…
}
…
}
Önceki C1 sınıfından yola çıkarak, O1, O2,… gibi birçok nesne oluşturulabilir. Hepsi p1, p2,… alanlarına ve m3, m4,… yöntemlerine sahip olacaktır. Ancak pi alanları için farklı değerlere sahip olacaklardır ve böylece her biri kendine özgü bir duruma sahip olacaktır. Eğer o1, C1 türünde bir nesne ise, o1.p1, o1’in p1 özelliğini ve o1.m1, O1 sınıfının yöntemini ifade eder.
İlk nesne modelini ele alalım: Personne sınıfı.
4.1.2. C# projesinin oluşturulması
Önceki örneklerde, bir projede tek bir kaynak dosyası vardı: Program.cs. Bundan sonra, aynı proje içinde birden fazla kaynak dosyası bulundurabileceğiz. Bunun nasıl yapılacağını göstereceğiz.
![]() |
[1]'te yeni bir proje oluşturun. [2]'te Konsol Uygulaması'nı seçin. [3]'te varsayılan değeri değiştirmeyin. [4]'te onaylayın. [5]'te, oluşturulan proje yer alır. Program.cs'in içeriği şöyledir:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
}
}
}
Oluşturulan projeyi kaydedelim:
![]() |
[1]'te kaydetme seçeneği. [2]'te, projenin kaydedileceği klasörü belirtin. [3]'te, projeye bir ad verin. [5]'te bir çözüm oluşturmak istediğinizi belirtin. Çözüm, bir dizi projeden oluşur. [4]'te çözüme bir ad verin. [6]'te kaydetme işlemini onaylayın.
![]() |
[1]'te, kaydedilen projeyi seçin. [2]'te, projeye yeni bir öğe ekleyin.
![]() |
[1]'te, bir sınıf eklemek istediğinizi belirtin. [2]'te, sınıfın adını girin. [3]'te, bilgileri onaylayın. [4]'te, [01] projesine yeni bir kaynak dosyası (Personne.cs) eklenmiştir:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
class Personne {
}
}
Chap2 dosyasındaki her bir kaynak dosyanın ad alanını değiştirin ve gereksiz ad alanlarının içe aktarımını silin:
using System;
namespace Chap2 {
class Personne {
}
}
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
}
}
}
4.1.3. Personne sınıfının tanımı
[Personne.cs] kaynak dosyasındaki Personne sınıfının tanımı şu şekilde olacaktır:
using System;
namespace Chap2 {
public class Personne {
// öznitelikler
private string prenom;
private string nom;
private int age;
// yöntem
public void Initialise(string P, string N, int age) {
this.prenom = P;
this.nom = N;
this.age = age;
}
// yöntem
public void Identifie() {
Console.WriteLine("[{0}, {1}, {2}]", prenom, nom, age);
}
}
}
Burada bir sınıfın, yani bir veri türünün tanımını görüyoruz. Bu türden değişkenler oluşturduğumuzda, bunlara nesneler veya sınıf örnekleri adını vereceğiz. Dolayısıyla bir sınıf, nesnelerin oluşturulduğu bir kalıptır.
Bir sınıfın üyeleri veya alanları veriler (öznitelikler), yöntemler (fonksiyonlar) veya özellikler olabilir. Özellikler, nesnenin özniteliklerinin değerini öğrenmek veya belirlemek için kullanılan özel yöntemlerdir. Bu alanlar, aşağıdaki üç anahtar kelimeden biriyle birlikte kullanılabilir:
Özel (private) bir alana yalnızca sınıfın iç yöntemleri erişebilir | |
Genel (public) bir alana, sınıf içinde tanımlanmış olsun ya da olmasın tüm yöntemler tarafından erişilebilir | |
Korunan (protected) bir alana yalnızca sınıfın veya türetilmiş bir nesnenin (daha sonra miras kavramına bakınız) dahili yöntemleri tarafından erişilebilir. |
Genel olarak, bir sınıfın verileri özel olarak bildirilirken, yöntemleri ve özellikleri genel olarak bildirilir. Bu, bir nesnenin kullanıcısının (programcı)
- nesnenin özel verilerine doğrudan erişemeyeceği
- nesnenin genel yöntemlerini ve özellikle de özel verilerine erişim sağlayan yöntemleri kullanabilir.
C dilinde bir sınıfın bildirim sözdizimi şöyledir:
public class C{
private donnée ou méthode ou propriété privée;
public donnée ou méthode ou propriété publique;
protected donnée ou méthode ou propriété protégée;
}
private, protected ve public özniteliklerinin bildirilme sırası herhangi bir şekilde olabilir.
4.1.4. Initialise yöntemi
Şu şekilde tanımlanan Personne sınıfımıza geri dönelim:
using System;
namespace Chap2 {
public class Personne {
// özellikler
private string prenom;
private string nom;
private int age;
// yöntem
public void Initialise(string p, string n, int age) {
this.prenom = p;
this.nom = n;
this.age = age;
}
// yöntem
public void Identifie() {
Console.WriteLine("[{0}, {1}, {2}]", prenom, nom, age);
}
}
}
Initialise yönteminin işlevi nedir? nom, prenom ve age, Personne sınıfına ait özel veriler olduğundan, şu komutlar:
yazıları geçersizdir. Personne türünde bir nesneyi genel bir yöntem aracılığıyla başlatmamız gerekir. Bu, Initialise yönteminin görevidir. Şöyle yazacağız:
p1.Initialise yazımı geçerlidir, çünkü Initialise genel erişimlidir.
4.1.5. new operatörü
Komut dizisi
hatalıdır.
komutu, p1'i Personne türünde bir nesneye referans olarak tanımlamaktadır. Bu nesne henüz mevcut değildir ve bu nedenle p1 başlatılmamıştır. Bu, sanki şunu yazmış gibi bir durumdur:
burada null anahtar sözcüğüyle, p1 değişkeninin henüz hiçbir nesneye referans vermediğini açıkça belirtmiş oluruz. Daha sonra şunu yazdığımızda
yazdığımızda, p1 tarafından referans verilen nesnenin Initialise yöntemini çağırmış oluruz. Ancak bu nesne henüz mevcut değildir ve derleyici bir hata bildirir. p1'in bir nesneye referans vermesi için şunu yazmak gerekir:
Bu, henüz başlatılmamış bir Personne türü nesne oluşturur: nom ve prenom öznitelikleri, String türündeki nesnelere referanslar olduğundan null değerini alacak, ve age ise 0 değerini alacaktır. Dolayısıyla varsayılan bir başlatma söz konusudur. Artık p1 bir nesneye referans verdiğine göre, bu nesnenin başlatma komutu
geçerlidir.
4.1.6. this anahtar sözcüğü
initialise yönteminin koduna bakalım:
public void Initialise(string p, string n, int age) {
this.prenom = p;
this.nom = n;
this.age = age;
}
this.prenom=p komutu, geçerli nesnenin (this) prenom özniteliğine p değerinin atandığını ifade eder. this anahtar sözcüğü, geçerli nesneyi belirtir: yani, yürütülen yöntemin bulunduğu nesneyi. Bunu nasıl anlarız? Çağıran programda p1 tarafından referans verilen nesnenin nasıl başlatıldığını inceleyelim:
Çağrılan, p1 nesnesinin Initialise yöntemidir. Bu yöntem içinde this nesnesine atıfta bulunulduğunda, aslında p1 nesnesine atıfta bulunulur. Initialise yöntemi şu şekilde de yazılabilirdi:
public void Initialise(string p, string n, int age) {
prenom = p;
nom = n;
this.age = age;
}
Bir nesnenin yöntemi, bu nesnenin A özniteliğine başvurduğunda, this.A yazımı örtük olarak kullanılır. Tanımlayıcı çakışması olduğunda ise bu yazımı açıkça kullanmak gerekir. Aşağıdaki komutda durum böyledir:
this.age=age;
burada age, hem geçerli nesnenin bir özniteliğini hem de yöntem tarafından alınan age parametresini ifade eder. Bu durumda, age özniteliğini this.age olarak belirtmek suretiyle belirsizliği gidermek gerekir.
4.1.7. Bir test programı
İşte kısa bir test programı. Bu program, [Program.cs] kaynak dosyasında yazılmıştır:
using System;
namespace Chap2 {
class P01 {
static void Main() {
Personne p1 = new Personne();
p1.Initialise("Jean", "Dupont", 30);
p1.Identifie();
}
}
}
[01] projesini çalıştırmadan önce, çalıştırılacak kaynak dosyasını belirtmeniz gerekebilir:
![]() |
[01] projesinin özelliklerinde, çalıştırılacak sınıf olarak [1] belirtilir.
Çalıştırma sonucunda elde edilen sonuçlar şunlardır:
4.1.8. Başka bir Initialise yöntemi
Yine Personne sınıfını ele alalım ve ona aşağıdaki yöntemi ekleyelim:
public void Initialise(Personne p) {
prenom = p.prenom;
nom = p.nom;
age = p.age;
}
Artık Initialise adında iki yöntemimiz var: farklı parametreler kabul ettikleri sürece bu durum geçerlidir. Burada da durum böyledir. Parametre artık bir kişiye ait p referansıdır. p kişisinin öznitelikleri, bu durumda geçerli nesneye (this) atanır. Initialise yönteminin, p nesnesinin özniteliklerine doğrudan erişimi olduğu, ancak bu özniteliklerin private türünde olduğu dikkatinizi çekecektir. Bu kural her zaman geçerlidir: C sınıfına ait bir o1 nesnesi, her zaman aynı C sınıfına ait nesnelerin özniteliklerine erişebilir.
İşte yeni Personne sınıfının bir testi:
using System;
namespace Chap2 {
class Program {
static void Main() {
Personne p1 = new Personne();
p1.Initialise("Jean", "Dupont", 30);
p1.Identifie();
Personne p2 = new Personne();
p2.Initialise(p1);
p2.Identifie();
}
}
}
ve sonuçları:
4.1.9. Personne sınıfının oluşturucuları
Oluşturucu, sınıfın adını taşıyan ve nesnenin oluşturulması sırasında çağrılan bir yöntemdir. Genellikle nesneyi başlatmak için kullanılır. Bu yöntem, argümanları kabul edebilir ancak herhangi bir sonuç döndürmez. Prototipinin veya tanımının önünde herhangi bir tür belirtilmez (void bile).
C sınıfının n adet argi argümanı kabul eden bir oluşturucusu varsa, bu sınıfa ait bir nesnenin bildirilmesi ve başlatılması şu şekilde yapılabilir:
veya
Bir C sınıfının bir veya daha fazla yapıcı işlevi varsa, bu sınıftan bir nesne oluşturmak için bu yapıcı işlevlerden birinin kullanılması zorunludur. Bir C sınıfının hiçbir yapıcı yöntemi yoksa, varsayılan olarak parametresiz yapıcı yöntemi olan public C() kullanılır. Bu durumda nesnenin özellikleri varsayılan değerlerle başlatılır. Önceki programlarda şu kod yazıldığında da tam olarak bu durum gerçekleşmiştir:
Personne sınıfımıza iki yapıcı oluşturalım:
using System;
namespace Chap2 {
public class Personne {
// özellikler
private string prenom;
private string nom;
private int age;
// yapıcılar
public Personne(String p, String n, int age) {
Initialise(p, n, age);
}
public Personne(Personne P) {
Initialise(P);
}
// yöntem
public void Initialise(string p, string n, int age) {
...
}
public void Initialise(Personne p) {
...
}
// yöntem
public void Identifie() {
Console.WriteLine("[{0}, {1}, {2}]", prenom, nom, age);
}
}
}
Her iki yapıcı da daha önce incelediğimiz Initialise yöntemlerini çağırmakla yetiniyor. Hatırlatmak gerekirse, bir yapıcıda örneğin Initialise(p) notasyonu bulunduğunda, derleyici bunu this.Initialise(p) olarak çevirir. Dolayısıyla, oluşturucuda Initialise yöntemi, this tarafından referans verilen nesne, yani o anda oluşturulmakta olan nesne üzerinde çalışmak üzere çağrılır.
İşte kısa bir test programı:
using System;
namespace Chap2 {
class Program {
static void Main() {
Personne p1 = new Personne("Jean", "Dupont", 30);
p1.Identifie();
Personne p2 = new Personne(p1);
p2.Identifie();
}
}
}
ve elde edilen sonuçlar:
4.1.10. Nesne referansları
Her zaman aynı Personne sınıfını kullanıyoruz. Test programı şu şekilde olur:
using System;
namespace Chap2 {
class Program2 {
static void Main() {
// p1
Personne p1 = new Personne("Jean", "Dupont", 30);
Console.Write("p1="); p1.Identifie();
// p2, p1 ile aynı nesneye başvurur
Personne p2 = p1;
Console.Write("p2="); p2.Identifie();
// p3, p1 tarafından işaret edilen nesnenin bir kopyası olacak bir nesneye işaret eder
Personne p3 = new Personne(p1);
Console.Write("p3="); p3.Identifie();
// p1 tarafından referans verilen nesnenin durumu değiştirilir
p1.Initialise("Micheline", "Benoît", 67);
Console.Write("p1="); p1.Identifie();
// p2=p1 olduğu için, p2'nin işaret ettiği nesnenin durumu değişmiş olmalı
Console.Write("p2="); p2.Identifie();
// p3, p1 ile aynı nesneye referans vermediğinden, p3 tarafından referans verilen nesnenin durumu değişmemiş olmalıdır
Console.Write("p3="); p3.Identifie();
}
}
}
Elde edilen sonuçlar şunlardır:
p1 değişkenini
p1, Personne("Jean","Dupont",30) nesnesine referans verir, ancak nesnenin kendisi değildir. C dilinde buna bir işaretçi denir; c.a.d, oluşturulan nesnenin adresidir. Ardından şunu yazarsak:
Değiştirilen Personne("Jean","Dupont",30) nesnesi değil, değeri değişen p1 referansıdır. Personne("Jean","Dupont",30) nesnesi, başka hiçbir değişken tarafından referanslanmıyorsa "kaybolur".
Şu kod yazıldığında:
p2 işaretçisi başlatılır: bu işaretçi, p1 işaretçisiyle aynı nesneye "işaret eder" (aynı nesneyi gösterir). Dolayısıyla, p1 tarafından "işaret edilen" (veya referans verilen) nesne değiştirilirse, p2 tarafından referans verilen nesne de değiştirilmiş olur.
Şu kod yazıldığında:
yazıldığında, yeni bir Personne nesnesi oluşturulur. Bu yeni nesneye p3 tarafından referans verilir. p1 tarafından "işaret edilen" (veya referans verilen) nesneyi değiştirirsek, p3 tarafından referans verilen nesne üzerinde hiçbir değişiklik olmaz. Elde edilen sonuçlar da bunu göstermektedir.
4.1.11. Nesne referansı türündeki parametrelerin aktarımı
Önceki bölümde, bir fonksiyonun parametrelerinin, .NET yapısı ile temsil edilen basit bir C# türünü temsil ettiği durumlarda aktarım yöntemlerini incelemiştik. Parametre bir nesne referansı olduğunda ne olduğunu görelim:
using System;
using System.Text;
namespace Chap1 {
class P12 {
public static void Main() {
// örnek 4
StringBuilder sb0 = new StringBuilder("essai0"), sb1 = new StringBuilder("essai1"), sb2 = new StringBuilder("essai2"), sb3;
Console.WriteLine("Dans fonction appelante avant appel : sb0={0}, sb1={1}, sb2={2}", sb0,sb1, sb2);
ChangeStringBuilder(sb0, sb1, ref sb2, out sb3);
Console.WriteLine("Dans fonction appelante après appel : sb0={0}, sb1={1}, sb2={2}, sb3={3}", sb0, sb1, sb2, sb3);
}
private static void ChangeStringBuilder(StringBuilder sbf0, StringBuilder sbf1, ref StringBuilder sbf2, out StringBuilder sbf3) {
Console.WriteLine("Début fonction appelée : sbf0={0}, sbf1={1}, sbf2={2}", sbf0,sbf1, sbf2);
sbf0.Append("*****");
sbf1 = new StringBuilder("essai1*****");
sbf2 = new StringBuilder("essai2*****");
sbf3 = new StringBuilder("essai3*****");
Console.WriteLine("Fin fonction appelée : sbf0={0}, sbf1={1}, sbf2={2}, sbf3={3}", sbf0, sbf1, sbf2, sbf3);
}
}
}
- 8. satır: StringBuilder türünde 3 nesne tanımlar. Bir StringBuilder nesnesi, bir string. nesnesine yakındır. Bir string nesnesiyle işlem yapıldığında, karşılığında yeni bir string nesnesi elde edilir. Dolayısıyla şu kod dizisinde:
- satır, bellekte bir string nesnesi oluşturur ve s bu nesnenin adresidir. 2. satırda, s.ToUpperCase() işlevi bellekte başka bir string nesnesi oluşturur. Böylece 1. ve 2. satırlar arasında s'in değeri değişmiştir (artık yeni nesneye işaret etmektedir). StringBuilder sınıfı ise ikinci bir nesne oluşturulmadan bir dizgiyi dönüştürmeye olanak tanır. Bu, yukarıda verilen örnektir:
- 8. satır: [sb0, sb1, sb2, sb3]'in StringBuilder türündeki nesnelere yaptığı 4 referans
- 10. satır: farklı modlarla ChangeStringBuilder yöntemine aktarılmıştır: sb0, sb1 varsayılan modda, sb2 ref anahtar sözcüğüyle, sb3 out anahtar sözcüğüyle.
- 15-22. satırlar: [sbf0, sbf1, sbf2, sbf3] biçimindeki biçimsel parametrelere sahip bir yöntem. sbfi biçimsel parametreleri ile sbi etkin parametreleri arasındaki ilişkiler şöyledir:
- sbf0 ve sb0, yöntemin başlatılması sırasında aynı nesneye işaret eden iki ayrı referanstır (adreslerin değer olarak aktarılması)
- Aynı durum sbf1 ve sb1 için de geçerlidir
- sbf2 ve sb2, yöntemin başlatılması sırasında aynı nesneye (ref anahtar kelimesi) ait tek bir referanstır
- sbf3 ve sb3, yöntemin yürütülmesinden sonra aynı nesneye ait aynı referanstır (anahtar kelime out)
Elde edilen sonuçlar şunlardır:
Açıklamalar:
- sb0 ve sbf0, aynı nesneye ait iki farklı referanstır. Bu nesne, sbf0 - satır 3 aracılığıyla değiştirilmiştir. Bu değişiklik, sb0 - satır 4 aracılığıyla görülebilir.
- sb1 ve sbf1, aynı nesneye ait iki farklı referanstır. sbf1'in değeri yöntem içinde değiştirilmiştir ve artık yeni bir nesneye işaret etmektedir - satır 3. Bu, sb1'in değerini hiçbir şekilde değiştirmez; bu referans aynı nesneye işaret etmeye devam eder - satır 4.
- sb2 ve sbf2, aynı nesneye ait tek bir referanstır. sbf2'in değeri yöntem içinde değiştirilir ve artık yeni bir nesneye işaret eder - 3. satır. sbf2 ve sb2 tek ve aynı varlık olduğundan, sb2'in değeri de değiştirilmiştir ve sb2, sbf2 ile aynı nesneye işaret etmektedir - 3. ve 4. satırlar.
- Yöntem çağrılmadan önce, sb3'in bir değeri yoktu. Yöntem çağrıldıktan sonra, sb3, sbf3'in değerini alır. Dolayısıyla aynı nesneye iki referans vardır - 3. ve 4. satırlar
4.1.12. Geçici nesneler
Bir ifadede, bir nesnenin oluşturucusunu açıkça çağırabiliriz: nesne oluşturulur, ancak ona erişemeyiz (örneğin, onu değiştirmek için). Bu geçici nesne, ifadenin değerlendirilmesi için oluşturulur ve ardından atılır. Bu nesnenin kapladığı bellek alanı, daha sonra "çöp toplayıcı" adı verilen bir program tarafından otomatik olarak geri kazanılır; bu programın görevi, program verileri tarafından artık referans gösterilmeyen nesnelerin kapladığı bellek alanını geri kazanmaktır.
Aşağıdaki yeni test programını ele alalım:
using System;
namespace Chap2 {
class Program {
static void Main() {
new Personne(new Personne("Jean", "Dupont", 30)).Identifie();
}
}
}
ve Personne sınıfının oluşturucularını bir mesaj görüntüleyecek şekilde değiştirelim:
// yapıcılar
public Personne(String p, String n, int age) {
Console.WriteLine("Constructeur Personne(string, string, int)");
Initialise(p, n, age);
}
public Personne(Personne P) {
Console.Out.WriteLine("Constructeur Personne(Personne)");
Initialise(P);
}
Şu sonuçları elde ediyoruz:
Bu, iki geçici nesnenin art arda oluşturulduğunu göstermektedir.
4.1.13. Özel öznitelikleri okuma ve yazma yöntemleri
Personne sınıfına, nesnelerin özniteliklerinin durumunu okumak veya değiştirmek için gerekli yöntemleri ekliyoruz:
using System;
namespace Chap2 {
public class Personne {
// özellikler
private string prenom;
private string nom;
private int age;
// yapıcılar
public Personne(String p, String n, int age) {
Console.WriteLine("Constructeur Personne(string, string, int)");
Initialise(p, n, age);
}
public Personne(Personne p) {
Console.Out.WriteLine("Constructeur Personne(Personne)");
Initialise(p);
}
// yöntem
public void Initialise(string p, string n, int age) {
this.prenom = p;
this.nom = n;
this.age = age;
}
public void Initialise(Personne p) {
prenom = p.prenom;
nom = p.nom;
age = p.age;
}
// erişim yöntemleri
public String GetPrenom() {
return prenom;
}
public String GetNom() {
return nom;
}
public int GetAge() {
return age;
}
//değiştiriciler
public void SetPrenom(String P) {
this.prenom = P;
}
public void SetNom(String N) {
this.nom = N;
}
public void SetAge(int age) {
this.age = age;
}
// yöntem
public void Identifie() {
Console.WriteLine("[{0}, {1}, {2}]", prenom, nom, age);
}
}
}
Yeni sınıfı aşağıdaki programla test ediyoruz:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Personne p = new Personne("Jean", "Michelin", 34);
Console.Out.WriteLine("p=(" + p.GetPrenom() + "," + p.GetNom() + "," + p.GetAge() + ")");
p.SetAge(56);
Console.Out.WriteLine("p=(" + p.GetPrenom() + "," + p.GetNom() + "," + p.GetAge() + ")");
}
}
}
ve şu sonuçları elde ediyoruz:
4.1.14. Özellikler
Bir sınıfın özniteliklerine erişmenin başka bir yolu da özellikler oluşturmaktır. Bu özellikler, özel öznitelikleri sanki genelmiş gibi işlememize olanak tanır.
Aşağıdaki Personne sınıfını ele alalım; burada önceki erişim ve değiştirme yöntemleri, okuma ve yazma özellikleriyle değiştirilmiştir:
using System;
namespace Chap2 {
public class Personne {
// özellikler
private string prenom;
private string nom;
private int age;
// oluşturucular
public Personne(String p, String n, int age) {
Initialise(p, n, age);
}
public Personne(Personne p) {
Initialise(p);
}
// yöntem
public void Initialise(string p, string n, int age) {
this.prenom = p;
this.nom = n;
this.age = age;
}
public void Initialise(Personne p) {
prenom = p.prenom;
nom = p.nom;
age = p.age;
}
// özellikler
public string Prenom {
get { return prenom; }
set {
// ad geçerli mi?
if (value == null || value.Trim().Length == 0) {
throw new Exception("prénom (" + value + ") invalide");
} else {
prenom = value;
}
}//if
}//ad
public string Nom {
get { return nom; }
set {
// soyadı geçerli mi?
if (value == null || value.Trim().Length == 0) {
throw new Exception("nom (" + value + ") invalide");
} else { nom = value; }
}//if
}//soyadı
public int Age {
get { return age; }
set {
// yaş geçerli mi?
if (value >= 0) {
age = value;
} else
throw new Exception("âge (" + value + ") invalide");
}//eğer
}//yaş
// yöntem
public void Identifie() {
Console.WriteLine("[{0}, {1}, {2}]", prenom, nom, age);
}
}
}
Bir özellik, bir özniteliğin değerini okumaya (get) veya ayarlamaya (set) olanak tanır. Bir özellik şu şekilde tanımlanır:
burada Type, özellik tarafından yönetilen özniteliğin türü olmalıdır. Bu özellik, get ve set adlı iki yönteme sahip olabilir. Get yöntemi genellikle yönettiği özniteliğin değerini döndürmekle görevlidir (başka bir şey de döndürebilir; bunu engelleyen bir şey yoktur). set yöntemi, value adlı bir parametre alır ve bunu normalde yönettiği özniteliğe atar. Bu yöntem, alınan değerin geçerliliğini kontrol etmek için bu fırsatı değerlendirebilir ve değer geçersiz çıkarsa bir istisna oluşturabilir. Burada yapılan da budur.
Bu get ve set yöntemleri nasıl çağrılır? Aşağıdaki test programını ele alalım:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Personne p = new Personne("Jean", "Michelin", 34);
Console.Out.WriteLine("p=(" + p.Prenom + "," + p.Nom + "," + p.Age + ")");
p.Age = 56;
Console.Out.WriteLine("p=(" + p.Prenom + "," + p.Nom + "," + p.Age + ")");
try {
p.Age = -4;
} catch (Exception ex) {
Console.Error.WriteLine(ex.Message);
}//try-catch
}
}
}
Şu komutta
Console.Out.WriteLine("p=(" + p.Prenom + "," + p.Nom + "," + p.Age + ")");
komutunda, p kişisinin Prenom, Nom ve Age özelliklerinin değerleri alınmaya çalışılır. Bu durumda, bu özelliklerin get yöntemi çağrılır ve bu yöntem, yönettikleri özniteliğin değerini döndürür.
Aşağıdaki komutta
komutunda, Age özelliğinin değeri ayarlanmak istenmektedir. Bu durumda, bu özelliğin set yöntemi çağrılır. Bu yöntem, value parametresinde 56 değerini alır.
C sınıfına ait ve yalnızca get yöntemini tanımlayan bir P özelliği, salt okunur olarak adlandırılır. c, C sınıfına ait bir nesne ise, c.P=değer işlemi derleyici tarafından reddedilir.
Önceki test programının çalıştırılması şu sonuçları verir:
Dolayısıyla özellikler, özel öznitelikleri sanki genelmiş gibi işlememize olanak tanır. Özelliklerin bir başka özelliği de, aşağıdaki sözdizimine göre bir oluşturucu ile birlikte kullanılabilmeleridir:
Bu sözdizimi, aşağıdaki kodla eşdeğerdir:
Özelliklerin sırası önemli değildir. İşte bir örnek.
Personne sınıfına, parametresiz yeni bir yapıcı eklenir:
public Personne() {
}
Yapıcı, nesnenin üyelerini başlatmaz. Buna varsayılan yapıcı denir. Sınıfta herhangi bir yapıcı tanımlanmadığında bu yapıcı kullanılır.
Aşağıdaki kod, daha önce sunulan sözdizimini kullanarak yeni bir Personne nesnesi oluşturur ve başlatır (6. satır):
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Personne p2 = new Personne { Age = 7, Prenom = "Arthur", Nom = "Martin" };
Console.WriteLine("p2=({0},{1},{2})", p2.Prenom, p2.Nom, p2.Age);
}
}
}
Yukarıdaki 6. satırda, parametresiz Personne() oluşturucu kullanılmaktadır. Bu özel durumda, şu şekilde de yazılabilirdi:
Personne p2 = new Personne() { Age = 7, Prenom = "Arthur", Nom = "Martin" };
yazılabilirdi, ancak bu sözdiziminde parametresiz Personne() oluşturucusunun parantezleri zorunlu değildir.
Çalıştırma sonuçları şunlardır:
Çoğu durumda, bir özelliğin get ve set yöntemleri, başka bir işlem yapmadan yalnızca özel bir alanı okur ve yazar. Bu senaryoda, aşağıdaki gibi tanımlanmış bir otomatik özellik kullanılabilir:
Özelliğe bağlı özel alan beyan edilmez. Bu alan derleyici tarafından otomatik olarak oluşturulur. Bu alana yalnızca özelliği aracılığıyla erişilebilir. Böylece, şunu yazmak yerine:
private string prenom;
...
// ilişkili özellik
public string Prenom {
get { return prenom; }
set {
// ad geçerli mi?
if (value == null || value.Trim().Length == 0) {
throw new Exception("prénom (" + value + ") invalide");
} else {
prenom = value;
}
}//if
}//ad
şöyle yazabiliriz:
prenom özel alanını bildirmeden. Yukarıdaki iki özellik arasındaki fark, ilkinin set'teki adın geçerliliğini kontrol etmesi, ikincisinin ise herhangi bir kontrol yapmamasıdır.
Otomatik özellik Prenom'i kullanmak, Prenom alanını genel olarak tanımlamakla aynı anlama gelir:
Bu iki bildirim arasında bir fark olup olmadığı sorusu akla gelebilir. public'i bir sınıfın alanı olarak bildirmek tavsiye edilmez. Bu, bir nesnenin durumunun kapsülleme kavramına aykırıdır; nesnenin durumu özel olmalı ve genel yöntemler aracılığıyla ortaya konulmalıdır.
Otomatik özellik virtuelle, olarak tanımlanırsa, bu özellik bir alt sınıfta yeniden tanımlanabilir:
class Class1 {
public virtual string Prop { get; set; }
}
class Class2 : Class1 {
public override string Prop { get { return base.Prop; } set {... } }
}
Yukarıdaki 2. satırda, Class2 alt sınıfı, set,'e, ana sınıf Class1'in base.Prop otomatik özelliğine atanan değerin geçerliliğini kontrol eden kod ekleyebilir.
4.1.15. Sınıf yöntemleri ve öznitelikleri
Bir uygulamada oluşturulan Personne nesnelerinin sayısını saymak istediğimizi varsayalım. Bir sayacı kendimiz yönetebiliriz, ancak burada veya orada oluşturulan geçici nesneleri unutma riski vardır. Personne sınıfının oluşturucularına, bir sayacı artıran bir komut eklemek daha güvenli görünmektedir. Sorun, oluşturucunun sayacı artırabilmesi için bu sayaca bir referans aktarılmasıdır: onlara yeni bir parametre aktarılması gerekir. Sayacı sınıf tanımına da dahil edebiliriz. Bu, sınıfın kendisinin bir özniteliği olduğu ve bu sınıfın belirli bir örneğine ait olmadığı için, static anahtar sözcüğüyle farklı şekilde tanımlanır:
private static long nbPersonnes;
Buna başvurmak için, bunun Personne sınıfının kendisinin bir özniteliği olduğunu belirtmek amacıyla Personne.nbPersonnes yazılır. Burada, sınıfın dışından doğrudan erişilemeyen özel bir öznitelik oluşturduk. Bu nedenle, nbPersonnes sınıf özniteliğine erişim sağlamak için bir genel özellik oluşturuyoruz. nbPersonnes değerini elde etmek için, bu özelliğin get yönteminin belirli bir Personne nesnesine ihtiyacı yoktur: zira nbPersonnes, tüm sınıfın bir özniteliğidir. Bu nedenle, yine static olarak tanımlanmış bir özelliğe ihtiyaç vardır:
public static long NbPersonnes {
get { return nbPersonnes; }
}
Bu özellik, dışarıdan Personne.NbPersonnes sözdizimiyle çağrılacaktır. İşte bir örnek.
Personne sınıfı şu şekilde olur:
using System;
namespace Chap2 {
public class Personne {
// sınıf öznitelikleri
private static long nbPersonnes;
public static long NbPersonnes {
get { return nbPersonnes; }
}
// örnek öznitelikleri
private string prenom;
private string nom;
private int age;
// yapıcılar
public Personne(String p, String n, int age) {
Initialise(p, n, age);
nbPersonnes++;
}
public Personne(Personne p) {
Initialise(p);
nbPersonnes++;
}
...
}
- ve 24. satırlarda, oluşturucular 7. satırdaki statik alanı artırır.
Aşağıdaki programla:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Personne p1 = new Personne("Jean", "Dupont", 30);
Personne p2 = new Personne(p1);
new Personne(p1);
Console.WriteLine("Nombre de personnes créées : " + Personne.NbPersonnes);
}
}
}
şu sonuçlar elde edilir:
4.1.16. Kişilerden oluşan bir dizi
Bir nesne, diğer veriler gibi bir veridir ve bu nedenle birden fazla nesne bir dizide bir araya getirilebilir:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
// bir kişi tablosu
Personne[] amis = new Personne[3];
amis[0] = new Personne("Jean", "Dupont", 30);
amis[1] = new Personne("Sylvie", "Vartan", 52);
amis[2] = new Personne("Neil", "Armstrong", 66);
// görüntüleme
foreach (Personne ami in amis) {
ami.Identifie();
}
}
}
}
- 7. satır: Personne türünde 3 elemanlı bir dizi oluşturur. Bu 3 eleman burada null, c.a.d değerleriyle başlatılır; bu değerler herhangi bir nesneye referans vermez. Yine, dilin yanlış kullanımı nedeniyle, nesne dizisinden söz ediliyor; oysa bu sadece nesne referanslarından oluşan bir dizidir. Kendisi de bir nesne olan nesne dizisinin oluşturulması (new'in varlığı), elemanlarının türünde hiçbir nesne oluşturmaz: bunu daha sonra yapmak gerekir.
- 8-10. satırlar: Personne türündeki 3 nesnenin oluşturulması
- 12-14. satırlar: amis dizisinin içeriğinin görüntülenmesi
Aşağıdaki sonuçlar elde edilir:
4.2. Örnekle miras alma
4.2.1. Genel bilgiler
Burada miras kavramını ele alıyoruz. Mirasın amacı, mevcut bir sınıfı ihtiyaçlarımıza uygun hale getirmek için “kişiselleştirmektir”. Enseignant adlı bir sınıf oluşturmak istediğimizi varsayalım: bir öğretmen, özel bir kişidir. Başka bir kişinin sahip olmayacağı özellikleri vardır: örneğin, öğrettiği ders. Ancak aynı zamanda her insanın sahip olduğu özelliklere de sahiptir: adı, soyadı ve yaşı. Dolayısıyla bir öğretmen, Personne sınıfının tam bir parçasıdır, ancak ek özelliklere de sahiptir. Enseignant sınıfını sıfırdan yazmak yerine, Personne sınıfının mevcut özelliklerini alıp bunları öğretmenlerin kendine özgü özelliklerine uyarlamayı tercih ederiz. Bunu mümkün kılan şey, miras alma kavramıdır.
Enseignant sınıfının Personne sınıfının özelliklerini miras aldığını ifade etmek için şunu yazacağız:
Personne, üst sınıf (veya ana sınıf) olarak adlandırılırken, Enseignant ise türetilmiş sınıf (veya alt sınıf) olarak adlandırılır. Bir Enseignant nesnesi, bir Personne nesnesinin tüm özelliklerine sahiptir: aynı özniteliklere ve yöntemlere sahiptir. Üst sınıfın bu öznitelikleri ve yöntemleri, alt sınıfın tanımında tekrarlanmaz; sadece alt sınıf tarafından eklenen öznitelikler ve yöntemler belirtilir:
Personne sınıfının şu şekilde tanımlandığını varsayalım:
using System;
namespace Chap2 {
public class Personne {
// sınıf öznitelikleri
private static long nbPersonnes;
public static long NbPersonnes {
get { return nbPersonnes; }
}
// örnek özellikleri
private string prenom;
private string nom;
private int age;
// yapıcılar
public Personne(String prenom, String nom, int age) {
Nom = nom;
Prenom = prenom;
Age = age;
nbPersonnes++;
Console.WriteLine("Constructeur Personne(string, string, int)");
}
public Personne(Personne p) {
Nom = p.Nom;
Prenom = p.Prenom;
Age = p.Age;
nbPersonnes++;
Console.WriteLine("Constructeur Personne(Personne)");
}
// özellikler
public string Prenom {
get { return prenom; }
set {
// ad geçerli mi?
if (value == null || value.Trim().Length == 0) {
throw new Exception("prénom (" + value + ") invalide");
} else {
prenom = value;
}
}//if
}//ad
public string Nom {
get { return nom; }
set {
// soyadı geçerli mi?
if (value == null || value.Trim().Length == 0) {
throw new Exception("nom (" + value + ") invalide");
} else { nom = value; }
}//if
}//soyadı
public int Age {
get { return age; }
set {
// yaş geçerli mi?
if (value >= 0) {
age = value;
} else
throw new Exception("âge (" + value + ") invalide");
}//eğer
}//yaş
// özellik
public string Identite {
get { return String.Format("[{0}, {1}, {2}]", prenom, nom, age);}
}
}
}
Identifie yöntemi, kişiyi tanımlayan ve salt okunur olan Identite özelliği ile değiştirilmiştir. Personne sınıfından miras alan bir Enseignant sınıfı oluşturuyoruz:
using System;
namespace Chap2 {
class Enseignant : Personne {
// özellikler
private int section;
// oluşturucu
public Enseignant(string prenom, string nom, int age, int section)
: base(prenom, nom, age) {
// Section özelliği aracılığıyla bölüm kaydedilir
Section = section;
// izleme
Console.WriteLine("Construction Enseignant(string, string, int, int)");
}//oluşturucu
// Section özelliği
public int Section {
get { return section; }
set { section = value; }
}// Section
}
}
Enseignant sınıfı, Personne sınıfının yöntemlerine ve özniteliklerine şunları ekler:
- 4. satır: Enseignant sınıfı, Personne sınıfından türetilmiştir
- 6. satır: section özniteliği, öğretmenin öğretmen kadrosunda ait olduğu bölüm numarasını belirtir (genel olarak her ders için bir bölüm). Bu özel özniteliğe, 18-21. satırlardaki Section genel özelliği aracılığıyla erişilebilir
- 9. satır: Bir öğretmenin tüm özniteliklerini başlatmaya yarayan yeni bir oluşturucu
4.2.2. Öğretmen nesnesinin oluşturulması
Bir alt sınıf, üst sınıfının oluşturucularını miras almaz. Bu nedenle kendi oluşturucularını tanımlaması gerekir. Enseignant sınıfının oluşturucusu şöyledir:
// oluşturucu
public Enseignant(string prenom, string nom, int age, int section)
: base(prenom, nom, age) {
// bölüm kaydediliyor
Section = section;
// takip
Console.WriteLine("Construction enseignant(string, string, int, int)");
}//üretici
Bildirim
public Enseignant(string prenom, string nom, int age, int section)
: base(prenom, nom, age) {
, oluşturucunun dört parametre aldığını belirtir: prenom, nom, age, section parametrelerini aldığını ve üç parametreyi ((prenom,nom,age)) temel sınıfına, bu örnekte Personne sınıfına ilettiğini belirtir. Bu sınıfın, (prenom,nom,age) parametreleriyle bir kişi oluşturmaya olanak tanıyan Personne(string, string, int) adlı bir oluşturucuya sahip olduğu bilinmektedir. Temel sınıfın oluşturulması tamamlandıktan sonra, Enseignant nesnesinin oluşturulması, oluşturucunun gövdesinin yürütülmesiyle devam eder:
= işaretinin solunda, nesnenin section özniteliği değil, ona bağlı Section özelliği kullanıldığına dikkat edilmelidir. Bu, oluşturucunun bu yöntemin yapabileceği olası geçerlilik denetimlerinden yararlanmasını sağlar. Böylece, bu kontrollerin iki farklı yere (yapıcıya ve özelliğe) yerleştirilmesi gerekmez.
Özetle, türetilmiş bir sınıfın oluşturucusu:
- temel sınıfın oluşturulması için ihtiyaç duyduğu parametreleri temel sınıfa aktarır
- diğer parametreleri, kendisine özgü öznitelikleri başlatmak için kullanır
Şu şekilde yazmak da tercih edilebilirdi:
// oluşturucu
public Enseignant(string prenom, string nom, int age, int section){
this.prenom=prenom;
this.nom=nom;
this.age=age;
this.section=section;
}
Bu imkansız. Personne sınıfı, prenom, nom ve age adlı üç alanını özel (private) olarak tanımlamıştır. Yalnızca aynı sınıfa ait nesneler bu alanlara doğrudan erişebilir. Buradaki gibi alt nesneler de dahil olmak üzere diğer tüm nesneler, bu alanlara erişmek için genel yöntemleri kullanmak zorundadır. Personne sınıfı bu üç alanı korumalı (protected) olarak tanımlamış olsaydı durum farklı olurdu: bu durumda, türetilmiş sınıfların bu üç alana doğrudan erişmesine izin vermiş olurdu. Örneğimizde, üst sınıfın oluşturucusunu kullanmak doğru çözümdü ve bu genel olarak uygulanan yöntemdir: bir alt sınıf nesnesi oluşturulurken, önce üst sınıf nesnesinin oluşturucusu çağrılır, ardından bu sefer alt sınıf nesnesine (örneğimizde section) özgü başlatma işlemleri tamamlanır.
İlk test programı [Program.cs]’i deneyelim:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Console.WriteLine(new Enseignant("Jean", "Dupont", 30, 27).Identite);
}
}
}
Bu program, sadece bir Enseignant nesnesi oluşturup (new) onu tanımlamaktadır. Enseignant sınıfında Identite yöntemi yoktur, ancak üst sınıfında bu yöntem mevcuttur ve üstelik kamuya açıktır: miras yoluyla Enseignant sınıfının kamu açık bir yöntemi haline gelir.
Projenin tamamı şu şekildedir:
![]() |
Elde edilen sonuçlar şöyledir:
Görüldüğü gibi:
- Personne nesnesi (1. satır), Enseignant nesnesinden (2. satır) önce oluşturulmuştur
- elde edilen kimlik, Personne nesnesine aittir
4.2.3. Bir yöntem veya özelliğin yeniden tanımlanması
Önceki örnekte, öğretmenin Personne kısmının kimliğini elde ettik, ancak Enseignant sınıfına (bölüm) özgü bazı bilgiler eksik. Bu nedenle, öğretmeni tanımlamaya yarayan bir özellik yazmamız gerekiyor:
using System;
namespace Chap2 {
class Enseignant : Personne {
// özellikler
private int section;
// oluşturucu
public Enseignant(string prenom, string nom, int age, int section)
: base(prenom, nom, age) {
// Section özelliği aracılığıyla bölüm kaydedilir
Section = section;
// izleme
Console.WriteLine("Construction Enseignant(string, string, int, int)");
}//oluşturucu
// Section özelliği
public int Section {
get { return section; }
set { section = value; }
}// bölüm
// Identite özelliği
public new string Identite {
get { return String.Format("Enseignant[{0},{1}]", base.Identite, Section); }
}
}
}
24-26. satırlarda, Enseignant sınıfının Identite özelliği, üst sınıfının (base.Identite) Identite özelliğine dayanmaktadır (25. satır) "Personne" kısmını görüntülemek için kullanılır ve ardından Enseignant sınıfına özgü section alanı ile tamamlanır. Identite özelliğinin tanımına dikkat edelim:
public new string Identite{
enseignant E nesnesini ele alalım. Bu nesne, içinde bir Personne nesnesi barındırır:
![]() |
Identite özelliği hem Enseignant sınıfında hem de üst sınıfı olan Personne sınıfında tanımlanmıştır. Alt sınıf olan Enseignant'te, Identite özelliğinin önüne, Enseignant. sınıfı için yeni bir Identite özelliğinin yeniden tanımlandığını belirtmek üzere new anahtar sözcüğü eklenmelidir
public new string Identite{
Enseignant sınıfı artık iki Identite özelliğine sahiptir:
- ana sınıf Personne'ten miras alınan özellik
- kendine ait olan
E, Enseignant nesnesi ise, E.Identite, Enseignant sınıfının Identite özelliğini belirtir. Kız sınıfın Identite özelliğinin, ana sınıfın Identite özelliğini yeniden tanımladığı veya gizlediği söylenir. Genel olarak, O bir nesne ve M bir yöntem ise, O.M yöntemini çalıştırmak için sistem, M yöntemini aşağıdaki sırayla arar:
- O nesnesinin sınıfında
- varsa, üst sınıfında
- varsa, ana sınıfının ana sınıfında
- vb…
Dolayısıyla miras, alt sınıfta üst sınıftaki aynı isimli yöntemleri/özellikleri yeniden tanımlamaya olanak tanır. Bu, alt sınıfın kendi ihtiyaçlarına göre uyarlanmasını sağlar. Biraz sonra ele alacağımız polimorfizmle birleştiğinde, yöntemlerin/özelliklerin yeniden tanımlanması mirasın en önemli avantajıdır.
Daha önce kullandığımız test programını ele alalım:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Console.WriteLine(new Enseignant("Jean", "Dupont", 30, 27).Identite);
}
}
}
Bu sefer elde edilen sonuçlar şunlardır:
4.2.4. Polimorfizm
Bir sınıf dizisini ele alalım: C0 ← C1 ← C2 ← … ←Cn
burada Ci ← Cj, Cj sınıfının Ci sınıfından türediğini gösterir. Bu, Cj sınıfının Ci sınıfının tüm özelliklerine ek olarak başka özelliklere de sahip olduğu anlamına gelir. Ci türünde Oi nesneleri olsun. Şu şekilde yazmak geçerlidir:
Nitekim, miras yoluyla Cj sınıfı, Ci sınıfının tüm özelliklerine ek olarak başka özelliklere de sahiptir. Dolayısıyla, Cj türündeki bir Oj nesnesi, kendi içinde Ci türünde bir nesne barındırır. Şu işlem:
işlemi, Oi'nin Oj nesnesinde bulunan Ci türündeki nesneye bir referans olmasını sağlar.
Ci sınıfına ait bir Oi değişkeninin, aslında sadece Ci sınıfına ait bir nesneye değil, aynı zamanda Ci, sınıfından türetilmiş herhangi bir nesneye de başvurabilmesi durumuna polimorfizm denir: bir değişkenin farklı nesne türlerine başvurabilme özelliği.
Bir örnek verelim ve herhangi bir sınıftan bağımsız olan aşağıdaki işlevi (static) ele alalım:
Aşağıdaki şekilde de yazabiliriz:
ya da
Bu son durumda, Affiche adlı statik yöntemin Personne türündeki p formal parametresine, Enseignant türünde bir değer atanacaktır. Enseignant türü, Personne türünden türediği için bu işlem geçerlidir.
4.2.5. Yeniden tanımlama ve polimorfizm
Affiche yöntemimizi tamamlayalım:
public static void Affiche(Personne p) {
// p kimliğini gösterir
Console.WriteLine(p.Identite);
}//gösterir
p.Identite özelliği, Personne p nesnesini tanımlayan bir karakter dizisi döndürür. Önceki örnekte, Affiche yöntemine aktarılan parametre Enseignant türünde bir nesne ise ne olur:
Enseignant e = new Enseignant(...);
Affiche(e);
Şu örneği inceleyelim:
using System;
namespace Chap2 {
class Program2 {
static void Main(string[] args) {
// bir öğretmen
Enseignant e = new Enseignant("Lucile", "Dumas", 56, 61);
Affiche(e);
// bir kişi
Personne p = new Personne("Jean", "Dupont", 30);
Affiche(p);
}
// gösterir
public static void Affiche(Personne p) {
// p'nin kimlik bilgilerini gösterir
Console.WriteLine(p.Identite);
}//gösterir
}
}
Elde edilen sonuçlar şunlardır:
Çalıştırma sonucu, p.Identite komutunun (17. satır) her seferinde bir Personne'in Identite özelliğini çalıştırdığını göstermektedir; önce (7. satır) Enseignant'te bulunan e kişisi, ardından (10. satır) Personne p'nin kendisi. Bu, Affiche'e parametre olarak gerçekten geçirilen nesneye uymamıştır. Enseignant e nesnesinin tam kimliğini elde etmeyi tercih ederdik. Bunun için p.Identite notasyonunun, p tarafından gerçekte işaret edilen nesnenin Identite özelliğine atıfta bulunması gerekirdi; "Personne" nesnesinin Identite özelliğine değil, p tarafından gerçekten işaret edilen nesnenin Identite özelliğine atıfta bulunması gerekirdi.
Bu sonuca, Identite'i temel sınıf Personne'te sanal (virtual) bir özellik olarak tanımlayarak ulaşılabilir:
public virtual string Identite {
get { return String.Format("[{0}, {1}, {2}]", prenom, nom, age); }
}
virtual anahtar sözcüğü, Identite'i sanal bir özellik haline getirir. Bu anahtar sözcüğü yöntemlere de uygulanabilir. Sanal bir özelliği veya yöntemi yeniden tanımlayan alt sınıflar, yeniden tanımladıkları özellik/yöntemi nitelemek için new yerine override anahtar sözcüğünü kullanmalıdır. Dolayısıyla, Enseignant sınıfında, Identite özelliği şu şekilde yeniden tanımlanmıştır:
public override string Identite {
get { return String.Format("Enseignant[{0},{1}]", base.Identite, Section); }
}
Yukarıdaki program şu sonuçları verir:
Bu sefer, 3. satırda öğretmenin tam kimliğini elde ettik. Şimdi bir özellik yerine bir yöntemi yeniden tanımlayalım. object sınıfı (System.Object'in C# takma adı), tüm C# sınıflarının "ana" sınıfıdır. Dolayısıyla şunu yazdığımızda:
yazdığımızda, dolaylı olarak şunu yazmış oluruz:
System.Object sınıfı, ToString adlı bir sanal yöntem tanımlar:
![]() |
ToString yöntemi, aşağıdaki örnekte gösterildiği gibi nesnenin ait olduğu sınıfın adını döndürür:
using System;
namespace Chap2 {
class Program2 {
static void Main(string[] args) {
// bir öğretmen
Console.WriteLine(new Enseignant("Lucile", "Dumas", 56, 61).ToString());
// bir kişi
Console.WriteLine(new Personne("Jean", "Dupont", 30).ToString());
}
}
}
Elde edilen sonuçlar şunlardır:
Personne ve Enseignant sınıflarında ToString yöntemini yeniden tanımlamamış olsak da, Object sınıfındaki ToString yönteminin, nesnenin gerçek sınıf adını görüntüleyebildiğini görebiliriz.
Personne ve Enseignant sınıflarındaki ToString yöntemini yeniden tanımlayalım:
// yöntem ToString
public override string ToString() {
return Identite;
}
Tanım her iki sınıfta da aynıdır. Aşağıdaki test programını ele alalım:
using System;
namespace Chap2 {
class Program3 {
public static void Main() {
// bir öğretmen
Enseignant e = new Enseignant("Lucile", "Dumas", 56, 61);
Affiche(e);
// bir kişi
Personne p = new Personne("Jean", "Dupont", 30);
Affiche(p);
}
// afiş
public static void Affiche(Personne p) {
// kimlik afişi
Console.WriteLine(p);
}//Afiş
}
}
Bir p nesnesini parametre olarak kabul eden Affiche yöntemine odaklanalım. 15. satırda, Console sınıfının WriteLine yönteminin, Personne türünde bir parametre alan hiçbir varyantı yoktur. Writeline'in çeşitli varyantları arasında, Object türünde bir parametre kabul eden bir varyant bulunmaktadır. Derleyici, WriteLine(Object o) yöntemini kullanacaktır; çünkü bu imza, o parametresinin Object türünde veya bunun türevlerinden biri olabileceği anlamına gelir. Object tüm sınıfların üst sınıfı olduğundan, herhangi bir nesne WriteLine'e parametre olarak geçirilebilir; dolayısıyla Personne veya Enseignant türündeki bir nesne de kullanılabilir. WriteLine(Object o) yöntemi, o.ToString()'i Out yazma akışına yazar. ToString yöntemi sanal olduğundan, o nesnesi (Object türünde veya türevinde) ToString yöntemini yeniden tanımlamışsa, bu yöntem kullanılacaktır. Personne ve Enseignant sınıflarında durum böyledir.
Çalıştırma sonuçları da bunu göstermektedir:
4.3. Bir sınıf için bir operatörün anlamını yeniden tanımlama
4.3.1. Giriş
Şu komutu ele alalım:
komutunu ele alalım; burada op1 ve op2 iki operandır. + operatörünün anlamını yeniden tanımlamak mümkündür. op1 operandı, C1 sınıfına ait bir nesne ise, C1 sınıfında aşağıdaki imzaya sahip bir statik yöntem tanımlanmalıdır:
Derleyici şu komuta rastladığında
komutuyla karşılaşırsa, bunu C1.operator+(op1,op2) şeklinde çevirir. operator yönteminin döndürdüğü tür önemlidir. Nitekim, op1+op2+op3 işlemini ele alalım. Bu işlem, derleyici tarafından (op1+op2)+op3 olarak dönüştürülür. res12'in op1+op2'in sonucu olduğunu varsayalım. Ardından gerçekleştirilen işlem res12+op3'tir. res12, C1 türündeyse, o da C1.operator+(res12,op3) şeklinde dönüştürülür. Bu sayede işlemler birbirine zincirlenebilir.
Tek bir işleneni olan tekli operatörleri de yeniden tanımlayabiliriz. Örneğin, op1, C1 türünde bir nesne ise, op1++ işlemi, C1 sınıfının statik bir yöntemi ile yeniden tanımlanabilir:
Burada belirtilenler, birkaç istisna dışında çoğu operatör için geçerlidir:
- == ve != operatörleri aynı anda yeniden tanımlanmalıdır
- &&, ||, [], (), +=, -=, ... operatörleri yeniden tanımlanamaz
4.3.2. Bir örnek
ArrayList sınıfından türetilmiş bir ListeDePersonnes sınıfı oluşturuyoruz. Bu sınıf dinamik bir listeyi uygular ve bir sonraki bölümde ele alınacaktır. Bu sınıftan yalnızca aşağıdaki öğeleri kullanıyoruz:
- L.Add(Object o) yöntemi, L listesine bir o nesnesi eklemeye olanak tanır. Burada o nesnesi, bir Personne nesnesi olacaktır.
- L listesindeki öğe sayısını veren L.Count özelliği
- L listesinin i'inci elemanını veren L[i] gösterimi
ListeDePersonnes sınıfı, ArrayList sınıfının tüm özniteliklerini, yöntemlerini ve özelliklerini miras alacaktır. Tanımı şöyledir:
using System;
using System.Collections;
using System.Text;
namespace Chap2 {
class ListeDePersonnes : ArrayList{
// + operatörünün yeniden tanımlanması, listeye bir kişi eklemek için
public static ListeDePersonnes operator +(ListeDePersonnes l, Personne p) {
// p kişisini ListeDePersonnes listesine ekliyoruz
l.Add(p);
// ListeDePersonnes listesi
return l;
}// + operatörü
// ToString
public override string ToString() {
// (el1, el2, ..., eln) olarak döndürür
// açılış parantezi
StringBuilder listeToString = new StringBuilder("(");
// kişi listesini (this) tarar
for (int i = 0; i < Count - 1; i++) {
listeToString.Append(this[i]).Append(",");
}//for
// son eleman
if (Count != 0) {
listeToString.Append(this[Count-1]);
}
// kapanış parantezi
listeToString.Append(")");
// bir dize döndürülmelidir
return listeToString.ToString();
}//ToString
}
}
- 6. satır: ListeDePersonnes sınıfı, ArrayList sınıfından türetilmiştir
- 8-13. satırlar: l + p işlemi için + operatörünün tanımı; burada l, ListeDePersonnes türündedir ve p, Personne türündedir veya bu sınıftan türetilmiştir.
- 10. satır: p kişisi l listesine eklenir. Burada, üst sınıf olan ArrayList sınıfının Add yöntemi kullanılmaktadır.
- 12. satır: l listesine yapılan referans, l + p1 + p2 gibi + operatörlerinin zincirlenebilmesi için döndürülür. l+p1+p2 işlemi (operatör önceliği dikkate alınarak) (l+p1)+p2 olarak yorumlanacaktır. l+p1 işlemi, l referansını döndürecektir. (l+p1)+p2 işlemi ise l+p2 olur ve bu işlem, p2 kişisini l kişi listesine ekler.
- 16. satır: Kişi listesini (kişi1, kişi2, ..) şeklinde görüntülemek için ToString yöntemini yeniden tanımlıyoruz; burada personnei, Personne sınıfının ToString yönteminin sonucudur.
- 19. satır: StringBuilder türünde bir nesne kullanıyoruz. Karakter dizisi üzerinde çok sayıda işlem (burada eklemeler) yapılması gerektiğinde, bu sınıf string sınıfından daha uygundur. Nitekim, bir string nesnesi üzerinde yapılan her işlem yeni bir string nesnesi oluştururken, bir StringBuilder nesnesi üzerinde yapılan aynı işlemler nesneyi değiştirir ancak yeni bir nesne oluşturmaz. Dizgileri birleştirmek için Append yöntemini kullanıyoruz.
- 21. satır: Kişi listesindeki öğeler tek tek taranır. Bu liste burada this ile gösterilir. Bu, ToString yönteminin uygulandığı geçerli nesnedir. Count özelliği, üst sınıf olan ArrayList sınıfının bir özelliğidir.
- 22. satır: this adlı geçerli listenin i numaralı öğesine this[i] notasyonu ile erişilebilir. Bu da yine ArrayList sınıfının bir özelliğidir. Dizgileri birleştirmek söz konusu olduğu için, this[i].ToString() yöntemi kullanılacaktır. Bu yöntem sanal olduğu için, this nesnesinin Personne türünde veya türevinde olan ToString yöntemi kullanılacaktır.
- 31. satır: string türünde bir nesne döndürmemiz gerekiyor (16. satır). StringBuilder sınıfında, StringBuilder türünden string türüne geçiş yapmayı sağlayan ToString adlı bir yöntem bulunmaktadır.
ListeDePersonnes sınıfının bir oluşturucusu olmadığına dikkat edilmelidir. Bu durumda,
kullanılacağı bilinir. Bu yapıcı, üst sınıfının parametresiz yapıcısını çağırmaktan başka bir şey yapmaz:
Bir test sınıfı şu şekilde olabilir:
using System;
namespace Chap2 {
class Program1 {
static void Main(string[] args) {
// kişi listesi
ListeDePersonnes l = new ListeDePersonnes();
// kişilerin eklenmesi
l = l + new Personne("jean", "martin",10) + new Personne("pauline", "leduc",12);
// görüntüleme
Console.WriteLine("l=" + l);
l = l + new Enseignant("camille", "germain",27,60);
Console.WriteLine("l=" + l);
}
}
}
- 7. satır: bir kişi listesi oluşturma
- 9. satır: + operatörüyle 2 kişinin eklenmesi
- 12. satır: bir öğretmenin eklenmesi
- 11. ve 13. satırlar: yeniden tanımlanan ListeDePersonnes.ToString() yönteminin kullanılması.
Sonuçlar:
4.4. Bir sınıf için bir indeksleyici tanımlama
Burada ListeDePersonnes sınıfını kullanmaya devam ediyoruz. l, ListeDePersonnes nesnesi ise, l listesindeki i numaralı kişiyi hem okuma (Kişi p=l[i]) hem de yazma işlemlerinde (l[i]=new Kişi(...)).
l[i] yazabilmek için (burada l[i], Personne nesnesini ifade eder), ListeDePersonnes sınıfında aşağıdaki this yöntemini tanımlamamız gerekir:
public Personne this[int i] {
get { ... }
set { ... }
}
this[int i] yöntemine bir indeksleyici denir; çünkü bu yöntem, obj bir dizi değil bir nesne olmasına rağmen, dizi notasyonunu anımsatan obj[i] ifadesine bir anlam kazandırır. this yönteminin içindeki get yöntemi,obj nesnesinin this yöntemi, variable=obj[i] yazıldığında çağrılır; obj[i]=değer yazıldığında ise set yöntemi çağrılır.
ListeDePersonnes sınıfı, kendisi de bir indeksleyiciye sahip olan ArrayList sınıfından türetilmiştir:
ListeDePersonnes sınıfındaki this yöntemi arasında bir çakışma vardır:
public Personne this[int i]
ile ArrayList sınıfındaki this yöntemi arasında bir çakışma var:
public object this[int i]
çünkü aynı adı taşıyorlar ve aynı türde parametre (int) kabul ediyorlar. ListeDePersonnes sınıfındaki this yönteminin, ArrayList sınıfındaki aynı adlı yöntemi “gizlediğini” belirtmek için, ListeDePersonnes sınıfının indeksleyicisi bildirimine new anahtar sözcüğünü eklemek zorundayız. Dolayısıyla şöyle yazacağız:
public new Personne this[int i]{
get { ... }
set { ... }
}
Bu yöntemi tamamlayalım. Örneğin, variable=l[i] yazıldığında this.get yöntemi çağrılır; burada l, ListeDePersonnes türündedir. Bu durumda, l listesindeki i numaralı kişiyi döndürmeliyiz. Bu, base[i] notasyonu ile yapılır; bu notasyon, ArrayList sınıfının i numaralı nesnesini, ListeDePersonnes sınıfının temel sınıfı haline getirir. Döndürülen nesne Object türünde olduğundan, Personne sınıfına bir tür dönüştürme gereklidir.
public new Personne this[int i]{
get { return (Personne) base[i]; }
set { ... }
}
set yöntemi, l[i]=p yazıldığında çağrılır; burada p, bir Personne'tir. Bu durumda, p kişisi, l listesinin i öğesine atanır.
public new Personne this[int i]{
get { ... }
set { base[i]=value; }
}
Burada, value anahtar sözcüğüyle temsil edilen p kişisi, ArrayList temel sınıfının i numaralı elemanına atanmıştır.
Dolayısıyla, ListeDePersonnes sınıfının indeksleyicisi şu şekilde olacaktır:
public new Personne this[int i]{
get { return (Personne) base[i]; }
set { base[i]=value; }
}
Şimdi, ["nom"], c.a.d gibi kişi adlarını da yazabilmek ve l listesini artık öğe numarasıyla değil, kişi adıyla indekslemek istiyoruz. Bunun için yeni bir indeksleyici tanımlıyoruz:
// ad üzerinden arama
public int this[string nom] {
get {
// kişi aranıyor
for (int i = 0; i < Count; i++) {
if (((Personne)base[i]).Nom == nom)
return i;
}//for
return -1;
}//al
}
İlk satır
public int this[string nom]
satırı, ListeDePersonnes sınıfının nom karakter dizisiyle indekslendiğini ve sonucun bir tamsayı olduğunu belirtir. Bu tamsayı, nom adlı kişinin listedeki konumunu gösterir; bu kişi listede yoksa -1 değerini verir. Yalnızca get, özelliği tanımlanır; böylece, set özelliğinin tanımlanmasını gerektirecek olan l["nom"]=değer yazma işlemi engellenir. new anahtar sözcüğü, indeksleyicinin bildiriminde gerekli değildir; çünkü temel sınıf ArrayList, this[string] indeksleyicisini tanımlamamaktadır.
get'in gövdesinde, parametre olarak geçirilen adı bulmak için kişi listesi taranır. Ad, i konumunda bulunursa i değeri, bulunmazsa -1 değeri döndürülür.
Önceki test programı şu şekilde tamamlanır:
using System;
namespace Chap2 {
class Program2 {
static void Main(string[] args) {
// kişi listesi
ListeDePersonnes l = new ListeDePersonnes();
// kişilerin eklenmesi
l = l + new Personne("jean", "martin",10) + new Personne("pauline", "leduc",12);
// görüntüleme
Console.WriteLine("l=" + l);
l = l + new Enseignant("camille", "germain",27,60);
Console.WriteLine("l=" + l);
// 1. öğeyi değiştir
l[1] = new Personne("franck", "gallon",5);
// 1. öğeyi görüntüleme
Console.WriteLine("l[1]=" + l[1]);
// liste görüntüleme l
Console.WriteLine("l=" + l);
// kişi arama
string[] noms = { "martin", "germain", "xx" };
for (int i = 0; i < noms.Length; i++) {
int inom = l[noms[i]];
if (inom != -1)
Console.WriteLine("Personne(" + noms[i] + ")=" + l[inom]);
else
Console.WriteLine("Personne(" + noms[i] + ") n'existe pas");
}//for
}
}
}
Çalıştırıldığında şu sonuçlar elde edilir:
4.5. Yapılar
C#'daki yapı, C dilindeki yapıya benzer ve sınıf kavramına çok yakındır. Bir yapı şu şekilde tanımlanır:
Beyan şekillerinin benzerliğine rağmen, sınıf ve yapı arasında önemli farklılıklar vardır. Örneğin, yapılarda miras kavramı yoktur. Türetilmemesi gereken bir sınıf yazarsak, ikisi arasında seçim yapmamıza yardımcı olacak yapı ve sınıf arasındaki farklar nelerdir? Bunu öğrenmek için aşağıdaki örneği inceleyelim:
using System;
namespace Chap2 {
class Program1 {
static void Main(string[] args) {
// bir sp1 yapısı
SPersonne sp1;
sp1.Nom = "paul";
sp1.Age = 10;
Console.WriteLine("sp1=SPersonne(" + sp1.Nom + "," + sp1.Age + ")");
// bir sp2 yapısı
SPersonne sp2 = sp1;
Console.WriteLine("sp2=SPersonne(" + sp2.Nom + "," + sp2.Age + ")");
// sp2 değiştirildi
sp2.Nom = "nicole";
sp2.Age = 30;
// sp1 ve sp2'nin doğrulanması
Console.WriteLine("sp1=SPersonne(" + sp1.Nom + "," + sp1.Age + ")");
Console.WriteLine("sp2=SPersonne(" + sp2.Nom + "," + sp2.Age + ")");
// bir op1 nesnesi
CPersonne op1=new CPersonne();
op1.Nom = "paul";
op1.Age = 10;
Console.WriteLine("op1=CPersonne(" + op1.Nom + "," + op1.Age + ")");
// bir op2 nesnesi
CPersonne op2=op1;
Console.WriteLine("op2=CPersonne(" + op2.Nom + "," + op2.Age + ")");
// op2 değiştirildi
op2.Nom = "nicole";
op2.Age = 30;
// op1 ve op2'nin doğrulanması
Console.WriteLine("op1=CPersonne(" + op1.Nom + "," + op1.Age + ")");
Console.WriteLine("op2=CPersonne(" + op2.Nom + "," + op2.Age + ")");
}
}
// yapı SPersonne
struct SPersonne {
public string Nom;
public int Age;
}
// sınıfı CPersonne
class CPersonne {
public string Nom;
public int Age;
}
}
- 38-41. satırlar: iki public alanı olan bir yapı: Nom, Age
- 44-47. satırlar: iki public alanı olan bir sınıf: Nom, Age
Bu program çalıştırıldığında şu sonuçlar elde edilir:
Daha önce Personne sınıfını kullandığımız yerde, artık SPersonne yapısını kullanıyoruz:
struct SPersonne {
public string Nom;
public int Age;
}
Bu yapı burada bir oluşturucuya sahip değildir. Daha sonra göstereceğimiz gibi bir oluşturucuya sahip olabilir. Varsayılan olarak, her zaman parametresiz oluşturucuya sahiptir; burada SPersonne().
- kodun 7. satırı:
SPersonne sp1;
şu komutla eşdeğerdir:
SPersonne sp1=new Spersonne();
(Ad, Yaş) yapısı oluşturulur ve sp1'in değeri bu yapının kendisidir. Sınıf durumunda, (Ad, Yaş) nesnesinin oluşturulması new operatörüyle açıkça yapılmalıdır (22. satır):
CPersonne op1=new CPersonne();
Önceki komut, bir CPersonne nesnesi oluşturur (kabaca bizim yapımıza eşdeğer) ve p1'in değeri bu nesnenin adresi (referansı) olur.
Özetleyelim
- yapı söz konusu olduğunda, sp1'in değeri yapının kendisidir
- sınıf söz konusu olduğunda, op1'in değeri oluşturulan nesnenin adresidir
![]() |
Programda 12. satıra şunu yazdığımızda:
SPersonne sp2 = sp1;
yeni bir sp2(Ad, Yaş) yapısı oluşturulur ve sp1, değeri, yani yapının kendisi ile başlatılır.
![]() |
sp1 yapısı, sp2'ye ([1]) kopyalanır. Bu, değerlerin kopyalanmasıdır. Şimdi 27. satırdaki komutu ele alalım:
CPersonne op2=op1;
Sınıflar söz konusu olduğunda, op1'in değeri op2'ye kopyalanır, ancak bu değer aslında nesnenin adresi olduğu için nesne kopyalanmaz: [2].
[1] yapısı söz konusu olduğunda, sp2 değerini değiştirirsek sp1 değerini değiştirmiş olmayız; program da bunu göstermektedir. [2] nesnesinde ise, op2 tarafından işaret edilen nesne değiştirilirse, op1 tarafından işaret edilen nesne de aynı olduğu için değiştirilir. Programın sonuçları da bunu göstermektedir.
Dolayısıyla bu açıklamalardan şunu çıkarabiliriz:
- yapı türündeki bir değişkenin değeri, yapının kendisidir
- nesne türündeki bir değişkenin değeri, işaret edilen nesnenin adresidir
Bu temel fark anlaşıldıktan sonra, yapı, aşağıdaki yeni örnekte de görüldüğü gibi sınıfa çok benzemektedir:
using System;
namespace Chap2 {
// yapı SPersonne
struct SPersonne {
// özel öznitelikler
private string nom;
private int age;
// özellikler
public string Nom {
get { return nom; }
set { nom = value; }
}//ad
public int Age {
get { return age; }
set { age = value; }
}//yaş
// Üretici
public SPersonne(string nom, int age) {
this.nom = nom;
this.age = age;
}//üretici
// ToString
public override string ToString() {
return "SPersonne(" + Nom + "," + Age + ")";
}//ToString
}//yapı
}//ad alanı
- 8-9. satırlar: iki özel alan
- 12-20. satırlar: ilgili genel özellikler
- 23-26. satırlar: bir oluşturucu tanımlanıyor. Parametresiz oluşturucu SPersonne() her zaman mevcuttur ve bildirilmesine gerek yoktur. Bildirilmesi durumunda derleyici tarafından reddedilir. 23-26. satırlardaki oluşturucuda, nom ve age özel alanlarını, Nom ve Age genel özellikleri aracılığıyla başlatmak cazip gelebilir. Bu, derleyici tarafından reddedilir. Yapının oluşturulması sırasında yapının yöntemleri kullanılamaz.
- 29-31. satırlar: ToString yönteminin yeniden tanımlanması.
Bir test programı şu şekilde olabilir:
using System;
namespace Chap2 {
class Program1 {
static void Main(string[] args) {
// bir kişi p1
SPersonne p1=new SPersonne();
p1.Nom="paul";
p1.Age= 10;
Console.WriteLine("p1={0}",p1);
// bir kişi p2
SPersonne p2 = p1;
Console.WriteLine("p2=" + p2);
// p2 değiştirildi
p2.Nom = "nicole";
p2.Age = 30;
// p1 ve p2'nin doğrulanması
Console.WriteLine("p1=" + p1);
Console.WriteLine("p2=" + p2);
// bir kişi p3
SPersonne p3 = new SPersonne("amandin", 18);
Console.WriteLine("p3=" + p3);
// bir kişi p4
SPersonne p4 = new SPersonne { Nom = "x", Age = 10 };
Console.WriteLine("p4=" + p4);
}
}
}
- 7. satır: Yapıda başka bir oluşturucu olduğu için, parametresiz oluşturucuyu açıkça kullanmak zorundayız. Yapıda hiçbir oluşturucu olmasaydı,
SPersonne p1;
komutu boş bir yapı oluşturmak için yeterli olurdu.
- 8-9. satırlar: Yapı, genel özellikleri aracılığıyla başlatılır
- 10. satır: p1.ToString yöntemi, WriteLine içinde kullanılacak.
- 21. satır: SPersonne(string, int) yapıcıyla bir yapı oluşturulur
- 24. satır: Parametresiz SPersonne() oluşturucusu kullanılarak bir yapı oluşturulur; küme parantezleri içinde, özel alanlar genel özellikleri aracılığıyla başlatılır.
Aşağıdaki yürütme sonuçları elde edilir:
Burada yapı ile sınıf arasındaki tek belirgin fark, bir sınıf kullanıldığında p1 ve p2 nesnelerinin programın sonunda aynı nesneye işaret etmiş olacağıdır.
4.6. Arayüzler
Bir arayüz, bir sözleşme oluşturan yöntem veya özellik prototiplerinden oluşan bir kümedir. Bir arayüzü uygulamaya karar veren bir sınıf, arayüzde tanımlanan tüm yöntemlerin bir uygulamasını sağlamayı taahhüt eder. Bu uygulamayı denetleyen derleyicidir.
Örneğin, System.Collections.IEnumerator arayüzünün tanımı şöyledir:
public interface System.Collections.IEnumerator
{
// Özellikler
Object Current { get; }
// Yöntemler
bool MoveNext();
void Reset();
}
Arayüzün özellikleri ve yöntemleri yalnızca imzalarıyla tanımlanır. Bunlar uygulanmaz (kodları yoktur). Arayüzü uygulayan sınıflar, arayüzün yöntemlerine ve özelliklerine kod ekler.
- 1. satır: C sınıfı, IEnumerator sınıfını uygular. Bir arayüzün uygulanmasında kullanılan : işareti ile bir sınıfın türetilmesinde kullanılan işaretin aynı olduğuna dikkat edilmelidir.
- 3-5. satırlar: IEnumerator arayüzünün yöntem ve özelliklerinin uygulanması.
Şu arayüzü ele alalım:
namespace Chap2 {
public interface IStats {
double Moyenne { get; }
double EcartType();
}
}
IStats arayüzü şunları içerir:
- bir salt okunur özellik Moyenne: bir değer dizisinin ortalamasını hesaplamak için
- bir EcartType yöntemi: standart sapmayı hesaplamak için
Burada, söz konusu değer dizisinin ne olduğu hiçbir yerde belirtilmediğine dikkat edilmelidir. Bu, bir sınıfın not ortalaması, belirli bir ürünün aylık satış ortalaması, belirli bir yerdeki ortalama sıcaklık vb. olabilir. Arayüzlerin çalışma prensibi budur: nesnede yöntemlerin varlığı varsayılır, ancak belirli verilerin varlığı varsayılmaz.
IStats arayüzünün ilk uygulama sınıfı, belirli bir dersten bir sınıfın öğrencilerinin notlarını kaydetmeye yarayan bir sınıf olabilir. Bir öğrenci, aşağıdaki Elève yapısıyla tanımlanır:
public struct Elève {
public string Nom { get; set; }
public string Prénom { get; set; }
}//Öğrenci
Öğrenci, adı ve soyadıyla tanımlanır. 2-3. satırlarda, bu iki özniteliğin otomatik özellikleri bulunur.
Bir not, aşağıdaki Note yapısıyla tanımlanabilir:
public struct Note {
public Elève Elève { get; set; }
public double Valeur { get; set; }
}//Not
Not, not verilen öğrenci ve notun kendisi ile tanımlanır. 2-3. satırlarda, bu iki öznitelik için otomatik özellikler bulunur.
Belirli bir dersteki tüm öğrencilerin notları, aşağıdaki TableauDeNotes sınıfında toplanır:
using System;
using System.Text;
namespace Chap2 {
public class TableauDeNotes : IStats {
// özellikler
public string Matière { get; set; }
public Note[] Notes { get; set; }
public double Moyenne { get; private set; }
private double ecartType;
// oluşturucu
public TableauDeNotes(string matière, Note[] notes) {
// genel özellikler aracılığıyla depolama
Matière = matière;
Notes = notes;
// not ortalamasının hesaplanması
double somme = 0;
for (int i = 0; i < Notes.Length; i++) {
somme += Notes[i].Valeur;
}
if (Notes.Length != 0) Moyenne = somme / Notes.Length;
else Moyenne = -1;
// standart sapma
double carrés = 0;
for (int i = 0; i < Notes.Length; i++) {
carrés += Math.Pow((Notes[i].Valeur - Moyenne), 2);
}//for
if (Notes.Length != 0)
ecartType = Math.Sqrt(carrés / Notes.Length);
else ecartType = -1;
}//oluşturucu
public double EcartType() {
return ecartType;
}
// ToString
public override string ToString() {
StringBuilder valeur = new StringBuilder(String.Format("matière={0}, notes=(", Matière));
int i;
// tüm notları birleştiririz
for (i = 0; i < Notes.Length-1; i++) {
valeur.Append("[").Append(Notes[i].Elève.Prénom).Append(",").Append(Notes[i].Elève.Nom).Append(",").Append(Notes[i].Valeur).Append("],");
};
//son not
if (Notes.Length != 0) {
valeur.Append("[").Append(Notes[i].Elève.Prénom).Append(",").Append(Notes[i].Elève.Nom).Append(",").Append(Notes[i].Valeur).Append("]");
}
valeur.Append(")");
// son
return valeur.ToString();
}//ToString
}//sınıf
}
- 6. satır: TableauDeNotes sınıfı, IStats arayüzünü uygulamaktadır. Dolayısıyla, Moyenne özelliğini ve EcartType yöntemini uygulamalıdır. Bunlar 10. satırda (Moyenne) ve 35-37. satırlarda (EcartType) uygulanmıştır
- 8-10. satırlar: üç otomatik özellik
- 8. satır: nesnenin notlarını kaydettiği ders
- 9. satır: Öğrencilerin not tablosu (Öğrenci, Not)
- 10. satır: not ortalaması - IStats arayüzünün Moyenne özelliğini uygulayan özellik.
- 11. satır: notların standart sapmasını saklayan alan - 35-37. satırlarla ilişkili get yöntemi, EcartType arayüzünün EcartType yöntemini uygular.
- 9. satır: notlar bir dizide saklanır. Bu dizi, TableauDeNotes sınıfının oluşturulması sırasında 14-33. satırlardaki oluşturucuya aktarılır.
- 14-33. satırlar: Oluşturucu. Burada, oluşturucuya aktarılan notların daha sonra değişmeyeceği varsayılmaktadır. Bu nedenle, oluşturucu kullanılarak bu notların ortalaması ve standart sapması hemen hesaplanır ve 10-11. satırlardaki alanlara kaydedilir. Ortalama, 10. satırdaki Moyenne otomatik özelliğinin altındaki özel alana kaydedilir; standart sapma ise 11. satırdaki özel alana kaydedilir.
- 10. satır: Otomatik özellik Moyenne’in get yöntemi, altta yatan özel alanı döndürür.
- 35-37. satırlar: EcartType yöntemi, 11. satırdaki özel alanın değerini döndürür.
Bu kodda birkaç ince ayrıntı bulunmaktadır:
- 23. satır: Moyenne özelliğinin set yöntemi, atama yapmak için kullanılır. Bu yöntem, 10. satırda özel olarak tanımlanmıştır; böylece Moyenne özelliğine bir değer atanması yalnızca sınıfın içinde mümkün olmaktadır.
- 40-54. satırlar: Performansı artırmak amacıyla, TableauDeNotes nesnesini temsil eden dizgiyi oluşturmak için bir StringBuilder nesnesi kullanılır. Bunun kodun okunabilirliğini büyük ölçüde olumsuz etkilediği belirtilebilir. Bu, madalyonun diğer yüzüdür.
Önceki sınıfta notlar bir diziye kaydediliyordu. TableauDeNotes nesnesinin oluşturulmasından sonra yeni bir not eklemek mümkün değildi. Şimdi, IStats arayüzünün ListeDeNotes adlı ikinci bir uygulamasını sunuyoruz; bu uygulamada notlar bir listeye kaydedilecek ve ListeDeNotes nesnesinin ilk oluşturulmasından sonra da not ekleme imkanı sağlanacaktır.
ListeDeNotes sınıfının kodu şu şekildedir:
using System;
using System.Text;
using System.Collections.Generic;
namespace Chap2 {
public class ListeDeNotes : IStats {
// özellikler
public string Matière { get; set; }
public List<Note> Notes { get; set; }
public double moyenne = -1;
public double ecartType = -1;
// yapıcı
public ListeDeNotes(string matière, List<Note> notes) {
// genel özellikler aracılığıyla depolama
Matière = matière;
Notes = notes;
}//oluşturucu
// not ekleme
public void Ajouter(Note note) {
// notun eklenmesi
Notes.Add(note);
// ortalama ve standart sapma sıfırlandı
moyenne = -1;
ecartType = -1;
}
// ToString
public override string ToString() {
StringBuilder valeur = new StringBuilder(String.Format("matière={0}, notes=(", Matière));
int i;
// tüm notlar birleştiriliyor
for (i = 0; i < Notes.Count - 1; i++) {
valeur.Append("[").Append(Notes[i].Elève.Prénom).Append(",").Append(Notes[i].Elève.Nom).Append(",").Append(Notes[i].Valeur).Append("],");
};
//son not
if (Notes.Count != 0) {
valeur.Append("[").Append(Notes[i].Elève.Prénom).Append(",").Append(Notes[i].Elève.Nom).Append(",").Append(Notes[i].Valeur).Append("]");
}
valeur.Append(")");
// son
return valeur.ToString();
}//ToString
// notların ortalaması
public double Moyenne {
get {
if (moyenne != -1) return moyenne;
// not ortalamasının hesaplanması
double somme = 0;
for (int i = 0; i < Notes.Count; i++) {
somme += Notes[i].Valeur;
}
// ortalama hesaplanır
if (Notes.Count != 0) moyenne = somme / Notes.Count;
return moyenne;
}
}
public double EcartType() {
// standart sapma
if (ecartType != -1) return ecartType;
// ortalama
double moyenne = Moyenne;
double carrés = 0;
for (int i = 0; i < Notes.Count; i++) {
carrés += Math.Pow((Notes[i].Valeur - moyenne), 2);
}//for
// standart sapma değerini döndürme
if (Notes.Count != 0)
ecartType = Math.Sqrt(carrés / Notes.Count);
return ecartType;
}
}//sınıf
}
- 7. satır: ListeDeNotes sınıfı, IStats arayüzünü uygular
- 10. satır: Notlar artık bir dizi yerine bir listeye eklenmektedir
- 11. satır: TableauDeNotes sınıfının Moyenne otomatik özelliği, burada özel bir alan olan moyenne lehine kaldırılmıştır; 11. satır, 48-60. satırlardaki salt okunur genel özellik Moyenne ile ilişkilendirilmiştir
- 22-28. satırlar: Artık önceden kaydedilmiş notlara yeni notlar eklenebiliyor; bu, daha önce mümkün değildi.
- satır 15-19: dolayısıyla, ortalama ve standart sapma artık oluşturucuda değil, arayüzün kendi yöntemlerinde hesaplanmaktadır: Moyenne (satır 48-60) ve EcartType (62-76). Ancak yeniden hesaplama, yalnızca ortalama ve standart sapma -1'den farklıysa başlatılır (satır 50 ve 64).
Bir test sınıfı şu şekilde olabilir:
using System;
using System.Collections.Generic;
namespace Chap2 {
class Program1 {
static void Main(string[] args) {
// bazı öğrenciler ve İngilizce notları
Elève[] élèves1 = { new Elève { Prénom = "Paul", Nom = "Martin" }, new Elève { Prénom = "Maxime", Nom = "Germain" }, new Elève { Prénom = "Berthine", Nom = "Samin" } };
Note[] notes1 = { new Note { Elève = élèves1[0], Valeur = 14 }, new Note { Elève = élèves1[1], Valeur = 16 }, new Note { Elève = élèves1[2], Valeur = 18 } };
// bir nesneye kaydedilir TableauDeNotes
TableauDeNotes anglais = new TableauDeNotes("anglais", notes1);
// ortalama ve standart sapmanın görüntülenmesi
Console.WriteLine("{2}, Moyenne={0}, Ecart-type={1}", anglais.Moyenne, anglais.EcartType(), anglais);
// öğrencileri ve dersi bir nesneye yerleştiriyoruz ListeDeNotes
ListeDeNotes français = new ListeDeNotes("français", new List<Note>(notes1));
// ortalama ve standart sapma gösterimi
Console.WriteLine("{2}, Moyenne={0}, Ecart-type={1}", français.Moyenne, français.EcartType(), français);
// bir not eklenir
français.Ajouter(new Note { Elève = new Elève { Prénom = "Jérôme", Nom = "Jaric" }, Valeur = 10 });
// ortalama ve standart sapma gösterimi
Console.WriteLine("{2}, Moyenne={0}, Ecart-type={1}", français.Moyenne, français.EcartType(), français);
}
}
}
- 8. satır: parametresiz oluşturucu kullanılarak ve genel özellikler aracılığıyla başlatılarak bir öğrenci tablosu oluşturulur
- 9. satır: Aynı teknik kullanılarak bir not tablosu oluşturulur
- satır 11: ortalamasını ve standart sapmasını hesapladığımız bir TableauDeNotes nesnesi satır 13
- 15. satır: ListeDeNotes nesnesinin ortalaması ve standart sapması 17. satırda hesaplanır. List<Note> sınıfı, IEnumerable<Note> arayüzünü uygulayan bir nesneyi kabul eden bir oluşturucuya sahiptir. notes1 dizisi bu arayüzü uygular ve List<Note> nesnesini oluşturmak için kullanılabilir.
- 19. satır: yeni bir not eklenmesi
- 21. satır: ortalama ve standart sapmanın yeniden hesaplanması
Çalıştırma sonuçları şöyledir:
Önceki örnekte, iki sınıf IStats arayüzünü uygulamaktadır. Bununla birlikte, örnek IStats arayüzünün yararını ortaya koymamaktadır. Test programını şu şekilde yeniden yazalım:
using System;
using System.Collections.Generic;
namespace Chap2 {
class Program2 {
static void Main(string[] args) {
// bazı öğrenciler ve İngilizce notları
Elève[] élèves1 = { new Elève { Prénom = "Paul", Nom = "Martin" }, new Elève { Prénom = "Maxime", Nom = "Germain" }, new Elève { Prénom = "Berthine", Nom = "Samin" } };
Note[] notes1 = { new Note { Elève = élèves1[0], Valeur = 14 }, new Note { Elève = élèves1[1], Valeur = 16 }, new Note { Elève = élèves1[2], Valeur = 18 } };
// bir nesneye kaydedilir TableauDeNotes
TableauDeNotes anglais = new TableauDeNotes("anglais", notes1);
// ortalama ve standart sapma gösterimi
AfficheStats(anglais);
// öğrencileri ve dersi bir nesneye yerleştiriyoruz ListeDeNotes
ListeDeNotes français = new ListeDeNotes("français", new List<Note>(notes1));
// ortalama ve standart sapma gösterimi
AfficheStats(français);
// bir not eklenir
français.Ajouter(new Note { Elève = new Elève { Prénom = "Jérôme", Nom = "Jaric" }, Valeur = 10 });
// ortalama ve standart sapmanın görüntülenmesi
AfficheStats(français);
}
// bir türün ortalama ve standart sapmasının gösterimi IStats
static void AfficheStats(IStats valeurs) {
Console.WriteLine("{2}, Moyenne={0}, Ecart-type={1}", valeurs.Moyenne, valeurs.EcartType(), valeurs);
}
}
}
- 25-27. satırlar: AfficheStats statik yöntemi, parametre olarak IStats türünü, yani bir arayüz türünü alır. Bu, etkin parametrenin IStats arayüzünü uygulayan herhangi bir nesne olabileceği anlamına gelir. Bir arayüz türüne sahip veri kullanıldığında, bu, verinin uyguladığı arayüz yöntemlerinin yalnızca kullanılacağı anlamına gelir. Geri kalanlar göz ardı edilir. Burada, sınıflar için gördüğümüz polimorfizme benzer bir özellik söz konusudur. Eğer birbirleriyle kalıtım yoluyla bağlantılı olmayan (dolayısıyla kalıtım polimorfizmi kullanılamayan) bir Ci sınıflar kümesi, aynı imzaya sahip bir yöntemler kümesi sunuyorsa, bu yöntemleri ilgili tüm sınıfların uygulayacağı bir I arayüzünde bir araya getirmek faydalı olabilir. Bu Ci sınıflarının örnekleri, I, c.a.d türünde bir biçimsel parametre kabul eden fonksiyonların gerçek parametreleri olarak kullanılabilir. Bu fonksiyonlar, farklı Ci sınıflarının kendine özgü özelliklerini ve yöntemlerini değil, yalnızca I arayüzünde tanımlanan Ci nesnelerinin yöntemlerini kullanır.
- 13. satır: AfficheStats yöntemi, IStats arayüzünü uygulayan TableauDeNotes türüyle çağrılır
- 17. satır: Aynı şekilde, ListeDeNotes türüyle de aynısı geçerlidir
Çalıştırma sonuçları, bir önceki örnektekilerle aynıdır.
Bir değişken, bir arayüzün türü olabilir. Dolayısıyla şöyle yazabiliriz:
- satırdaki bildirim, stats1'in IStats arayüzünü uygulayan bir sınıfın örneği olduğunu belirtir. Bu bildirim, derleyicinin stats1 içinde yalnızca arayüzün yöntemlerine erişime izin vereceği anlamına gelir: Moyenne özelliği ve EcartType yöntemi.
Son olarak, arayüzlerin birden fazla şekilde uygulanabileceğini belirtelim; örneğin, c.a.d. Şu şekilde yazılabilir:
şeklinde yazılabilir; burada Ij'ler arayüzlerdir.
4.7. Soyut sınıflar
Soyut sınıf, örneklenemeyen bir sınıftır. Örneklenebilecek türetilmiş sınıflar oluşturulması gerekir.
Soyut sınıflar, bir sınıf dizisinin kodunu sadeleştirmek için kullanılabilir. Şu örneği inceleyelim:
using System;
namespace Chap2 {
abstract class Utilisateur {
// alanlar
private string login;
private string motDePasse;
private string role;
// üretici
public Utilisateur(string login, string motDePasse) {
// bilgiler kaydedilir
this.login = login;
this.motDePasse = motDePasse;
// kullanıcı tanımlanıyor
role=identifie();
// kimliği doğrulandı mı?
if (role == null) {
throw new ExceptionUtilisateurInconnu(String.Format("[{0},{1}]", login, motDePasse));
}
}
// toString
public override string ToString() {
return String.Format("Utilisateur[{0},{1},{2}]", login, motDePasse, role);
}
// kimlik doğrulama
abstract public string identifie();
}
}
- 11-21. satırlar: Utilisateur sınıfının oluşturucusu. Bu sınıf, bir web uygulamasının kullanıcısına ait bilgileri depolar. Uygulamada, kullanıcı adı ve şifre ile kimlik doğrulaması yapılan çeşitli kullanıcı türleri vardır (6-7. satırlar). Bu iki bilgi, bazı kullanıcılar için LDAP hizmetinde, diğerleri için SGBD hizmetinde vb. doğrulanır.
- 13-14. satırlar: Kimlik doğrulama bilgileri saklanır
- 16. satır: Bu bilgiler bir identifie yöntemi tarafından doğrulanır. Kimlik doğrulama yöntemi bilinmediğinden, 29. satırda abstract anahtar sözcüğüyle soyut olarak tanımlanır. identifie yöntemi, kullanıcının rolünü (kısaca ne yapmaya yetkisi olduğunu) belirten bir karakter dizisi döndürür. Bu dize null işaretçisi ise, 19. satırda bir istisna atılır.
- 4. satır: Soyut bir yöntemi olduğu için, sınıfın kendisi de "abstract" anahtar sözcüğüyle soyut olarak tanımlanmıştır.
- 29. satır: "identify" adlı soyut yöntemin bir tanımı yoktur. Bu yönteme bir tanım, türetilmiş sınıflar tarafından verilecektir.
- 24-26. satırlar: Sınıfın bir örneğini tanımlayan ToString yöntemi.
Burada, geliştiricinin Utilisateur sınıfı ve türetilmiş sınıfların örneklerinin oluşturulmasını kontrol etmek istediği varsayılmaktadır; bunun nedeni, kullanıcı tanınmadığında belirli bir türde bir istisna atılacağından emin olmak istemesi olabilir (19. satır). Türetilmiş sınıflar bu oluşturucuya dayanabilir. Bunun için “identifie” yöntemini sağlamaları gerekir.
ExceptionUtilisateurInconnu sınıfı şu şekildedir:
using System;
namespace Chap2 {
class ExceptionUtilisateurInconnu : Exception {
public ExceptionUtilisateurInconnu(string message) : base(message){
}
}
}
- 3. satır: Exception sınıfından türetilmiştir
- 4-6. satırlar: Sınıfın, parametre olarak bir hata mesajı kabul eden tek bir yapıcı işlevi vardır. Bu parametre, aynı yapıcı işlevine sahip olan üst sınıfa (5. satır) aktarılır.
Şimdi Utilisateur sınıfını, Administrateur alt sınıfından türetelim:
namespace Chap2 {
class Administrateur : Utilisateur {
// üretici
public Administrateur(string login, string motDePasse)
: base(login, motDePasse) {
}
// tanımlar
public override string identifie() {
// tanımlama LDAP
// ...
return "admin";
}
}
}
- 4-6. satırlar: Oluşturucu, aldığı parametreleri üst sınıfına aktarmakla yetinir
- 9-12. satırlar: Administrateur sınıfının `identifie` yöntemi. Bir yöneticinin LDAP sistemi ile tanımlandığını varsayalım. Bu yöntem, üst sınıfındaki `identifie` yöntemini yeniden tanımlar. Soyut bir yöntemi yeniden tanımladığı için override. anahtar sözcüğünü eklemeye gerek yoktur
Şimdi Utilisateur sınıfını, alt sınıf olan Observateur'ten türetelim:
namespace Chap2 {
class Observateur : Utilisateur{
// üretici
public Observateur(string login, string motDePasse)
: base(login, motDePasse) {
}
//tanımlar
public override string identifie() {
// tanımlama SGBD
// ...
return "observateur";
}
}
}
- 4-6. satırlar: Oluşturucu, aldığı parametreleri üst sınıfına aktarmakla yetinir
- 9-13. satırlar: Observateur sınıfının kimlik belirleme yöntemi. Bir gözlemcinin, bir veritabanındaki kimlik bilgilerinin doğrulanmasıyla tanımlandığı varsayılmaktadır.
Sonuç olarak, Administrateur ve Observateur nesneleri, üst sınıf olan Utilisateur. sınıfının oluşturucusu tarafından örneklenir. Bu oluşturucu, söz konusu sınıfların sağladığı tanımlama yöntemini kullanacaktır.
Üçüncü bir sınıf olan Inconnu de Utilisateur sınıfından türetilmiştir:
namespace Chap2 {
class Inconnu : Utilisateur{
// üretici
public Inconnu(string login, string motDePasse)
: base(login, motDePasse) {
}
//tanımlayıcı
public override string identifie() {
// kullanıcı bilinmiyor
// ...
return null;
}
}
}
- 13. satır: identifie yöntemi, kullanıcının tanınmadığını belirtmek için null işaretçisini döndürür.
Bir test programı şu şekilde olabilir:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
Console.WriteLine(new Observateur("observer","mdp1"));
Console.WriteLine(new Administrateur("admin", "mdp2"));
try {
Console.WriteLine(new Inconnu("xx", "yy"));
} catch (ExceptionUtilisateurInconnu e) {
Console.WriteLine("Utilisateur non connu : "+ e.Message);
}
}
}
}
6., 7. ve 9. satırlarda, WriteLine yöntemi tarafından [Utilisateur].ToString() yönteminin kullanılacağına dikkat edilmelidir.
Çalıştırma sonuçları şöyledir:
4.8. Sınıflar, arayüzler, jenerik yöntemler
İki tamsayıyı yer değiştiren bir yöntem yazmak istediğimizi varsayalım. Bu yöntem şu şekilde olabilir:
public static void Echanger1(ref int value1, ref int value2){
// value1 ve value2 referansları değiştirilir
int temp = value2;
value2 = value1;
value1 = temp;
}
Şimdi, Personne nesnelerine ait iki referansı yer değiştirmek istersek, şöyle yazardık:
public static void Echanger2(ref Personne value1, ref Personne value2){
// value1 ve value2 referansları yer değiştirilir
Personne temp = value2;
value2 = value1;
value1 = temp;
}
Bu iki yöntemi birbirinden ayıran şey, parametrelerin T türüdür: int yerine Echanger1, Personne yerine Echanger2. Jenerik sınıflar ve arayüzler, yalnızca bazı parametrelerinin türü bakımından farklılık gösteren yöntemlere duyulan ihtiyacı karşılar.
Jenerik bir sınıf kullanıldığında, Echanger yöntemi şu şekilde yeniden yazılabilir:
namespace Chap2 {
class Generic1<T> {
public static void Echanger(ref T value1, ref T value2){
// value1 ve value2 referansları değiştiriliyor
T temp = value2;
value2 = value1;
value1 = temp;
}
}
}
- 2. satır: Generic1 sınıfı, T olarak gösterilen bir türle parametrelendirilir. Bu türe istenilen herhangi bir ad verilebilir. Bu T türü daha sonra sınıfın 3. ve 5. satırlarında yeniden kullanılır. Generic1 sınıfının jenerik bir sınıf olduğu söylenir.
- 3. satır: değiştirilecek T türüne ilişkin iki referansı tanımlar
- 5. satır: temp geçici değişkeni T türündedir.
Sınıfı test etmek için bir program şu şekilde olabilir:
using System;
namespace Chap2 {
class Program {
static void Main(string[] args) {
// int
int i1 = 1, i2 = 2;
Generic1<int>.Echanger(ref i1, ref i2);
Console.WriteLine("i1={0},i2={1}", i1, i2);
// dize
string s1 = "s1", s2 = "s2";
Generic1<string>.Echanger(ref s1, ref s2);
Console.WriteLine("s1={0},s2={1}", s1, s2);
// Kişi
Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
Generic1<Personne>.Echanger(ref p1, ref p2);
Console.WriteLine("p1={0},p2={1}", p1, p2);
}
}
}
- 8. satır: T1, T2, ... türleriyle parametreleştirilmiş bir jenerik sınıf kullanıldığında, bu sınıfların "örnekleştirilmesi" gerekir. 8. satırda, Generic1<int> türündeki Echanger statik yöntemi kullanılarak, Echanger yöntemine geçirilen referansların int türünde olduğu belirtilir.
- 12. satır: Generic1<string> türündeki Echanger statik yöntemi kullanılarak, Echanger yöntemine aktarılan referansların string türünde olduğu belirtilir.
- 16. satır: Generic1<Personne> türündeki statik Echanger yöntemi kullanılarak, Echanger yöntemine aktarılan referansların Personne türünde olduğu belirtilir.
Çalıştırma sonuçları şöyledir:
Echanger yöntemi şu şekilde de yazılabilirdi:
namespace Chap2 {
class Generic2 {
public static void Echanger<T>(ref T value1, ref T value2){
// value1 ve value2 referansları değiştirilir
T temp = value2;
value2 = value1;
value1 = temp;
}
}
}
- 2. satır: Generic2 sınıfı artık jenerik değildir
- 3. satır: Echanger statik yöntemi jeneriktir
Test programı şu şekilde olur:
using System;
namespace Chap2 {
class Program2 {
static void Main(string[] args) {
// int
int i1 = 1, i2 = 2;
Generic2.Echanger<int>(ref i1, ref i2);
Console.WriteLine("i1={0},i2={1}", i1, i2);
// dize
string s1 = "s1", s2 = "s2";
Generic2.Echanger<string>(ref s1, ref s2);
Console.WriteLine("s1={0},s2={1}", s1, s2);
// Kişi
Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
Generic2.Echanger<Personne>(ref p1, ref p2);
Console.WriteLine("p1={0},p2={1}", p1, p2);
}
}
}
- 8., 12. ve 16. satırlar: <> içinde parametrelerin türünü belirterek Echanger yöntemini çağırıyoruz. Aslında derleyici, gerçek parametrelerin türüne göre hangi Echanger yöntem varyantının kullanılacağını çıkarabilir. Dolayısıyla aşağıdaki yazım da geçerlidir:
Generic2.Echanger(ref i1, ref i2);
...
Generic2.Echanger(ref s1, ref s2);
...
Generic2.Echanger(ref p1, ref p2);
1., 3. ve 5. satırlar: Çağrılan Echanger yönteminin varyantı artık belirtilmemiştir. Derleyici, kullanılan gerçek parametrelerin niteliğinden bunu çıkarabilir.
Genel parametrelere kısıtlamalar getirilebilir:

Aşağıdaki yeni jenerik Echanger yöntemini ele alalım:
namespace Chap2 {
class Generic3 {
public static void Echanger<T>(ref T value1, ref T value2) where T : class {
// value1 ve value2 referansları değiştiriliyor
T temp = value2;
value2 = value1;
value1 = temp;
}
}
}
- 3. satır: T türünün bir referans (sınıf, arayüz) olması şartı
Aşağıdaki test programını ele alalım:
using System;
namespace Chap2 {
class Program4 {
static void Main(string[] args) {
// int
int i1 = 1, i2 = 2;
Generic3.Echanger<int>(ref i1, ref i2);
Console.WriteLine("i1={0},i2={1}", i1, i2);
// dize
string s1 = "s1", s2 = "s2";
Generic3.Echanger(ref s1, ref s2);
Console.WriteLine("s1={0},s2={1}", s1, s2);
// Kişi
Personne p1 = new Personne("jean", "clu", 20), p2 = new Personne("pauline", "dard", 55);
Generic3.Echanger(ref p1, ref p2);
Console.WriteLine("p1={0},p2={1}", p1, p2);
}
}
}
Derleyici, 8. satırda bir hata bildirir çünkü int türü bir sınıf veya arayüz değil, bir yapıdır:

Aşağıdaki yeni jenerik Echanger yöntemini ele alalım:
namespace Chap2 {
class Generic4 {
public static void Echanger<T>(ref T element1, ref T element2) where T : Interface1 {
// 2 öğenin değerleri alınır
int value1 = element1.Value();
int value2 = element2.Value();
// 1. öğe > 2. öğe ise, öğeleri değiştiriyoruz
if (value1 > value2) {
T temp = element2;
element2 = element1;
element1 = temp;
}
}
}
}
- 3. satır: T türü, Interface1 arayüzünü uygulamalıdır. Bu arayüzün, 5. ve 6. satırlarda kullanılan Value, yöntemi vardır; bu yöntem, T türündeki nesnenin değerini verir.
- 8-12. satırlar: element1 ve element2 referansları, yalnızca element1'in değeri element2'in değerinden büyükse değiştirilir.
Interface1 arayüzü şu şekildedir:
namespace Chap2 {
interface Interface1 {
int Value();
}
}
Bu arayüz, aşağıdaki Class1 sınıfı tarafından uygulanır:
using System;
using System.Threading;
namespace Chap2 {
class Class1 : Interface1 {
// nesnenin değeri
private int value;
// yapıcı
public Class1() {
// 1 ms bekleme
Thread.Sleep(1);
// 0 ile 99 arasında rastgele bir değer
value = new Random(DateTime.Now.Millisecond).Next(100);
}
// özel alan value erişim yöntemi
public int Value() {
return value;
}
// örneğin durumu
public override string ToString() {
return value.ToString();
}
}
}
- 5. satır: Class1, Interface1 arayüzünü uygular
- 7. satır: Class1 örneğinin değeri
- satır 10-14: value alanı, 0 ile 99 arasında rastgele bir değerle başlatılır
- 18-20. satırlar: Interface1 arayüzünün Value yöntemi
- satır 23-25: sınıfın ToString yöntemi
Interface1 arayüzü, Class2 sınıfı tarafından da uygulanmaktadır:
using System;
namespace Chap2 {
class Class2 : Interface1 {
// nesnenin değerleri
private int value;
private String s;
// oluşturucu
public Class2(String s) {
this.s = s;
value = s.Length;
}
// özel alan erişim yöntemi value
public int Value() {
return value;
}
// örnek durumu
public override string ToString() {
return s;
}
}
}
- 4. satır: Class2, Interface1 arayüzünü uygular
- 6. satır: Class2 örneğinin değeri
- satır 10-13: value alanı, yapıcıya geçirilen karakter dizisinin uzunluğu ile başlatılır
- 16-18. satırlar: Interface1 arayüzünün Value yöntemi
- 21-22. satırlar: sınıfın ToString yöntemi
Bir test programı şu şekilde olabilir:
using System;
namespace Chap2 {
class Program5 {
static void Main(string[] args) {
// Class1 türündeki örnekleri değiştir
Class1 c1, c2;
for (int i = 0; i < 5; i++) {
c1 = new Class1();
c2 = new Class1();
Console.WriteLine("Avant échange --> c1={0},c2={1}", c1, c2);
Generic4.Echanger(ref c1, ref c2);
Console.WriteLine("Après échange --> c1={0},c2={1}", c1, c2);
}
// Class2 türündeki örnekleri değiştirme
Class2 c3, c4;
c3 = new Class2("xxxxxxxxxxxxxx");
c4 = new Class2("xx");
Console.WriteLine("Avant échange --> c3={0},c4={1}", c3, c4);
Generic4.Echanger(ref c3, ref c4);
Console.WriteLine("Avant échange --> c3={0},c4={1}", c3, c4);
}
}
}
- 8-14. satırlar: Class1 türündeki örnekler karşılıklı olarak aktarılır
- satır 16-22: Class2 türündeki örnekler değiştirilir
Çalıştırma sonuçları şöyledir:
'in genel arayüz kavramını örneklemek için, bir kişi tablosunu önce isimlerine, sonra yaşlarına göre sıralayacağız. Bir tabloyu sıralamamızı sağlayan yöntem, Array sınıfının statik Sort yöntemidir:

Hatırlatmak gerekirse, statik bir yöntem, sınıfın bir örneğinin adı yerine sınıfın adını yöntemin önüne ekleyerek kullanılır. Sort yönteminin farklı imza biçimleri vardır (aşırı yüklenmiştir). Biz şu imza biçimini kullanacağız:
Sort, T'nin herhangi bir türü ifade ettiği jenerik bir yöntemdir. Yöntem iki parametre alır:
- T[] dizi: sıralanacak T türündeki elemanlardan oluşan dizi
- IComparer<T> karşılaştırıcı: IComparer<T> arayüzünü uygulayan bir nesne referansı.
IComparer<T>, aşağıdaki gibi tanımlanmış genel bir arayüzdür:
IComparer<T> arayüzünün tek bir yöntemi vardır. Compare yöntemi:
- T türündeki t1 ve t2 öğelerini parametre olarak alır
- t1>t2 ise 1, t1==t2 ise 0, t1<t2 ise -1 değerini döndürür. <, ==, > operatörlerine anlam vermek geliştiricinin sorumluluğundadır. Örneğin, p1 ve p2 iki Personne nesnesi ise, p1'in adı p2'nin adından alfabetik sırayla önce geliyorsa p1>p2 denilebilir. Böylece kişilerin adlarına göre artan sırada bir sıralama elde edilir. Yaşa göre sıralama istiyorsak, p1'in yaşı p2'nin yaşından büyükse p1>p2 deriz.
- Azalan sıralamaya sahip olmak için, +1 ve -1 sonuçlarını tersine çevirmek yeterlidir.
Kişilerden oluşan bir diziyi sıralamak için yeterli bilgiye sahibiz. Program şu şekildedir:
using System;
using System.Collections.Generic;
namespace Chap2 {
class Program6 {
static void Main(string[] args) {
// kişi dizisi
Personne[] personnes1 = { new Personne("claude", "pollon", 25), new Personne("valentine", "germain", 35), new Personne("paul", "germain", 32) };
// görüntüleme
Affiche("Tableau à trier", personnes1);
// adına göre sıralama
Array.Sort(personnes1, new CompareNoms());
// görüntüleme
Affiche("Tableau après le tri selon les nom et prénom", personnes1);
// yaşa göre sıralama
Array.Sort(personnes1, new CompareAges());
// görüntüleme
Affiche("Tableau après le tri selon l'âge", personnes1);
}
static void Affiche(string texte, Personne[] personnes) {
Console.WriteLine(texte.PadRight(50, '-'));
foreach (Personne p in personnes) {
Console.WriteLine(p);
}
}
}
// kişilerin adı ve soyadlarının karşılaştırma sınıfı
class CompareNoms : IComparer<Personne> {
public int Compare(Personne p1, Personne p2) {
// isimler karşılaştırılır
int i = p1.Nom.CompareTo(p2.Nom);
if (i != 0)
return i;
// adların eşitliği - ilk adların karşılaştırılması
return p1.Prenom.CompareTo(p2.Prenom);
}
}
// kişilerin yaşlarının karşılaştırılması sınıfı
class CompareAges : IComparer<Personne> {
public int Compare(Personne p1, Personne p2) {
// yaşların karşılaştırılması
if (p1.Age > p2.Age)
return 1;
else if (p1.Age == p2.Age)
return 0;
else
return -1;
}
}
}
- 8. satır: kişi dizisi
- 12. satır: Kişi dizisinin soyad ve adı sırasına göre sıralanması. Sort jenerik yönteminin 2. parametresi, IComparer<Personne> jenerik arayüzünü uygulayan CompareNoms sınıfının bir örneğidir.
- satır 30-39: IComparer<Kişi> jenerik arayüzünü uygulayan CompareNoms sınıfı.
- satır 31-38: IComparer<T> arayüzünün int CompareTo(T,T) jenerik yönteminin uygulaması. Yöntem, iki karakter dizisini karşılaştırmak için 3.3.5.4 numaralı paragrafta tanıtılan String.CompareTo yöntemini kullanır.
- 16. satır: Kişi tablosunun yaşlara göre sıralanması. Sort jenerik yönteminin 2. parametresi, IComparer<Person> jenerik arayüzünü uygulayan ve 42-51. satırlarda tanımlanan CompareAges sınıfının bir örneğidir.
Çalıştırma sonuçları şöyledir:
4.9. Ad alanları
Ekrana bir satır yazmak için şu komutu kullanırız:
Console sınıfının tanımına bakarsak
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
gördüğümüz gibi, bu sınıf System ad alanının bir parçasıdır. Bu, Console sınıfının System.Console olarak adlandırılması gerektiği anlamına gelir ve aslında şöyle yazmamız gerekir:
Bunu, using cümlesini kullanarak önleyebiliriz:
System ad alanını using cümlesiyle içe aktardığımızı söyleriz. Derleyici bir sınıf adına (burada Console) rastladığında, using cümleleriyle içe aktarılan çeşitli ad alanlarında bu sınıfı aramaya başlar. Burada, System ad alanında Console sınıfını bulacaktır. Şimdi, Console sınıfına ekli ikinci bilgiye dikkat edelim:
Bu satır, Console sınıfının tanımının hangi "derlemede" bulunduğunu gösterir. Visual Studio dışında derleme yaparken, kullanılması gereken sınıfları içeren farklı dll dosyalarının referanslarını belirtmek gerektiğinde, bu bilgi yararlı olabilir. Bir sınıfın derlenmesi için gerekli olan dll dosyalarına referans vermek için şunu yazarsınız:
burada csc, C# derleyicisidir. Bir sınıf oluştururken, bunu bir ad alanı içinde oluşturabilirsiniz. Bu ad alanlarının amacı, örneğin sınıflar satıldığında sınıflar arasında ad çakışmalarını önlemektir. Sırasıyla dll, e1.dll ve e2.dll paketlerinde dağıtılan sınıfları dağıtan iki şirket olduğunu varsayalım. C adlı bir müşteri, her iki şirketin de Personne sınıfını tanımladığı bu iki sınıf kümesini satın alsın. C adlı müşteri, bir programı şu şekilde derler:
prog.cs kaynak kodu Personne sınıfını kullanıyorsa, derleyici, Personne sınıfını e1.dll'ten mi yoksa e2.dll'ten mi alması gerektiğini bilemeyecektir. Bir hata bildirir. Eğer E1 şirketi sınıflarını E1 adlı bir ad alanında, E2 şirketi ise E2 adlı bir ad alanında oluşturursa, o zaman her iki Personne sınıfı da sırasıyla E1.Personne ve E2.Personne olarak adlandırılacaktır. Müşteri, sınıflarında ya E1.Personne ya da E2.Personne'i kullanmalıdır, ancak Personne'i kullanmamalıdır. Ad alanı, bu belirsizliği ortadan kaldırır.
Bir ad alanında sınıf oluşturmak için şu kod yazılır:
4.10. Örnek uygulama - V2
Önceki bölümün 3.6 paragrafında incelenen vergi hesaplamasını ele alalım ve şimdi bunu sınıflar ve arayüzler kullanarak işleyelim. Sorunu hatırlayalım:
Bir vergi mükellefinin vergisini hesaplayan bir program yazmayı hedefliyoruz. Beyan edeceği tek geliri maaşı olan bir vergi mükellefinin basitleştirilmiş durumunu ele alıyoruz (2003 yılı gelirleri için 2004 rakamları):
- Çalışanın hisse sayısı şu şekilde hesaplanır: nbParts = nbEnfants/2 + 1 (eğer bekarsa), evliyse nbEnfants/2+2 olarak hesaplanır; burada nbEnfants, çalışanın çocuk sayısıdır.
- En az üç çocuğu varsa, yarım pay daha ekler
- vergilendirilebilir geliri R=0,72*S olarak hesaplanır; burada S, yıllık maaşıdır
- aile katsayısı QF = R/nbParts olarak hesaplanır
- Vergisi I hesaplanır. Aşağıdaki tabloyu ele alalım:
4262 | 0 | 0 |
8382 | 0,0683 | 291,09 |
14753 | 0,1914 | 1322,92 |
23888 | 0,2826 | 2668,39 |
38868 | 0,3738 | 4846,98 |
47932 | 0,4262 | 6883,66 |
0 | 0,4809 | 9505,54 |
Her satırda 3 alan vardır. I vergisini hesaplamak için, QF<=alan1 olan ilk satırı ararız. Örneğin, QF=5000 ise şu satır bulunur
I vergisi bu durumda 0,0683*R - 291,09*nbParts'e eşittir. Eğer QF, QF<=alan1 ilişkisini hiçbir zaman karşılamıyorsa, o zaman son satırdaki katsayılar kullanılır. Burada:
bu da I=0,4809*R - 9505,54*nbParts vergi tutarını verir.
Öncelikle, önceki tablodaki bir satırı kapsayabilecek bir yapı tanımlıyoruz:
namespace Chap2 {
// bir vergi dilimi
struct TrancheImpot {
public decimal Limite { get; set; }
public decimal CoeffR { get; set; }
public decimal CoeffN { get; set; }
}
}
Ardından, vergiyi hesaplayabilen bir IImpot arayüzü tanımlıyoruz:
namespace Chap2 {
interface IImpot {
int calculer(bool marié, int nbEnfants, int salaire);
}
}
- 3. satır: üç veriden (vergi mükellefinin evli olup olmadığı, çocuk sayısı ve maaşı) vergiyi hesaplama yöntemi
Ardından, bu arayüzü uygulayan bir soyut sınıf tanımlıyoruz:
namespace Chap2 {
abstract class AbstractImpot : IImpot {
// vergi hesaplaması için gerekli vergi dilimleri
// harici bir kaynaktan alınır
protected TrancheImpot[] tranchesImpot;
// vergi hesaplaması
public int calculer(bool marié, int nbEnfants, int salaire) {
// pay sayısının hesaplanması
decimal nbParts;
if (marié) nbParts = (decimal)nbEnfants / 2 + 2;
else nbParts = (decimal)nbEnfants / 2 + 1;
if (nbEnfants >= 3) nbParts += 0.5M;
// vergilendirilebilir gelir ve aile katsayısı hesaplaması
decimal revenu = 0.72M * salaire;
decimal QF = revenu / nbParts;
// vergi hesaplaması
tranchesImpot[tranchesImpot.Length - 1].Limite = QF + 1;
int i = 0;
while (QF > tranchesImpot[i].Limite) i++;
// sonuç döndürme
return (int)(revenu * tranchesImpot[i].CoeffR - nbParts * tranchesImpot[i].CoeffN);
}//hesapla
}//sınıf
}
- 2. satır: AbstractImpot sınıfı, IImpot arayüzünü uygular.
- 7. satır: Vergi hesaplamasına ilişkin yıllık veriler, korumalı bir alan biçimindedir. AbstractImpot sınıfı, bu alanın nasıl başlatılacağını bilmez. Bu görevi türetilmiş sınıflara bırakır. Bu nedenle, herhangi bir örneklenmesini engellemek amacıyla soyut olarak tanımlanmıştır (2. satır).
- satır 10-25: IImpot arayüzünün calculer yönteminin uygulaması. Türetilmiş sınıfların bu yöntemi yeniden yazması gerekmeyecektir. Böylece AbstractImpot sınıfı, türetilmiş sınıfların ortak özelliklerini barındıran bir sınıf işlevi görür. Bu sınıfa, tüm türetilmiş sınıfların ortak özellikleri yerleştirilir.
IImpot arayüzünü uygulayan bir sınıf, AbstractImpot sınıfından türetilerek oluşturulabilir. Şimdi bunu yapacağız:
using System;
namespace Chap2 {
class HardwiredImpot : AbstractImpot {
// vergi hesaplaması için gerekli veri tabloları
decimal[] limites = { 4962M, 8382M, 14753M, 23888M, 38868M, 47932M, 0M };
decimal[] coeffR = { 0M, 0.068M, 0.191M, 0.283M, 0.374M, 0.426M, 0.481M };
decimal[] coeffN = { 0M, 291.09M, 1322.92M, 2668.39M, 4846.98M, 6883.66M, 9505.54M };
public HardwiredImpot() {
// vergi dilimleri tablosunun oluşturulması
tranchesImpot = new TrancheImpot[limites.Length];
// doldurma
for (int i = 0; i < tranchesImpot.Length; i++) {
tranchesImpot[i] = new TrancheImpot { Limite = limites[i], CoeffR = coeffR[i], CoeffN = coeffN[i] };
}
}
}// sınıf
}// ad alanı
HardwiredImpot sınıfı, 7-9. satırlarda vergi hesaplaması için gerekli verileri sabit olarak tanımlar. Bu sınıfın oluşturucusu (11-18. satırlar), bu verileri kullanarak ana sınıf AbstractImpot'in korumalı alanı tranchesImpot'i başlatır.
Bir test programı şu şekilde olabilir:
using System;
namespace Chap2 {
class Program {
static void Main() {
// Etkileşimli vergi hesaplama programı
// kullanıcı klavyeden üç veri girer: evli nbEnfants maaş
// program daha sonra ödenecek vergi tutarını gösterir
const string syntaxe = "syntaxe : Marié NbEnfants Salaire\n"
+ "Marié : o pour marié, n pour non marié\n"
+ "NbEnfants : nombre d'enfants\n"
+ "Salaire : salaire annuel en F";
// bir nesne oluşturulur IImpot
IImpot impot = new HardwiredImpot();
// sonsuz döngü
while (true) {
// vergi hesaplaması için parametreler isteniyor
Console.Write("Paramètres du calcul de l'Impot au format : Marié (o/n) NbEnfants Salaire ou rien pour arrêter :");
string paramètres = Console.ReadLine().Trim();
// Yapılması gereken bir şey var mı?
if (paramètres == null || paramètres == "") break;
// giriş satırındaki argüman sayısının kontrolü
string[] args = paramètres.Split(null);
int nbParamètres = args.Length;
if (nbParamètres != 3) {
Console.WriteLine(syntaxe);
continue;
}//if
// parametrelerin geçerliliğinin kontrolü
// evli
string marié = args[0].ToLower();
if (marié != "o" && marié != "n") {
Console.WriteLine(syntaxe + "\nArgument marié incorrect : tapez o ou n");
continue;
}//if
// nbEnfants
int nbEnfants = 0;
bool dataOk = false;
try {
nbEnfants = int.Parse(args[1]);
dataOk = nbEnfants >= 0;
} catch {
}//if
// veri doğru mu?
if (!dataOk) {
Console.WriteLine(syntaxe + "\nArgument NbEnfants incorrect : tapez un entier positif ou nul");
continue;
}
// maaş
int salaire = 0;
dataOk = false;
try {
salaire = int.Parse(args[2]);
dataOk = salaire >= 0;
} catch {
}//try-catch
// veri doğru mu?
if (!dataOk) {
Console.WriteLine(syntaxe + "\nArgument salaire incorrect : tapez un entier positif ou nul");
continue;
}
// parametreler doğru - vergi hesaplanıyor
Console.WriteLine("Impot=" + impot.calculer(marié == "o", nbEnfants, salaire) + " euros");
// sonraki vergi mükellefi
}//while
}
}
}
Yukarıdaki program, kullanıcının vergi hesaplaması simülasyonlarını tekrar tekrar yapmasına olanak tanır.
- 16. satır: IImpot arayüzünü uygulayan impot nesnesinin oluşturulması. Bu nesne, IImpot arayüzünü uygulayan HardwiredImpot türünün bir örneğinin oluşturulmasıyla elde edilir. Burada, impot değişkenine HardwiredImpot türü değil, IImpot türü atandığını belirtmek gerekir. Bunu yazarak, impot nesnesinin yalnızca calculer yöntemiyle ilgilendiğimizi, geri kalanıyla ilgilenmediğimizi belirtmiş oluruz.
- 19-68. satırlar: vergi hesaplama simülasyonları döngüsü
- 22. satır: calculer yöntemine gerekli olan üç parametre, klavyeden tek bir satırda girilerek talep edilir.
- 26. satır: [chaine].Split(null) yöntemi, [chaine]'i kelimelere ayırmaya olanak tanır. Bu kelimeler, args adlı bir dizide saklanır.
- 66. satır: IImpot arayüzünü uygulayan impot nesnesinin calculer yöntemi çağrılır.
İşte programın çalıştırılmasına ilişkin bir örnek:









