Uygulamaların hem fonksiyonel hem de ara yüz olarak genişletilebilir olması bir ihtiyaçtır. Fakat genelde uygulamalar tek parça halinde yazılırlar ve tak-çalıştır mantığına sahip bir genişletilebilme yeteneği sunmazlar. System.ComponentModel.Composition isim uzayında bu genişletilebilirlik problemi için isteyen her uygulamanın kullanabileceği basit bir genişletilebilir uygulama çözüm alt yapısı sunulmaktadır. Alt yapı çalışma anında uygun genişleletme parçalarını bulur ve yükler. Ayrıca alt yapı yüklenen parçaların yaşam döngülerini de takip eder.
System.ComponentModel.Composition ile birlikte uygulamalar için Katalog (Catalog) ve taşıyıcı (Container) kavramları gelmektedir.
Katalog sadece uygulama genelinde ki dağıtım(Export) tanımlarını saklamaktadır. Yani uygulamanın ihtiyaç duyduğu bir nesnenin nereden ve nasıl oluşturulacağının cevabını katalog vermektedir. Katalog çalışma zamanında kendisini güncelleyen bir yapıdadır. Belirtilen dizinde ki dll’ler içinden dağıtım tanımlarını toplayabilmektedir. Dizin içinde ki dll’ler değiştikçe katalogda kendisini güncellemektedir.
Taşıyıcı ise parçaları(Part) bir araya getiren kısımdır. Her bir parça kodlanırken uygulama geneline ithal etmek istediği (Export) tanımları ve uygulamadan almak istediği (Import) ihraç tanımları söylemektedir. Taşıyıcı çalışma anında parçaları oluşturur ve kullanımda olduğu sürece saklar.
Taşıyıcı ve katalog arasında çalışan fakat bizim görmediğimiz ExportProvider sınıfı vardır. ExportProvider sınıfı parçaları oluşturan sınıftır.Taşıyıcı ve katalog ile konuşarak kendisinden istenen parçayı üretmektedir.
Ortamı Hazırlama
Katalog Oluşturma
Uygulama ilk olarak tip tanımlarını saklayacağı bir tip katalog oluşturmalıdır. En sık kullanılan kataloglardan ilk AssemblyCatalog’dur. AssemblyCatalog verdiğiniz assembly dosyasını taramakta ve parça kontratlarını saklamaktadır.
[cs]
var assemblyCatalog = new AssemblyCatalog (Assembly.GetExecutingAssembly ());
Başka bir katalog türü ise DirectoryCatalog’dur. DirectoryCatalog ise verilen dizinde ki tüm assembly dosyalarını taramakta ve bulduğu tüm parça taşıma kontratlarını saklamaktadır.
[cs]
var directoryCatalog = new DirectoryCatalog (Path .Combine (".." , "Plugin" ));
Birden çok katalogu AggregateCatalog ile bir arada kullanarak daha geniş bir parça taşıma kontratı havuzuna sahip olabilirsiniz.
[cs]
var aggregateCatalog = new AggregateCatalog (assemblyCatalog, directoryCatalog);
Bu üç katalog dışında daha az kullanılan SilverLight’a özel DeploymentCatalog ve sadece parametre olarak verilen nesneler için tip katalogu oluşturan TypeCatalog sınıfları da mevcuttur.
Taşıyıcı Oluşturmak
Parçalarımızın program içinde nasıl hareket edeceğini gösteren kontratlarımızı barındıran katalogumuzu oluşturduk. Şimdi sıra katalogu kullanacak olan taşıyıcıyı oluşturmaktadır. Taşıyıcıyı iki şekilde oluşturabilirsiniz. Eğer taşıyıcıya hiçbir katalog vermeden oluşturursanız taşıyıcı varsayılan olarak katalogsuz olarak oluşturacaktır. Fakat katalog olmadığı için parçaları oluştururken gerekli tüm parçaları doğrudan taşıyıcıya eklemeniz gerekecektir. Burada dikkat edilmesi gereken husus taşıyıcı [Export] [Import] niteliklerini sahip parçalar ile çalışabilmektedir.
[cs]
var compositionContainer = new CompositionContainer ();
Katalogsuz taşıyıcı kullanma durumu hemen hemen hiç kullanılmamaktadır. Genelde katalogsuz olarak taşıyıcı oluşturulmamaktadır. Eğer kendinize ait bir katalog oluşturdunuzsa taşıyıcının bu katalog ile çalışmasını isteyebilirsiniz.
[cs]
var aggregateCatalog = new AggregateCatalog (assemblyCatalog, directoryCatalog);
var compositionContainer = new CompositionContainer (aggregateCatalog);
Parçalar ve Kontratlar
Katalog ve taşıyıcıyı oluşturduktan sonra sıra uygulama parçalarını oluşturmaya geldi. [Export] ve [Import] niteliğine sahip tüm sınıflar, ara yüzler, değişkenler, metotlar program parçası sayılmaktadır. [Export] ve [Import] nitelikleri ile parçalar taşıyıcıya uygulama içinde nasıl hareket edeceğini yani taşınma kontratlarını belirtmektedir.
Dışa Aktarım
Her hangi bir parçaya sadece [Export] niteliği ekleyerek dışa aktarılabilir olmasını sağlayabilirsiniz.
[cs]
// 1- Basit dışa aktarım
[Export ]
public class SimpleContractExport
Sınıfınızı üst sınıfın bir örneği olarak da dışa aktarabilirsiniz.
[cs]
// 2- üst sınıfı ile dışa aktarım kontratı
[Export (typeof (SimpleContractExport ))]
public class SimpleDrivenTypeExport : SimpleContractExport
Sınıfınızı özel bir etiket belirterek de dışarı aktarabilirsiniz. Bu durumda aynı etiket ile çağrıldığında bu dışa aktarım kontratı çalışacaktır.
[cs]
// 3- etiket ile dışa aktır kontratı
[Export ("SimpleTagedTest" )]
public class SimpleTagedTypeExport
Dışarı aktarmak istediğiniz bir ara yüzün uygulaması da olabilmektedir.
[cs]
// 4- bir arayüz uygulması olarak dışa aktarma
[Export (typeof (ISimpleInterface ))]
public class InterfaceContractExport : ISimpleInterface
Her zaman bir sınıfı aktarmak zorunda değilsiniz. Diğer program bileşenleri de dışarı aktarılabilmektedir. Her hangi bir property dışarı aktarılabilmektedir.
[cs]
// 5- bir property olarak dışa aktarım kontratı
[Export ("NextString" )]
public string NextStringProperty
Yukarıdaki örnekte bir etiket verilmiştir çünkü bir string tipi dışarı aktarılmaktadır. Birçok string tipi arasında bulabilmek için bir etiket verilmiştir. Genelde property olarak aktarımlar Factory Pattern uygulamak için kullanılmaktadır.
[cs]
// 5- bir property olarak dışa aktarım kontratı
[Export ]
public ISimpleInterface SimpleInterfaceFactory
{
get
{
return new OtherInterfaceContractExport ();
}
}
Tam bu noktada nesnelere police enjekte etmek için ara bir çözüm bulunmaktadır. Eğer uygulamanızda ki sadece birkaç nesne üzerinde “Policy Injection” kullanıcaksanız property olarak aktarım işinize yarayacaktır.
[cs]
// 5- bir property olarak dışarı aktarım
// ve özel bir sınıfta policy injection kullanımı
[Export ]
public ISimpleInterface SimpleInterfaceFactoryWithPI
{
get
{
return RoC.PI.ProxyCreator
.CreateObject ();
}
}
Sadece property’leri değil event’larıda dışarı aktarabilmektesiniz.
[cs]
// 6- bir metod olarak dışa aktarım kontratı
[Export (typeof (Func))]
public string GetNext ()
Olayları uygulama genelinde paylaşırken dikkatli olmak gerekmektedir. Zira bir olayı dışarı aktarmak için taşıyıcı önce olayı barındıran sınıfı oluşturacaktır. Olayı barındıran sınıf olaya bağlanan tüm delegeler yok olduğunda yok edilecektir. Fakat olaya bağlanan delegelerin olduğu sınıflarda yok edilmek içinde delegelerin yok edilmesini bekleyecektir. Yani uygulamanızda gereksiz yere bir birini bekleyen nesneleriniz kalacak ve bunlar yok edilmeyecektir. Bu durumda kullanılacak WeakReference , WeakEventPattern ve EventAggregator gibi yöntemler bulunmaktadır. Başka bir yazıda açıklamak üzere uygulama genelinde olay paylaşımının tehlikesinden bahsedip geçmiş olalım.
Bazen de bir üst sınıf veya ara yüze ait tüm alt sınıfların dışarı aktarılabilir olmasını istersiniz. Bu durumda üst sınıfınızı işaretleyerek tüm alt sınıflarınızın taşınabilir olmasını sağlayabilirsiniz.
[cs]
// 7- bir üst sınıf olarak dışa aktarım kontratı
[InheritedExport ]
public interface INext { .. }
public class NextClass1 : INext { .. }
public class NextClass2 : INext{ .. }
Böylelikle kullanabileceğimiz dışa aktarım kontratlarını inceledik. Hafızada yer tutan tüm programlama öğelerini dışarı aktarabiliriz. Aktarırken bir etiket verebilir veya program öğesinin tipi ile değil de dönüşebileceği her hangi bir tip ile aktarabiliriz.
İçe Aktarım
Tabii ki dışarı aktardığımız program parçalarının diğer program parçaları tarafından içeri aktarılması gerekmektedir. Dışa aktarıma benzer şekilde içe aktarım yolları vardır.
Nesnenin oluşması sırasında Cunstructor parametresi olarak diğer parçaları ithal edebilirsiniz. Aşağıdaki örnekte SimpleImport nesnesi oluşturulurken taşıyıcı InterfaceContractExport tipine ait bir dışa aktarım kontratı olup olmadığına bakar. Bulduğu InterfaceContractExport kontratı ile eğer taşıyıcı üzerinde nesne örneği yoksa önce aktaracağı InterfaceContractExport nesnesini oluşturur. Taşıyıcıda içe aktarılacak InterfaceContractExport nesne örneğini alır ve SimpleImport sınıfına constructor parametresi olarak gönderir.
[cs]
// 1- constractor parametresi olarak kontratları içe aktarım
[ImportingConstructor ]
public SimpleImport (InterfaceContractExport simpleInterfaceImplemantation)
Her zaman ithal etmek istediğiniz parça için bir dışa aktarım kontratı olmayabilir. Eğer ithal etmek istediğiniz parça var ise parçayı ithal etmeniz gerekmektedir.
[cs]
// 2- opsiyonel içe aktarı
// eger import edilen parça var ise getir yoksa Default(T) değerini getir
[ImportingConstructor ]
public SimpleImport ([Import (AllowDefault = true )]INext next)
İçe aktarımı property olarak da kullanabilirsiniz. Nesne create edildikten sonra property aktarımları atanacaktır.
[cs]
// 3- Property olarak kontratları içe aktarım
[Import ]
public SimpleContractExport Simple { get ; set ; }
Property içe aktarımla aynı şekilde değişkenlerde de içe aktarım yapılabilmektedir.
[cs]
// 4. Field olarak içe aktarım
// etiketleri tüm aktarımda kullanabilirsiniz
[Import ("SimpleTagedTest" )]
private SimpleTagedTypeExport _simpleTagedTypeExport ;
Eğer içe aktarmak istediğiniz parça taşıyıcı üzerinde birden çok varsa bunların hepsini birden alabilirsiniz. Bir sonra ki yazıda taşıyıcı üzerinde ki parçalar üzerinde sorgular çalıştırıp istediğiniz özel parçaları bulmayı inceleyeceğiz. Şimdilik belirli bir tipte ki tüm parçaları alalım.
[cs]
// 5 -kontratları çoklu içe aktarım
[ImportMany ]
private INext [] _nextArray ;
Bu şekilde parça paylaşan programlarda en büyük ihtiyaçlardan biriside her hangi bir parçanın ilk erişildiğinde oluşturulmasıdır. Yani içe aktardığınız parçaya ait bir metot veya property erişimi yapana kadar parça oluşturulmayacaktır. Parçayı kullanan sınıf ilk ne zaman erişim sağlarsa o zaman içe aktarılan parça oluşturulacaktır.
[cs]
// 6 -kontralları gevşek bağlama (lazyload ) ile içe aktarım
// ilk erişeme kadar nesneyi taşıyıcı üzerinden almaz.
// ilk erişimde nesne taşıyıcıdan getirilir
[Import ]
public Lazy SimpleLazy { get ; set ; }
public void UseLazy ()
{
// SimpleLazy.IsValueCreated == false
// ilk erişimi yapılıyor bu kod satırı çalışana
// ISimpleInterface nesne örneği oluşturulmuyor
SimpleLazy .Value .GetExecutingAssemblyName ();
// SimpleLazy.IsValueCreated == true
}
Basit Genişletilebilir Uygulama
Basit bir uygulama ile genişletilebilir (composite) özelliklerini test edelim. Öncelikle katalogu olmayan bir taşıyıcıyı nasıl kullanacağımızı görelim:
[cs]
[Export ]
public class ParameterTest1
{
public int Number1 { get { return 10; } }
}
public class ParameterTest2
{
[Export ("ParameterTestNumber" )]
public int Number2 { get { return 11; } }
}
public class ContainerWithOutCatalog
{
public ContainerWithOutCatalog ()
{
// katalog olmadan taşıyıcı oluştur
var container = new CompositionContainer ();
// this içinde ki [Import] ları parametrelerle doldur
container.ComposeParts (this ,new ParameterTest1 (), new ParameterTest2 ());
Console .WriteLine (
"ContainerWithOutCatalog.ParameterTest.Number1:{0}" ,
ParameterTest .Number1 );
Console .WriteLine (
"ContainerWithOutCatalog._numberTest:{0}" ,
_numberTest );
}
[Import ]
public ParameterTest1 ParameterTest { get ; set ; }
[Import ("ParameterTestNumber" )] private int _numberTest ;
}
Yukarıda ki kodlar sorunsuz şekilde çalışmaktadır. Dikkat edilmesi gereken [Import] yapılan tüm parçaların ComposeParts fonsiyonuna parametre olarak vermek durumundasınız. Çünkü gerekli parçalara ait [Export] kontratlarını alabilecek bir kataloga sahip degilsiniz.
Kataloga sahip bir taşıyıcı ile çalışmayı görelim:
[cs]
public class ContainerWithCatalog
{
public ContainerWithCatalog ()
{
var assemblyCatalog = new AssemblyCatalog (GetType ().Assembly );
var compositionContainer = new CompositionContainer (assemblyCatalog);
compositionContainer.ComposeParts (this );
//üüm [Export] ve [Import]ıı y dene
Console .WriteLine ("ContainerWithCatalog.ImportSimple:{0}" ,
_simpleImport .SimpleContractImport .Number ());
Console .WriteLine (
"ContainerWithCatalog.ImportAllowDefault:{0}" ,
_simpleImport .NonExported == default (INext ) ? "default" : "non default" );
Console .WriteLine ("ContainerWithCatalog.ImportDrivenType:{0}" ,
_simpleImport .DrivenTypeImport .Number ());
Console .WriteLine (
"ContainerWithCatalog.ImportWithLabel:{0}" ,
_simpleImport .GetSimpleTagedTypeExport2 ());
Console .WriteLine (
"ContainerWithCatalog.ImportingConstructor:{0}" ,
_simpleImport .SimpleInterface .Number ());
Console .WriteLine (
"ContainerWithCatalog.ImportProperty:{0}" ,
_simpleImport .NextStringProperty );
Console .WriteLine (
"ContainerWithCatalog.ImportEvent:{0}" ,
_simpleImport .NextNumber .Invoke ());
Console .WriteLine (
"ContainerWithCatalog.ImportMany:{0}" ,
_simpleImport .WriteAllNext ());
}
[Import ] private SimpleImport _simpleImport ;
}
Uygulamanın çıktısı şu şekilde görülecektir: