Generics Nedir? (C#)
Generics yapı, oluşturduğumuz sınıfın, methodun, interface’in veya parametrenin, belirli bir tip için değil, oluşturduğumuz şablonun yapısına uyan her tip ile çalışmasına olanak sağlayan bir yapıdır. Böylece her seferinde tip belirtip tekrar tekrar method, class yazmamızı engeller ve daha kaliteli, yönetilebilir ve esnek bir kod yapısı kurmamızı sağlar. Yüzeysel olarak açıklamak istediğimizde, Generic’leri bu şekilde tanımlayabiliriz.
Endişelenmeyin! Bahsettiğimiz yapıyı Unity’de farkında olmadan kullanıyoruz. Vereceğimiz somut örnek problemler ve çözümleri ile bu yapıyı çok daha iyi kavrayacağız ve kod tekrarlarından ne kadar kaçındığımızı gördüğümüzde şaşıracaksınız.
Generics’leri Farkında Olmadan Nasıl Kullanıyoruz?
Unity’de GameObject’lerin üzerinde bulunan bileşenlere ulaşmanın en yaygın iki yolu vardır:
- [SerializeField] kullanıp inspector aracılığı ile gerekli bileşenin atamasını yapmak.
- GetComponent<> methodunu kullanmak.
İkinci maddeye bakalım. GetComponent<>() fonksiyonu, istediğimiz tipteki bi bileşene ulaşmamızı sağlayan bir fonksiyondur. Bu method, generics yapıda kurulmuştur. <> işaretlerinin arasına bileşenin türünü yazarak belirtilen bileşene ulaşabiliyoruz. Fakat böyle bir method olmasaydı, her bir tip için yeni bir method oluşturmamız gerekirdi.
Aynı şekilde listeleri düşünelim. Herhangi bir liste oluşturduğumuzda, <> kısmına listemizin içereceği türü belirtiyoruz. Böylece her seferinde liste oluşturmak için List<> yazmamız ve içine tipini belirtmemiz yeterli oluyor.
Ne Zaman Generics Kullanmalıyız?
Somut bir problem ile Generics kullanımını anlamaya çalışalım. Şöyle bir kurgu oluşturalım. Sahnemizde birçok türden düşmanlar var. Druid, Demon Hunter, Paladin. Bunların her biri Enemy class’ından inherit alıyor. Sahnemizde bulunan tüm düşmanları EnemyManager.cs adlı sınıfta bir listede tutalım ve atamasını inspectorden yapalım.
[SerializeField] private List<Enemy> enemiesInScene;
Bir silahımız olduğunu varsayalım, bu silahın sadece sahnede bulunan Druid türüne ait düşmanlara hasar vermesini istiyoruz. Bu türdeki objelere ulaşmanın birçok yolu var. Biz Generics’leri daha iyi anlamak için listeden Druid sınıfına ait olan objeleri bulup döndürelim.
[SerializeField] private List<Enemy> enemiesInScene;
private List<Druid> FindAllDruids()
{
List<Druid> druidList = new List<Druid>();
foreach (Enemy enemy in enemiesInScene)
{
if (enemy is Druid)
{
druidList.Add(enemy as Druid);
}
}
return druidList;
}
Her şey güzel gözüküyor. FindAllDruids() methodumuz bize listemizdeki tüm Druidleri döndürüyor. Peki şimdi ihtiyaç doğrultusunda sahnedeki tüm Paladin’leri bulmamız gerekti. Aynı işlemi yapalım.
private List<Paladin> FindAllPaladins()
{
List<Paladin> paladinList = new List<Paladin>();
foreach (Enemy enemy in enemiesInScene)
{
if (enemy is Paladin)
{
paladinList.Add(enemy as Paladin);
}
}
return paladinList;
}
Evet, FindAllPaladins() adında bir method daha oluşturduk ve listemizdeki Paladin türündeki düşmanları bulduk. Ve şimdi de Demon Hunter’lara ulaşmamız gerektiğini varsayalım. Tekrar tekrar bu methodları yazmak durumunda mıyız? Yazılabilir, ama bu clean-code prensiplerine aykırıdır. Her yeni düşman eklediğimizde, onun için tekrar bir uzun uzun method yazmamız gerekir. Sürekli bu şekilde ilerleyemeyiz. Bu durumda kırmızı alarm bizim için yanar. Tabii ki daha güzel ve iyi bir yol var. Çok daha kolay ve modüler yöntem olan Generics’leri burada kullanabiliriz.
Generics Yapı Nasıl Oluşturulur?
Generics dediğimizde T ve where anahtar kelimesi aklımıza gelmeli.
T harfi başta korkutsa da, for loop’ta bulunan “i” olarak sürekli kullandığımız bir placeholder harfinden başka bir şey değil. Type anlamına geldiği için genellikle T harfi kullanılıyor.
Where ise methodumuzun alabileceği türleri kısıtlamamızı sağlıyor. Örneğin GetComponent’ı incelediğimizde, tipini belirttiğimiz yere sadece Component türünde bir tip belirtebiliyoruz.
Rigidbody2D rb = GetComponent<Rigidbody2D>(); // sadece component türünde yazabiliriz. Rb bir component.
Kendi verdiğimiz örnekte ise devamında Enemy olarak kısıtlıyoruz çünkü zaten sadece Enemy tipindeki objelere ihtiyacımız var. Haydi şimdi az önce yaşadığımız tekrar tekrar method oluşturma sorununu Generics ile çözmeye çalışalım.
private List<T> FindAllEnemiesByType<T>() where T : Enemy
{
List<T> enemyList = new List<T>();
foreach (Enemy enemy in enemiesInScene)
{
if (enemy is T)
{
enemyList.Add(enemy as T);
}
}
return enemyList;
}
Hadi biraz kodu inceleyelim. Listenin tipini T olarak belirledik. Bize T tipi içeren bir liste döndürecek. FindAllEnemiesByType<T> şeklinde bir yapı yazdık. Bu methodu kullandığımızda, hangi düşman türünden objelere ulaşmak istiyorsak içine tipini yazmamız yeterli olacak. Gördüğümüz üzere T tipini sadece Enemy olması için kısıtladık. Bu bağlamda tipini sadece Enemy türünde yazabileceğiz, yazmaz isek hata alırız.
Daha detaylı incelediğimizde, aynı parametre kullanır gibi, tür kullanabileceğimiz yerlerde T kullandık. Örneğin birden çok method oluşturduğumuz durumlarda, her birinde ayrı olarak o türlerde listeler oluşturduk. (Druid, Paladin listesi gibi.) Fakat burada sadece T olarak oluşturmamız yetti. Methodu kullandığımızda tipini ne belirttiysek, T olan yerlere o belirttiğimiz tip gelmiş olacak. Kısaca bir method şablonu oluşturduk ve belirttiğimiz yerlere, istediğimiz türü assign edebilir hale geldik.
var paladins = FindAllEnemiesByType<Paladin>(); // paladinleri döndürür.
var demonHunters = FindAllEnemiesByType<DemonHunter>(); // demon hunterları döndürür.
İşte birden çok method oluşturmak yerine tek bir methodu birden çok kez kullanarak daha kullanışlı ve tekrar kullanılabilir, sürdürülebilir halde getirdik. Örnek problemimiz doğrultusunda Generics Methodları kullandık. Bunları class olarak, interface olarak da kullanabiliriz. Umarım Generics’ler kafanızda daha net bir şekilde oturmuştur.
Generics ile ilgi daha fazla bilgi edinmek için şunlara göz atabilirsiniz:
https://www.tutorialsteacher.com/csharp/csharp-generics
https://www.youtube.com/watch?v=7VlykMssZzk&t=1s&ab_channel=CodeMonkey
https://www.srdrylmz.com/c-generic-siniflar-metotlar-ve-arayuzler/
[…] kullanarak extension method oluşturmaktır. Eğer generics ile ilgili soru işaretleriniz varsa buradan generics ile ilgili yazıma göz […]