Nesneler Dünyası

VBA konularının başında nesne kavramına biraz girmiş ve Excel'in nesne modelinden bahsetmiştik. O bölümü okumayanların önce orayı okumasını tavsiye ederim. Bu bölümde ise nesnelere biraz daha yakından bakıcaz. Burada Türkçe'nin avantajını kullanarak bu kavrama Türkçe adıyla hitap edicem, zira bir de değişken tipi olarak Object var elimizde. İkisi birbirine karışmasın diye genel nesne kavramını Nesne olarak, tip olanı ise Object olarak belirticem.

Nedir?

Nesnelerin ne olduğuna bakmadan önce nesnelerin ne olmadıklarına bakalım. Basit değişkenlerle nesnelerin birbirinden çok temel bir farkı vardır. Basit değişkenlerin tek bir amacı vardır: Bir değer depolamak.

                    
                            Dim i As Integer
                            Dim ad As String

                            i = 10
                            ad = "Volkan"
                                    
                                

Nesneler ise, bir değer depolamaktan daha fazlasını yaparlar. Nesneler, hem çoklu veri tutarlar hem de bir eylem icra ederler.

                    
                                Dim ws As Worksheet
                                Set ws = Activesheet
                                ws.Name = "Kredi" 'veri
                                Debug.Print ws.Index 'veri
                                ws.Add 'eylem
                                        
                                    

Nesnelerin bileşenleri

Artık bildiğiniz üzere, Excel'de herşey bir nesnedir, hatta nesneler topluluğu olan collectionlar(sonu "s" ile biten) da nesnedir(Ör:Workbook da nesne, Workbooks collection'ı da).

Bu nesnelerin bazıları sadece veri tutarlar, bu verilere özellik(property) denir, bazı nesneler belirli eylemleri(metod) de icra ederler. Bazıları ise ayrıca kendileriyle ilgili bir eylem olduğunda bir olay(event) meydana getirirler.

Nesne kavramı olmasaydı herşeyi değişkenlerle yönetmemiz gerekirdi ki bu inanılmaz karmaşık bir dünyaya neden olurdu. Düşünsenize, 30 propertysi olan bir obje içi 30 ayrı değişken tanımlamanız gerekirdi, ki bu sadece değişken tanımlamayla ilgili endişemiz, diğer dezavantajlarını sanymıyorum bile.

NOT:VBA, tam anlamıyla bir Nesne Yönelimli Programlama(OOP) dili değildir, ama bu kavramı destekler. Excelent eklentisini yazdığım dil olan VB.NET ise tam bir OOP dilidir.

Property'ler

Bazı propertyler Readonly'dir(salt okunur), yani bunlara değer atayamazsınız, bazıları ise hem okunabilir hem yazılabilirdir.

                    
                            MsgBox ActiveCell.Address 'bu readonlydir
                            ActiveCell.Value = 1 'bu hem okunur hem yazılabilirdir.
                            MsgBox ActiveCell.Value
                                    
                                

Property'lere değer atamak, eğer dönüş değerleri basit data tiplerindeyse, aynı bunlar gibi atanır. i=1 ile ActiveCell.Value = 1 örneğindeki gibi

Ancak dönüş değeri nesne olan propertylere nesne atamalarındaki Set ifadesi ile atama yaparız.

                    
                            Set ilkkolon = ActiveCell.CurrentRegion.Columns(1)
                                    
                                

Metodlar

Metodlar, nesnelerin eylem icra eden üyeleridir. Sub olarak da Function olarak da tanımlanmış olabilirler. Mesela Workbook nesnesinin Add metodu bir Function'dır, zira geriye birWorkbook nesnesi döndürür. Ancak Save metodu bir Sub'dır, zira geriye birşey döndürmez, sadece bir eylem icra eder.

Eventler

Belli nesnelerin de belirli eylemler olduğunda meydana gelen olayları vardır. Bu konuyu olaylar bölümünde genişçe ele almıştık.

Nesne türleri

Ben nesneleri 4 gruba ayırıyorum(bu sınıflama tamamen bana aittir, resmi bir gruplama değildir)

İlk grupta Excelin nesne modelindeki nesneler var. Range, Cell, Worksheet gibi. Bunlar Excel librarysinde yer alırlar. Tümüne buradan ulaşabilrisiniz

post-thumb

İkinci grupta, default gelen librarylerdeki nesneler bulunuyor. Yani ilave herhangi bir library'yi reference olarak eklemeden yaratacağımız nesneler. Collection gibi; bu nesne VBA librarysinde bulunur.

post-thumb

Üçüncü grupta, bir library ekleyerek yaratılan nesneler var. Dictionary, FileSystemObject gibi. Bunlar da Scripting Runtime library'sinde bulunurlar.

post-thumb

Excel'in nesne modeli dışında kendi nesnelerimizi de yaratabiliriz, bunlar da dördüncü grup oluyor. Tabi bunun için önce bu nesnenin taslağını oluşturan Class yaratmamız gerekiyor. Classlara bu bölümde değinmeyeceğiz, onlarla ilgili bilgiye buradan ulaşalabilirsiniz.

Nesne üyelerine erişmek

Klasik yöntem

Bir nesne üyesine erişmek için en bilinen yol, nesne adını yazıp sonra "." koymaktır. Ör: Workook.Name

With - End With

Bir diğer yöntem de daha önce gördüğümüz With kalıbı. Bu kalıbı şurada anlatmıştık, sadece kısa bir örnek verelim.

                    
                                Dim fd As FileDialog
                                Set fd = Application.FileDialog(msoFileDialogFolderPicker)
                                With fd
                                  .Title = "Klasör seçin"
                                  If .Show = True Then
                                    lblKlasör.Caption = .SelectedItems(1)
                                  End If
                                End With
                                        
                                    

Me

Bir class modülündeyken(workbook, worksheet modülleri dahil, veya kendinize ait bir calass) kendisine Me ifadese ile erişebiliyorsunuz. Burada unutulmaması gereken şudur; Me, her zaman o an içinde bulunulan classa başvurur. Ör: Thisworkbook modülündeyken: Me.Save

Genel tanımlama ve yaratım teknikleri

New ifadesi

Excel nesne modelinde bulunan nesneleri tanımlarken New ifadesini kullanmayız. Zira Excel açıldığında bunlar otomatikman yaratılmış olurlar, o yüzden sadece değişken tanımlamak yeterlidir.

Bunlara atama yapmak için ise Set ifadesini kullanıyoruz.

                    
                            Dim rng As Range
                            Dim ws As Worksheet

                            Set rng = ActiveCell
                            Set ws = ActiveSheet
                                    
                                

Excel nesne modeli dışındaki nesneleri yaratmak için ise New ifadesini kullanmak zorundayız. Bu şekilde nesne yaratmanın da iki yolu vardır.

Yöntem 1:Tek satırda

İlk yöntemde Dim ve New ifadelerini aynı satırda kullanırız, yani tanımlama ve yaratım aynı anda olur. Sonra nesnenin üyelerini hemen kullanmaya başlayabilirz. Aslında yaratım aynı anda olmamaktadır, bu nesne ilk nerede görülürse işte o sırada yaratım olmaktadır.

                    
                    Dim coll As New Collection
                    coll.Add "elma"
                            
                        
Yöntem 2:Ayrı satırlarda(Set'li yöntem)

Bu yöntemde ise tanımlama ile yaratım&atama ayrı satırlarda gerçekleşir. Tanımlama Dim ile, yaratım&atama Set ve New ile yapılır.

                    
                    Dim coll As Collection 'Tanımlama
                    Set coll = New Collection 'Set ile atama, New ile yaratım
                            
                        
İstisna

Eğer ki, elde edeceğimiz nesneyi bir fonksiyon veya metod ile elde edeceksek o zaman New ifadesi kullanılmaz.

                    
                    Dim coll As Collection 'Tanımlama
                    Set coll = New Collection 'Set ile atama, New ile
                            
                        

Başka bir örnek de veritabanı işlemlerinden olsun. Aşağıdaki iki nesne için de New gerekmedi.

                    
                        Dim db As Database
                        Dim rs As Recordset
                        Set db = OpenDatabase("...") ' Veritabanı dosya yolunu buraya ekleyin
                        Set rs = db.OpenRecordset("...") ' SQL sorgusunu veya tablo adını buraya ekleyin
                                
                            
Hangi yöntem ne zaman kullanılır?

İkinci yöntem performans yönetimi açısından tercih edilir.Aslında günümüz bilgisayarları açısından bakıldığında buradaki performans etkisi artık ihmal edilebilir düzeydedir. O yüzden iki yöntem de kullanılabilir. Ancak belli özel durumlarda ikinci yöntemin kullanılması tavsiye edilir.

Eğer, tanımladığımız değişkeni belli bir duruma/şarta göre yaratmamız sözkonusu ise tek satırda değil, iki satırda yani Set'li yöntemle tanımlarız.

Mesela mail gönderimi yapılacak bir durum düşünelim. Eğer B2 hücresinde bir değer varsa o zaman mail gönderilsin, yoksa gönderilmesin. O yüzden ilk başta tanımlamayı yapalım, ama henüz nesneyi yaratmamıza gerek yok.

                    
                        Dim oApp As Outlook.Application

                        If Not IsEmpty(Range("F2")) Then
                           Set oApp = New Outlook.Application 
                        End If
                                
                            

Burada, ilgili nesne bir değişkene atanana kadar onu yaratmaz. Diğer yöntemle farkını görmek için aşağıdaki iki kodu çalıştırıp kendiniz görün, gerçi ben yazılacak değerleri yanlarında belirttim, ama kendinizin de görmesinde fayda var.(Outlook libraraysini eklemeyi unutmayın)

                    
                        Sub setliyöntem()
                        Dim oApp As Outlook.Application
                        Dim coll As Collection

                        Debug.Print TypeName(coll) 'Nothing
                        Debug.Print coll.Count 'hata

                        Debug.Print TypeName(oApp) 'Nothing
                        Debug.Print oApp 'hata alınır

                        If Not IsEmpty(Range("F2")) Then
                           Set oApp = New Outlook.Application
                        End If

                        End Sub
                        --------------------
                        Sub teksatıryöntemi()
                        Dim oApp As New Outlook.Application
                        Dim coll As New Collection

                        Debug.Print TypeName(coll) 'collection
                        Debug.Print coll.Count '0

                        Debug.Print TypeName(oApp) 'Application
                        Debug.Print oApp 'Outlook

                        End Sub
                                
                            

Tek satırda kullanım yönteminin avantajı ise, ilgili değişkeni Nothing ile yok etseniz bile tekrar kullanabiliyor olmanızdır. Mesela şu kod problemsiz çalışır.

                    
                            Sub coll1()

                            Dim coll As New Collection

                            coll.Add "Apple"
                            Set coll = Nothing
                            coll.Add "Pear" 'yeni bir collection yaratılır

                            End Sub
                                    
                                

Ancak aynı kodu Setli yönteme çalışıtırırsak hata alırız.

                    
                            Sub coll2()

                            Dim coll As Collection
                            Set coll = New Collection

                            coll.Add "Apple"
                            Set coll = Nothing

                            coll.Add "Pear" 'hata

                            End Sub
                                
                                

Early ve Late Binding

Bir diğer nesne yaratım yöntemi ise CreateObject ile yaratımdır, ki buna LateBinding yöntemi ile yaratım denir.

Bu yönteme daha çok, VBA'nin default libraryleri olan VBA, Excel ve Office libraryleri dışındaki librarylerde bulunan classlardan nesne yaratmak istediğimizde başvururuz. En sık kullanılan classlar şunlardır:

  • Scripting.Runtime librarysi içindeki FileSystemObject
  • Yine Scripting.Runtime librarysi içindeki Dictionary
  • Outlook, Word gibi diğer Ofis uygulamaları

Syntax'ı şu şekildedir: CreateObject("library.class")

Değişken tanımlamayı Object tipli yapıp sonra da Set ile atama ve yaratmayı yaparız.

                    
                        Dim obj As Object 'tanımlama
                        Set obj = CreateObject("Outlook.Application") 'yaratma ve atama
                        Set obj = CreateObject("Scripting.Dictionary")
                        Set obj = CreateObject("Scripting.FileSystemObject")
                            
                            

NOT: Nasıl olsa LateBinding yapıyorum, o yüzden değişken tanımlamaya gerek yok diye düşünmeyin. Zira object tipli değişkenler hafızda 4 byte yer işgal ederken, değişken tanımlamadığınız durumda bunlar otomatikman Variant algılanacakları için hafızada 16 byte işgal ederler.

Early Binding'te ise tanımlamak istediğimiz nesnenin bulunduğu library'yi Tools>References menüsünden eklememiz gerekir.

post-thumb

Library'yi ekledikten sonra artık klasik yoldan değişken tanımlayabiliriz, nesneyi doğru yazma konusunda Intellisense bize yardımcı olur.

post-thumb

veya bunu 2 satırlık versiyonla da yapabiliriz.

                    
                        Dim dict As Dictionary
                        Set dict = New Dictionary
                            
                            

Bu örnekte olduğu gibi aynı classtan başka bir library içinde yoksa library adını yazmamıza gerek yoktur, aksi halde karışıklık olur. Böyle bir durumda library adını da belirtmeliyiz.(Veritabanı işlemlerinde bu durumu görüyoruz)

post-thumb

Early Binding vs Late Binding

  • Late Bindingin avantajı, yazdığınız kodu bir başkasına gönderdiğinizde(veya network üzerinden çalıştırdıklarında) onda da kesinlikle sorunsuz çalışacağını biliyor olmanızdır. Zira herhangi bir kütüphane eklenmesi durumu olmadığı için versiyon farkları problem yaratmayacaktır. Early bindingte ise siz Office 2016 ile çalışıyorken, diğer kişi Office 2013 ile çalışıyorsa sizin eklediğiniz Outlook 16.0 reference'ini bulamayacağı için hata alacaktır. O yüzden eğer yaptığınız çalışmayı başka birinin bilgisayarında çalıştırma durumu varsa Late binding kullanmanız faydalı olackatır, aksi halde Early binding kullanın.
  • Early bindingle çalışmanın avanatjı ise intellisenseten faydalanmaktır. Bu hem daha hızlı hem de hatasız kod yazmanızı sağlayacaktır. LateBinding'te yazdığınız kod Intellisense ile teyit edilmemiş olacağı için tam çalışmadan önce birkaç kez derleme hatası almanız olasıdır.
  • Early bindingle çalışmanın bir diğer faydası da kodun çalışma hızıdır. VBA, ilgili nesnenin ne olduğunu direkt bileceği için arka planda bir dönüştürme işlemi yapmasına gerek kalmayacak ve de kod hızlı çalışcaktır. Zaten Early denmesinin sebebi de budur, öncende/erkenden hangi classla çalışacağımıza karar vermiş oluyoruz. Latebindingte ise sonradan/gecikmeli bir tespit işlemi de olacağı için kod daha yavaş çalışacaktır. Ortalama hız farkı 2 kattır.
  • Bazı nesneler Late bindingle ile yaratılamaz. Mesela CreateObject("DAO.Database") gibi bir kullanım sözkonusu değildir. Bu yöntemin kullanımı için ilgili nesnelerle ilgili bazı teknik önayarların yapılmış olması gereklidir. Burada ve burada teknik detaylar yazılı.

Bu sayfaadan çok daha teknik detaylarına ulaşabilirsiniz.

Benim nihai önerim şudur: Intellisense ve performans sebepleriyle her zaman Early Bindingle başlayın, başkasının bu dosyayı çalıştırması da sözkonusu olacaksa, Early binding olan yerleri latebindige çevirin.

NOT: Bir de GetObject diye bir fonksiyon vardır. "Mevcutta açık olan bir uygulama varken CreateObject ile o nesneyi tekrar yaratmanın anlamı yok, ona GetObject ile ulaşabilrsiniz" amacıyla vardır. Günümüz bilgisayarlarındaki bellek kapasitesi düşünüldüğünde çok gereği olmayan bir fonksiyondur. Yine de görürseniz şaşırmayın diye bahsetmek istedim.

Hafıza adresleri

Basit veri tipleri sözkonusu olduğunda bunlar için hafızada ayrı yerler açılır. Mesela aşağıdaki örnekte X ve Y için hafızada iki ayrı alan açılır.

                    
                        Dim X As Integer, Y As Integer
                        X = 20
                        Y = 20
                            
                            

Bellek gösterimini ise aşağıdaki gibi yapabiliriz. İki değişken de aynı değere sahip olduğu halde iki farklı alan işgal edilir: 2şer byte'tan toplam 4 byte.

post-thumb

Nesnelerde ise durum biraz farklıdır. Nesneler sözkonusu oluğunda değişkenlerde nesnenin kendisi değil, nesnenin işaret ettiği adres depolanır. Buna programlamada Pointer denir.

Aşağıdaki örnekte bellekte sadece bir alan işgal edilir, o da Object tipli değişkenlerin değeri 4 byte olduğu için toplam 4 byte'tır, 8 byte değil. Zira iki değişken de hafızadaki aynı yere işaret ediyorlar.

                    
                        Dim wb1 As Workbook, wb2 As Workbook
                        Set wb1 = Workbooks("deneme.xlsx")
                        Set wb2 = wb1
                            
                            
post-thumb

Bu da şu demek oluyor; nesne değişkeni ile nesnenin kendisi farklı şeylerdir . Aşğağıdaki örnekte VarPtr değişkenlerin bellekteki adresini verirken, ObjPtr nesnelerin kendisinin adresini verir. Adresten kastımız, uzunca bir sayıdır, bu sayının ne olduğu önemli değildir, önemli olan içeriğidir, commentlere bakınız.

                    
                        Sub hafıza()
                        Dim wb1 As Workbook
                        Dim wb2 As Workbook

                        Set wb1 = ActiveWorkbook
                        Set wb2 = wb1

                        Debug.Print wb1.Sheets.Count '1
                        Debug.Print wb2.Sheets.Count '1

                        wb1.Sheets.Add

                        Debug.Print wb1.Sheets.Count '2
                        Debug.Print wb2.Sheets.Count '2

                        'wb1 ve wb2 "değişkenlerinin" adresi
                        Debug.Print "wb1 nesne değişkeninin adresi:" & VarPtr(wb1) 'aşağıdaki ile farklı
                        Debug.Print "wb2 nesne değişkeninin adresi: " & VarPtr(wb2)

                        'wb1 ve wb2'nin işaret ettiği yerin adresi
                        Debug.Print "wb1'in adresi:" & ObjPtr(wb1) 'aşağıdaki ile aynı
                        Debug.Print "wb2'nin adresi:" & ObjPtr(wb2)
                        End Sub
                            
                            

Hafızayı temizleme

Otomatik ama gecikmeli temizlik

Bir değişkene bir nesne atadıktan sonra tekrar başka bir nesne atarsak, artık ilk nesne varolmaz, ve bir süre sonra bellekten silinir.

Mesela aşağıdaki örnekte, 1'den 10'a kadar sayıları tutan collection son satırdan itibaren yok olur ve ona erişmenini hiçbir yolu kalmaz.(Bu işlem hemen değil biraz gecikmeli olur)

                    
                        Dim coll As Collection
                        Set coll = New Collection
                        For i = 1 To 10
                          coll.Add i
                        Next i
                        Set coll = New Collection 'ilk nesne yok olur
                            
                            

Burda aslında biz bellekte iki tane collection için yer açtık ama son satıra geldiğimizde artık ilkine hiçbir şey atanmış olmadığı için Garbage Collector denen sistem bir süre sonra bunu bellekten atar, yani özetle bir nesneye hiçbir değişken işaret etmiyorsa bu nesne bellekten silinir.

Manuel ama anında temizlik

Bu yöntemi şimdiye kadar birçok örnekte gördük aslında. Bir değişkene Nothing değerini atayınca o değişkenle onun başvurduğu nesne arasındaki ilişkiyi kopartırız.

Aslında çoğu durumda bu işlem gerekli değildir, zira yukarda gördüğümüz gibi bir nesneye başvuran bir değişken kalmadığında bu nesne bellkten gecikmeli de olsa otomatikman silinir.

Ancak bazı durumlarda, özellikle döngüsel işlemlerde Nothing ataması gerekebilir. Çünkü Garbage Collector'ın ne zaman devreye gireceği belli değildir, ve biz belleği hemen boşaltmak istiyorsak işte o zaman Nothing ataması yaparız.

Mesela toplu mail gönderiminde kullandığımız aşağıdaki koda bakalım. oMail değişkenine Nothing atamak faydalıdır, zira bunu yapmazsak ilk mail nesnesi hala bir süre daha bellekte kalmaya devam edecek, taki GC gelip onu yokedene kadar. Eğer tek seferde yüzlerce mail atacaksanız bu işlemi yapmanızı şiddetle öneririm, aksi halde bellekte yüzlerce mail nesnesi birikebilir ve işlem bellek yetersizliğinden yarıda kesilebilir.

Bununla beraber son satırdaki oApp değişkenine Nothing ataması çok da kritik değildir. Ben yine de alışkanlıkla bunu yapmayı tercih ediyorum. yorum.

                    
                                Sub çoklumail_Button1_Click()
                                Dim oApp As Outlook.Application
                                Dim oMail As Outlook.MailItem
                                Dim alıcılar As Range, a As Range

                                Set oApp = New Outlook.Application
                                Set alıcılar = Range(Range("A2"), Range("A2").End(xlDown))

                                For Each a In alıcılar
                                    Set oMail = oApp.CreateItem(olMailItem)
                                    With oMail
                                        .Subject = "Doğum günü"
                                        .To = a.Value
                                        .Body = a.Offset(0, 3).Value & "Doğum gününüz kutlar, ailenizle birlikte mutlu yıllar dilerim"
                                        .Body = .Body & vbCrLf & "Gönderenin adı soyadı"
                                        .Send
                                    End With
                                    Set oMail = Nothing 'zorunlu değil ama faydalı
                                Next a

                                Set oApp = Nothing 'zorunla da değil kritik faydası da yok
                                End Sub