C#’ta Interface (Arayüz) Kavramı

C#’ta arayüzler, sınıf ve struct yapılarının ne yapması gerektiğini belirten yapılardır. Bir arayüz; method, özellik (properties), olay (event) ve indexleyici (indexers) yapılarını içerebilir. Bir arayüzün içindeki metodların gövdeleri yoktur. Yani bir arayüz hiçbir zaman hiç bir şekilde bir uygulama sağlamaz. Bir arayüzü birden fazla sınıf ya da struct kullanabildiği gibi, bir sınıf ya da struct yapısı da birden fazla arayüz uygulayabilirler.

Arayüzler çoklu kalıtımı (multiple inheritance) desteklerler. Örneğin; aşağıdaki ICombobox arayüzü hem ITextBox hem de IListBox arayüzlerinden kalıtım alır:

interface IControl 
{ 
    void Boyama(); 
} 
interface ITextBox : IControl 
{ 
    void YazıBelirler(string text); 
} 
interface IListBox : IControl 
{ 
    void OgeBelirle(string[] items); 
} 
interface IComboBox : ITextBox, IListBox { }

Sınıflar ve struct yapıları da çoklu kalıtımı desteklerler. Aşağıda, EditBox sınıfı hem IControl hem de IDataBound arayüzlerinden kalıtım alır:

interface IDataBound 
{ 
    void Bind(); 
} 
public class EditBox : IControl, IDataBound 
{ 
    public void Boyama() { } 
    public void Bind() {}

}

Bir sınıf ya da struct belirli bir arayüzü uyguladığında, o sınıf ya da struct örneği, dolaylı olarak arayüz tipine çevrilebilir. Örneğin;

EditBox editBox = new EditBox(); 
IControl control = editBox; 
IDataBound dataBound = editBox;

Arayüzler aşağıdaki nedenlerden dolayı özeldirler;

  • Bir sınıf birden fazla arayüz uygulayabilir. Buna karşılık, bir sınıf sadece bir sınıftan kalıtım alabilir.
  • Arayüz üyelerinin tümü dolaylı olarak soyuttur (abstract). Buna karşılık, bir sınıf hem soyut hem de somut üyelerin uygulanmasına olanak sağlar.
  • Struct yapıların arayüzleri uygulayabilir. Buna karşılık, bir struct yapısı bir sınıf yapısından kalıtım alamaz.

Bir arayüz tanımlanması tıpkı sınıf tanımlamasına benzer, fakat bir arayüz kendi üyelerinin hepsi dolaylı olarak soyut oldukları için bunlara herhangi bir uygulama sağlamaz. Bu üyeler, bu arayüzü uygulayan sınıf ya da struct yapıları tarafından kullanılırlar.

System.Collections namespace’inde bulunan IEnumerator arayüzünün tanımı şu şekildedir;

public interface IEnumerator 
    { 
        bool MoveNext(); 
        object Current { get; } 
        void Reset(); 
    }

Bir arayüz üyesi her zaman dolaylı olarak public özelliktedir ve herhangi bir erişim değiştiricisi tanımlanamaz. Bir arayüzü uygulamak onun tüm elemanlarını public olarak kullanılmasına olanak sağlamak anlamına gelir.

internal class GeriSayim : IEnumerable 
{ 
    int sayac = 10; 
    public bool MoveNext() { return sayac-- > 0; } 
    public object Current { get { return sayac; } } 
    public void Reset() { throw new NotSupportedException(); } 

}

Dolaylı olarak bir nesneyi herhangi bir arayüz tipine çevirebilirsiniz;

IEnumerator e = new Countdown();

Bir arayüz başka bir arayüzden türetilebilir. Bu sayede türetilen arayüz diğer arayüzün tüm üyelerini kalıtım alır. Örneğin;

public interface IGeriAl 
     { 
         void GeriAl(); 
     } 
     public interface IYenidenYap : IGeriAl 
     { 
         void YenidenYap(); 
     }

Burada IYenidenYap, IGeriAl arayüzünün tüm üyelerini kalıtım alır.

Açık Arayüz Uygulaması (Explicit Interface Implementation)

      Birden fazla arayüz uygulaması bazen üye imzalarının çarpışması sonuçları doğurabilir. Bu durumu arayüz üyesine uygulanan

açıkça uygulama(explicitly implementing)

    şeklinde çözebilirsiniz. Şu aşağıdaki koda göz atalım;
class Test 
{ 
    interface I1 
    { 
        void Foo(); 
    } 
    interface I2 
    { 
        int Foo(); 
    } 
    public class Arac : I1, I2 
    { 
        public void Foo() 
        { 
            Console.WriteLine("I1.Foo uygulamasi"); 
        }

        int I2.Foo() 
        { 
            Console.WriteLine("I2.Foo uygulaması"); 
            return 23; 
        } 

    } 
}

Görüldüğü üzere, hem I1 hem de I2 arayüzlerinin çelişkili Foo imzaları var. Arac sınıfı, açık bir şekilde I2 arayüzünün Foo methodunu uygular. Bu durum bu iki methodun aynı sınıf içerisinde bir arada olmasına olanak verir. Bu üyelerin çağrımını ancak arayüz çevrimleri ile sağlayabiliriz:

Arac a = new Arac(); 
a.Foo();       // I1.Foo uygulanır. 
(I1)a.Foo(); // I1.Foo uygulanır. 
(I2)a.Foo(); // I2.Foo uygulanır.

Arayüz Üyelerini Sanal Uygulama (Implementing Interface Members Virtually)

Açıkça uygulanmış arayüz üyeleri default olarak sealed tanımlıdır. Temel sınıfta bu üyeler virtual ya da abstract olarak tanımlanmak zorundadır. Örneğin;

using System; 
using System.Collections; 
class Test 
{ 
    public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class Sınıf1 : IGeriAl 
    { 
        public virtual void GeriAl() 
        { 
            Console.WriteLine("Sınıf1.GeriAl()"); 
        } 
    } 
    public class Sınıf2 : Sınıf1 
    { 
        public override void GeriAl() 
        { 
            Console.WriteLine("Sınıf2.GeriAl()"); 
        } 
    } 
    public static void Main() 
    { 
        Sınıf2 s = new Sınıf2(); 
        s.GeriAl();                  // Sınıf2.GeriAl() 
        ((IGeriAl)s).GeriAl();  // Sınıf2.GeriAl() 
        ((Sınıf1)s).GeriAl();    // Sınıf2.GeriAl() 
    } 
}

Bir arayüz elemanını temel sınıftan doğrudan çağırmak ya da alt sınıftan arayüz çağrısı ile çağırmak bize aynı sonucu verir.

Altsınıflarda Arayüzü Tekrar Uygulama

Bir altsınıf, temel sınıf tarafından uygulanmış arayüz üyelerini tekrar tekrar uygulayabilir. Tekrar uygulama, üye uygulamasını kaçırır ve temel sınıftaki üyeler virtual olsun ya da olmasın çalışır. Aşağıdaki örnek ile daha iyi anlatmaya çalışalım;

Bu örnekte; TextBox sınıfı IGeriAl.GeriAl() methodunu açıkça uygular. Bu yüzden virtual olarak işaretlenemezler. Override etmek için, RichTextBox sınıfı IGeriAl.GeriAl() methodunu yeniden uygulamak zorundadır.

public interface IGeriAl 
    { 
        void GeriAl(); 
    } 
    public class TextBox : IGeriAl 
    { 
        void IGeriAl.GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        } 
    } 
    public class RichTextBox : TextBox, IGeriAl 
    { 
        public new void GeriAl() 
        { 
            Console.WriteLine("RichTextBox.GeriAl"); 
        } 

    }

Altsınıfın uygulamasında arayüz çağrısı olarak tekrar uygulanmış üyeleri çağırmak için;

RichTextBox r = new RichTextBox(); 
r.GeriAl(); //RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();  //RichTextBox.GeriAl

Peki ya TextBox sınıfımızı aşağıdaki şekilde değiştirirsek?

public class TextBox : IGeriAl 
    { 
        public void GeriAl() 
        { 
            Console.WriteLine("TextBox.GeriAl"); 
        }

    }

Bu özellik bize GeriAl() methodunu farklı bir şekilde çağırma olanağı sağlar.

RichTextBox r = new RichTextBox(); 
r.GeriAl();                   // RichTextBox.GeriAl 
((IGeriAl)r).GeriAl();    // RichTextBox.GeriAl 
((TextBox)r).GeriAl();  // TextBox.GeriAl

Bu üçüncü durum bize gösteriyor ki, bu durumda bir uygulama yapılması bir üyenin base sınıftan değil de direkt arayüz tarafından çağrılması durumlarında etkin hale gelir. Bu genellikle istenilmeyen bir durumdur.

Arayüzler ve Boxing

Bir struct yapısını bir arayüz yapısına çevirmek boxing işlemidir. Dolaylı olarak uygulanmış struct yapısını çağırmak boxing işlemi değildir.

         interface I 
        { 
            void Metod(); 
        }

        struct S : I 
        { 
            public void Metod() { } 
        }

        S str = new S(); 
        str.Metod(); //Boxing değil

        I i = S; 
        i.Metod(); //Arayüz yapısına çevirdiğimizde boxing gerçekleşir.

Sınıf vs. Arayüz Yazılması

Temel olarak;

  • Uygulama aşamasında doğal olarak paylaşıllan tipler için sınıf ya da alt sınıflar kullanın.
  • Bağımsız uygulamaya sahip tipler için arayüzler kullanın.

Aşağıdaki sınıflar göz önüne alalım;

    abstract class Hayvan { } 
    abstract class Kus : Hayvan { } 
    abstract class Bocek : Hayvan { } 
    abstract class Kusgiller : Hayvan { } 
    abstract class Etobur : Hayvan { }

    //Somut Sınıflar

    class DeveKusu : Kus { } 
    class Kartal : Kus, Kusgiller, Etobur { } //Geçersiz 
    class Arı : Bocek, Kusgiller { } //Geçersiz 
    class Pire : Bocek, Etobur { } //Geçersiz

Kartal, Arı ve Pire sınıfları derlenmezler çünkü birden fazla sınıftan kalıtım almaları yasaktır. Bunu çözmek için, bazı tipleri arayüz tipine çevirmek zorundayız. Tabi burada soru şudur, hangi tipleri? Genel kuralımızı takip ederek, Bocek ve Kus sınıfları uygulamalarını paylaşırlar. Bu yüzden bunlar sınıf olarak kalmak zorundadır. Buna karşılık, Kusgiller uçmak için bağımsız bir mekanizmaya sahiptir ve Etobur et yiyenler için bağımsız stratejilere sahiptir. Bu yüzden bu sınıfları arayüz tipine çevirmemiz gerekir.

interface IKusgiller { } 
interface IEtobur { }